diff --git a/.gitignore b/.gitignore
index 6e52fa4522039a6336bc278f9d2b6217f194a35e..7ff4882a41ca87f23f0d46663f615bc66a0adaba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,3 +46,4 @@ res/i18n/lang-*
 out
 .directory
 temp
+*_uic.py
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index 0b22a0d50fa101b4efb365076ed7f9f8454a1b6b..42b0704102dc58a6571cb0f76274f42197e31eb0 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -38,9 +38,6 @@ install:
   - "%CMD_IN_ENV% conda config --add channels inso/channel/sakia"
   - "%CMD_IN_ENV% conda create -q -n test-environment python=%PYTHON_VERSION% libsodium=1.0.3 setuptools=19.2"
 
-cache:
-  - C:\Qt\5.6\5.6
-
 build_script:
   - ".\\ci\\appveyor\\build.cmd"
 
diff --git a/ci/appveyor/build.cmd b/ci/appveyor/build.cmd
index 49965b50196df5d37a81ca8873326f912b4f6501..37c9a8dd3efe0a934f58e7355c17b5dabd088cd0 100644
--- a/ci/appveyor/build.cmd
+++ b/ci/appveyor/build.cmd
@@ -5,13 +5,12 @@ call activate test-environment
 echo "%PATH%"
 echo "%QT_PLUGIN_PATH%"
 python -V
-call pyuic5 --version
-
-pyrcc5 -version
 
 lrelease -version
 
-pip install PyQt5
+call pyuic5 --version
+pyrcc5 -version
+
 pip install -r requirements.txt
 pip install pyinstaller
 pip install six
diff --git a/ci/appveyor/tests.cmd b/ci/appveyor/tests.cmd
index 094bc394e38247295483cfccd0019374eccfd14e..61e3c79ae7545d9c5f931b70fee343862959e63b 100644
--- a/ci/appveyor/tests.cmd
+++ b/ci/appveyor/tests.cmd
@@ -11,8 +11,7 @@ pyrcc5 -version
 
 lrelease -version
 
-echo "%CWD%"
-
-python setup.py test
+echo "%cd%"
+py.test tests/
 
 if %errorlevel% neq 0 exit /b 1
\ No newline at end of file
diff --git a/ci/travis/before_install.sh b/ci/travis/before_install.sh
index d83c13a9639a392c11e4ae3637ea5ff4be54d8b3..5ff6df18644fd7d351abe1f4dd71f07a47f51a62 100755
--- a/ci/travis/before_install.sh
+++ b/ci/travis/before_install.sh
@@ -16,20 +16,21 @@ then
     brew install libsodium
     ## Ensure your brew QT version is up to date. (brew install qt -> qt 4.8)
     brew install qt5
-    brew link --force qt5
     brew install pyenv-virtualenv
     pyenv update
+    pip install PyQt5
 elif [ $TRAVIS_OS_NAME == "linux" ]
 then
     sudo apt-get update
     sudo apt-get install -qq -y libxcb1 libxcb1-dev libx11-xcb1 libx11-xcb-dev libxcb-keysyms1 libxcb-keysyms1-dev libxcb-image0 \
             libxcb-image0-dev libxcb-shm0 libxcb-shm0-dev libxcb-icccm4 libxcb-icccm4-dev \
             libxcb-xfixes0-dev libxrender-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0 \
-            libxcb-render-util0-dev libxcb-glx0-dev libgl1-mesa-dri libegl1-mesa libpcre3-dev \
-            curl libdbus-1-dev libdbus-glib-1-dev autoconf automake libtool libgstreamer-plugins-base0.10-0
-    wget https://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-linux-x64-5.7.0.run
-    chmod +x qt-opensource-linux-x64-5.7.0.run
-    ./qt-opensource-linux-x64-5.7.0.run --script $HOME/build/duniter/sakia/ci/travis/qt-installer-noninteractive.qs
+            libxcb-render-util0-dev libxcb-glx0-dev libgl1-mesa-dri libegl1-mesa libpcre3 libgles2-mesa-dev \
+            freeglut3-dev libfreetype6-dev xorg-dev xserver-xorg-input-void xserver-xorg-video-dummy xpra libosmesa6-dev \
+            curl libdbus-1-dev libdbus-glib-1-dev autoconf automake libtool libgstreamer-plugins-base0.10-0 dunst
+    wget https://download.qt.io/official_releases/qt/5.7/5.7.1/qt-opensource-linux-x64-5.7.1.run
+    chmod +x qt-opensource-linux-x64-5.7.1.run
+    ./qt-opensource-linux-x64-5.7.1.run --script $HOME/build/duniter/sakia/ci/travis/qt-installer-noninteractive.qs
 
     wget http://archive.ubuntu.com/ubuntu/pool/universe/libs/libsodium/libsodium13_1.0.1-1_amd64.deb
     sudo dpkg -i libsodium13_1.0.1-1_amd64.deb
@@ -54,7 +55,4 @@ fi
 
 pyenv shell $PYENV_PYTHON_VERSION
 
-pip install PyQt5
-
-make -j 2 && make install
-pyenv rehash
+cd $HOME
diff --git a/ci/travis/build.sh b/ci/travis/build.sh
index 2cd869bb8399bdc0362effa7f2571bf84e3bb140..5b374636ea1c17decf82ec565a5bbaba8856e778 100755
--- a/ci/travis/build.sh
+++ b/ci/travis/build.sh
@@ -8,7 +8,9 @@ pyenv shell $PYENV_PYTHON_VERSION
 pip install --upgrade pip
 pyenv rehash
 pip install coveralls
+pip install pytest-cov
 pip install pyinstaller
+pip install PyQt5
 pip install -r requirements.txt
 if [ $TRAVIS_OS_NAME == "linux" ]
 then
diff --git a/ci/travis/debian/usr/share/applications/sakia.desktop b/ci/travis/debian/usr/share/applications/sakia.desktop
index 6e6c0cbe0c4fe0860c65c733d241b879152c8956..331392380d9f7dd4cbf8a2a92b1e13b5da47a64f 100644
--- a/ci/travis/debian/usr/share/applications/sakia.desktop
+++ b/ci/travis/debian/usr/share/applications/sakia.desktop
@@ -7,3 +7,4 @@ Icon=/opt/sakia/sakia.png
 Terminal=false
 Type=Application
 Categories=Utility;Application;
+MimeType=x-scheme-handler/duniter
diff --git a/ci/travis/test.sh b/ci/travis/test.sh
index 2ef4409d195aeceeaf55fceaf6a4846d1ec71ab5..ad1112601a75072e1566ada9e689c12f77e3369a 100755
--- a/ci/travis/test.sh
+++ b/ci/travis/test.sh
@@ -6,7 +6,7 @@ if [ $TRAVIS_OS_NAME == "linux" ]
 then
     export XVFBARGS="-screen 0 1280x1024x24"
     export DISPLAY=:99.0
-    sudo sh -e /etc/init.d/xvfb restart
+    sh -e /etc/init.d/xvfb start
     sleep 3
 fi
 
@@ -14,9 +14,9 @@ cd $HOME/build/duniter/sakia
 pyenv shell $PYENV_PYTHON_VERSION
 if [ $TRAVIS_OS_NAME == "linux" ]
 then
-    coverage run --source=sakia.core,sakia.gui,sakia.models setup.py test
+    py.test --cov=sakia tests/
 else
-    python setup.py test
+    py.test
 fi
 
 
diff --git a/doc/uml/api.png b/doc/uml/api.png
deleted file mode 100644
index f557d71281f747f665355c62849852fd427b0826..0000000000000000000000000000000000000000
Binary files a/doc/uml/api.png and /dev/null differ
diff --git a/doc/uml/api.pu b/doc/uml/api.pu
deleted file mode 100644
index f4a6ca46ace9b1302ecc225a51a719b5e9698648..0000000000000000000000000000000000000000
--- a/doc/uml/api.pu
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-
-package api {
-	package api.bma {
-		class BMADataAccess {
-			{static} _cache
-			{static} _request(req : Request, network)
-			{static} _post(req : Request, network)
-			{static} _broadcast(req : Request, network)
-		}
-		BMADataAccess ..> api.bma.API
-	}
-	package api.es {
-			class ESDataAccess {
-			}
-			ESDataAccess ..> api.es.API
-	}
-
-}
-
-@enduml
\ No newline at end of file
diff --git a/doc/uml/backend.png b/doc/uml/backend.png
new file mode 100644
index 0000000000000000000000000000000000000000..37c008fe788da0529c54093419b0463df4c529fa
Binary files /dev/null and b/doc/uml/backend.png differ
diff --git a/doc/uml/backend.pu b/doc/uml/backend.pu
new file mode 100644
index 0000000000000000000000000000000000000000..fdbf31b29e477d68ad5d17cba2605ee0e50fe022
--- /dev/null
+++ b/doc/uml/backend.pu
@@ -0,0 +1,51 @@
+@startuml
+
+!include data.pu
+!include processors.pu
+!include services.pu
+
+ProfileService "1" --> "1" UserParameters
+ProfileService "*" --> "1" UserParametersRepo
+
+AccountService "1" --> "1" Key
+AccountService "*" --> "1" KeyRepo
+
+
+TransactionsService "1" --> "*" Transaction
+TransactionsService "*" --> "1" TransactionProcessor
+TransactionProcessor "1" --> "1" TransactionRepo
+
+RegistryService "1" --> "*" Identity
+RegistryService "1" --> "*" Certification
+RegistryService "*" --> "1" IdentitiesProcessor
+IdentitiesProcessor "1" --> "1" IdentitiesRepo
+RegistryService "*" --> "1" CertificationProcessor
+CertificationProcessor "1" --> "1" CertificationRepo
+
+NetworkService "1" --> "*" Node
+NetworkService "*" --> "1" NodesProcessor
+NodesProcessor "1" --> "1" NodesRepo
+
+BlockchainService "1" --> "1" Blockchain
+BlockchainService "1" --> "1" Community
+BlockchainService "*" --> "1" BlockchainProcessor
+BlockchainProcessor "1" --> "1" BlockchainRepo
+BlockchainService "*" --> "1" CommunityProcessor
+CommunityProcessor "1" --> "1" CommunityRepo
+
+package Connectors {
+    class BMAConnector << (S,cyan) >>  {
+        get()
+        post()
+        broadcast()
+    }
+}
+
+AccountService --> BMAConnector
+BlockchainProcessor --> BMAConnector
+CommunityProcessor --> BMAConnector
+TransactionProcessor --> BMAConnector
+IdentitiesProcessor --> BMAConnector
+CertificationProcessor --> BMAConnector
+
+@enduml
\ No newline at end of file
diff --git a/doc/uml/core-classes.png b/doc/uml/core-classes.png
deleted file mode 100644
index 54e0d69e92bc3200fa7bcd1bc6d2458cad95ff81..0000000000000000000000000000000000000000
Binary files a/doc/uml/core-classes.png and /dev/null differ
diff --git a/doc/uml/core-classes.pu b/doc/uml/core-classes.pu
deleted file mode 100644
index c1463421238e08d35f9872ef6c926a4aa55c5003..0000000000000000000000000000000000000000
--- a/doc/uml/core-classes.pu
+++ /dev/null
@@ -1,108 +0,0 @@
-@startuml
-
-hide fields
-hide methods
-
-package core {
-	class App {
-	-- Signals --
-	current_account_changed(str : account_name)
-	data_changed()
-	-- Slots --
-	-- Properties --
-	current_account
-	accounts
-	-- Methods --
-	}
-	App --* Account : accounts
-
-	class Account {
-	-- Signals --
-	wallets_changed(int : nb_wallets)
-	community_added(int : index)
-	community_removed(int : index)
-	data_changed()
-	-- Slots --
-	-- Properties --
-	communities
-	wallets
-	-- Methods --
-	}
-	Account "1" --* "*" Wallet
-	Account "1" --* "*" Community
-
-	class Wallet {
-		-- Signals --
-	money_received(Transfer)
-	money_sent(Transfer)
-	name_changed(str : new_name
-	data_changed()
-	-- Slots --
-	-- Properties --
-	transfers
-	-- Methods --
-	}
-	Wallet "1" --* "*" Transfer
-
-	class Transfer {
-	-- Signals --
-	state_changed(int : new_state)
-	-- Slots --
-	-- Properties --
-	-- Methods --
-	}
-
-	class Community {
-	-- Signals --
-	members_changed()
-	data_changed()
-	-- Slots --
-	-- Properties --
-	network
-	-- Methods --
-
-	}
-	App --> Identity
-	class Identity {
-		{static} _identities
-		{static} load(data : dict)
-		{static} lookup(search : str)
-	}
-
-}
-
-
-package net {
-	class Network {
-	-- Signals --
-	node_found(int : index)
-	node_removed(int : index)
-	block_found(int : block_number)
-	-- Slots --
-	-- Properties --
-	nodes
-	root_nodes
-	-- Methods --
-	}
-	Community "1" --* "1" Network
-	Network "1" --* "*" Node
-
-	class Node {
-	-- Signals --
-	changed()
-	-- Slots --
-	-- Properties --
-	endpoints
-	pubkey
-	uid
-	block
-	state
-	-- Methods --
-	}
-	
-	Network "1" --* "1" BmaAccess
-	
-	class BmaAccess {
-	}
-}
-@enduml
diff --git a/doc/uml/cutecoin.png b/doc/uml/cutecoin.png
deleted file mode 100644
index 417230847dec853471dc5e7a618273d5be5e898b..0000000000000000000000000000000000000000
Binary files a/doc/uml/cutecoin.png and /dev/null differ
diff --git a/doc/uml/cutecoin.pu b/doc/uml/cutecoin.pu
deleted file mode 100644
index d79f67dfb4614935631fd76663cdd922aac6b841..0000000000000000000000000000000000000000
--- a/doc/uml/cutecoin.pu
+++ /dev/null
@@ -1,36 +0,0 @@
-@startuml
-
-!include core-classes.pu
-!include gui-classes.pu
-!include models-classes.pu
-!include api.pu
-
-MainWindow "1" --> "1" App
-
-CertificationDialog --> Community
-TransferDialog --> Community
-
-CurrencyTab "1" --> "1" Community
-
-CommunityTab -right-> IdentitiesFilterProxyModel
-NetworkTab -right-> NetworkFilterProxyModel
-WalletTab -right-> WalletsFilterProxyModel
-
-WalletsFilterProxyModel -up-> Wallet
-NetworkFilterProxyModel -up-> Network
-TxHistoryFilterProxyModel -up-> Transfer
-
-ConfigureAccountDialog --> CommunitiesListModel
-ConfigureCommunityDialog --> RootNodesTableModel
-
-ConfigureAccountDialog --> Account
-ConfigureCommunityDialog --> Community
-
-Account ..> BMADataAccess
-Community ..> BMADataAccess
-Wallet ..> BMADataAccess
-Transfer ..> BMADataAccess
-Identity ..> BMADataAccess
-BMADataAccess .left.> Network
-
-@enduml
\ No newline at end of file
diff --git a/doc/uml/data.png b/doc/uml/data.png
new file mode 100644
index 0000000000000000000000000000000000000000..9926347691564e7f3e43ae5cc165e46f32d5b913
Binary files /dev/null and b/doc/uml/data.png differ
diff --git a/doc/uml/data.pu b/doc/uml/data.pu
new file mode 100644
index 0000000000000000000000000000000000000000..8303a44b0c7ee491550adbd5390c2deef6ebf06f
--- /dev/null
+++ b/doc/uml/data.pu
@@ -0,0 +1,180 @@
+@startuml
+
+
+class Identity << (D,orchid) >> {
+    currency: str (FK)
+    uid: str
+    pubkey: str (PK)
+    blockstamp: BlockUID
+    timestamp: int
+    signature: str
+    written_on: BlockUID
+    revoked_on: BlockUID
+    member: bool
+    membership_buid: BlockUID
+    membership_timestamp: int
+    membership_type: str
+    membership_written_on: BlockUID
+}
+
+class Certification << (D,orchid) >> {
+    currency: str (PK)
+    certifier: str (PK)
+    certified: str (PK)
+    blockstamp: BlockUID (PK)
+    timestamp: int
+    signature: str
+    written_on: BlockUID
+}
+
+class Transaction  << (D,orchid) >> {
+    currency: str (FK)
+    blockstamp: str
+    locktime: int
+    issuer: str
+    recipient: str
+    amount: int
+    comment: str
+    sha_hash: str (PK)
+}
+
+class Community  << (D,orchid) >> {
+    profile: str (FK)
+    pubkey: str (FK)
+    currency: str (PK)
+    c: float
+    dt: int
+    ud0: int
+    sig_period: int
+    sig_stock: int
+    sig_window: int
+    sig_validity: int
+    sig_qty: int
+    xpercent: float
+    ms_validity: int
+    step_max: int
+    median_time_blocks: int
+    avg_gen_time: int
+    dt_diff_eval: int
+    blocks_rot: int
+    percent_rot: float
+}
+
+class Blockchain  << (D,orchid) >> {
+    currency: str (PK)
+    current_buid: BlockUID
+    nb_members: int
+    current_mass: int
+    median_time: int
+    last_ud: int
+    last_ud_base: int
+    previous_mass: int
+}
+
+class Node  << (D,orchid) >> {
+    currency: str (FK)
+    endpoints: str
+    uid: str
+    pubkey: str (PK)
+    current_buid: BlockUID
+    previous_buid: BlockUID
+    state: int
+    software: str
+    version: str
+    merkle_nodes: dict
+}
+
+class Key  << (D,orchid) >> {
+    pubkey: str (PK)
+    salt: str
+}
+
+class UserParameters  << (D,orchid) >> {
+    profile: str (PK)
+    lang: str
+    ref: 0
+    expert_mode: bool,
+    digits_after_comma: int
+    maximized: bool
+    notifications: bool
+    enable_proxy: bool
+    proxy_type: int
+    proxy_address: str
+    proxy_port: 8080: int
+    international_system_of_units: bool
+    auto_refresh: bool
+    forgetfulness: bool
+}
+
+class UserParametersRepo << (R,orange) >> {
+    Create()
+    Update()
+    Save()
+    Drop()
+}
+
+
+class KeyRepo << (R,orange) >>  {
+    Create()
+    Update()
+    Save()
+    Drop()
+}
+
+
+class NodesRepo << (R,orange) >>   {
+    Create()
+    Update()
+    Save()
+    Drop()
+}
+
+class BlockchainRepo << (R,orange) >>  {
+    Create()
+    Update()
+    Save()
+    Drop()
+}
+class CommunityRepo << (R,orange) >>  {
+    Commit()
+    Update()
+    Save()
+    Drop()
+}
+class TransactionRepo << (R,orange) >>  {
+    Commit()
+    Update()
+    Save()
+    Drop()
+}
+class CertificationRepo  << (R,orange) >> {
+    Commit()
+    Update()
+    Save()
+    Drop()
+}
+class IdentitiesRepo  << (R,orange) >>  {
+    Commit()
+    Update()
+    Save()
+    Drop()
+}
+
+IdentitiesRepo "1" --* "*" Identity
+
+CertificationRepo "1" --* "*" Certification
+
+TransactionRepo "1" --* "*" Transaction
+
+CommunityRepo "1" --* "*" Community
+
+BlockchainRepo "1" --* "*" Blockchain
+
+NodesRepo "1" --* "*" Node
+
+KeyRepo "1" --* "*" Key
+
+UserParametersRepo "1" --* "*" UserParameters
+
+
+@enduml
\ No newline at end of file
diff --git a/doc/uml/gui-classes.png b/doc/uml/gui-classes.png
deleted file mode 100644
index e00da1f229639c06e0ee5f201ea8d1d7e487f111..0000000000000000000000000000000000000000
Binary files a/doc/uml/gui-classes.png and /dev/null differ
diff --git a/doc/uml/gui-classes.pu b/doc/uml/gui-classes.pu
deleted file mode 100644
index 1b00e1a188119584fcbc71d86e0ccf60b92d3ee7..0000000000000000000000000000000000000000
--- a/doc/uml/gui-classes.pu
+++ /dev/null
@@ -1,50 +0,0 @@
-@startuml
-
-
-package gui {
-		class MainWindow {
-		}
-		MainWindow "1" --* "1" CommunityView
-
-		class CommunityView {
-		}
-		CommunityView "1" --* "1" WalletTab
-		CommunityView "1" -down-* "1" InformationsTab
-		CommunityView "1" --* "1" TransactionsTab
-		CommunityView "1" --* "1" IdentitiesTab
-		CommunityView "1" --* "1" WotTab
-		CommunityView "1" -down-* "1" NetworkTab
-
-
-		class WalletTab {
-		}
-
-		class InformationsTab {
-		}
-
-		class TransactionsTab {
-		}
-
-		class NetworkTab {
-		}
-
-		class IdentitiesTab {
-		}
-
-		class WotTab {
-		}
-		package dialogs {
-		class CertificationDialog
-		class TransferDialog
-		class ContactDialog
-		class ConfigureAccountDialog
-		class ConfigureCommunityDialog
-		}
-
-	MainWindow --> CertificationDialog
-	MainWindow --> TransferDialog
-	MainWindow --> ContactDialog
-	MainWindow --> ConfigureAccountDialog
-	ConfigureAccountDialog --> ConfigureCommunityDialog
-}
-@enduml
diff --git a/doc/uml/models-classes.png b/doc/uml/models-classes.png
deleted file mode 100644
index 5833c6ecaf2c4c1b57601a5c682bdb941c2a61ea..0000000000000000000000000000000000000000
Binary files a/doc/uml/models-classes.png and /dev/null differ
diff --git a/doc/uml/models-classes.pu b/doc/uml/models-classes.pu
deleted file mode 100644
index 1650739d5d4c65bfa48ba299c543b4d019cde42b..0000000000000000000000000000000000000000
--- a/doc/uml/models-classes.pu
+++ /dev/null
@@ -1,39 +0,0 @@
-@startuml
-
-package models {
-	class WalletsFilterProxyModel {
-	}
-
-	WalletsFilterProxyModel --> WalletsTableModel : source
-
-	class WalletsTableModel {
-	}
-
-	class IdentitiesFilterProxyModel {
-	}
-	IdentitiesFilterProxyModel --> IdentitiesTableModel : source
-
-	class IdentitiesTableModel {
-	}
-
-	class NetworkFilterProxyModel {
-	}
-	NetworkFilterProxyModel --> NetworkTableModel : source
-
-	class NetworkTableModel {
-	}
-
-	class TxHistoryFilterProxyModel {
-	}
-	TxHistoryFilterProxyModel --> TxHistoryTableModel : source
-	class TxHistoryTableModel {
-	}
-
-	class CommunitiesListModel {
-	}
-
-	class RootNodesTableModel {
-	}
-}
-
-@enduml
\ No newline at end of file
diff --git a/doc/uml/network.png b/doc/uml/network.png
index c31386d981219956b30d8dc291e48fd9d69c4745..9de1956d213cea9ce16baf9033d2a17b835dc4f9 100644
Binary files a/doc/uml/network.png and b/doc/uml/network.png differ
diff --git a/doc/uml/processors.png b/doc/uml/processors.png
new file mode 100644
index 0000000000000000000000000000000000000000..da005cebef0f6e9be98173e638a49fa91061fe2f
Binary files /dev/null and b/doc/uml/processors.png differ
diff --git a/doc/uml/processors.pu b/doc/uml/processors.pu
new file mode 100644
index 0000000000000000000000000000000000000000..92483fb87278b9722a21ae71efb14ed6f951e0f6
--- /dev/null
+++ b/doc/uml/processors.pu
@@ -0,0 +1,21 @@
+@startuml
+
+
+class IdentitiesProcessor  << (P,lightgreen) >> {
+    find_identities()
+    find_certifiers_of()
+    find_certified_by()
+}
+
+class CertificationProcessor  << (P,lightgreen) >> {
+}
+class TransactionProcessor  << (P,lightgreen) >> {
+}
+class CommunityProcessor  << (P,lightgreen) >> {
+}
+class BlockchainProcessor  << (P,lightgreen) >> {
+}
+class NodesProcessor  << (P,lightgreen) >> {
+}
+
+@enduml
\ No newline at end of file
diff --git a/doc/uml/requests.png b/doc/uml/requests.png
index 85339a6ae644809ea4836ee4c109da64144f3e71..acfe67960fb3a49efc897fbd6b9d5942b77fe157 100644
Binary files a/doc/uml/requests.png and b/doc/uml/requests.png differ
diff --git a/doc/uml/services.png b/doc/uml/services.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa2287997a8fb761142da8c2f1b8a16a28515ab9
Binary files /dev/null and b/doc/uml/services.png differ
diff --git a/doc/uml/services.pu b/doc/uml/services.pu
new file mode 100644
index 0000000000000000000000000000000000000000..7811ff112bc8c7d9571d3466c02c2539a4285ac7
--- /dev/null
+++ b/doc/uml/services.pu
@@ -0,0 +1,36 @@
+@startuml
+
+
+
+class ProfileService << (S,cyan) >>  {
+    add_connection()
+    remove_connection()
+}
+
+class AccountService << (S,cyan) >>  {
+    send_transaction()
+    send_certification()
+    send_membership()
+    send_identity()
+    send_revokation()
+}
+
+class TransactionsService << (S,cyan) >>  {
+    handle_new_block()
+    refresh_transactions()
+    rollback_transactions()
+}
+
+class RegistryService << (S,cyan) >> {
+    handle_new_block()
+}
+
+class NetworkService << (S,cyan) >>  {
+    discover_network()
+}
+
+class BlockchainService  << (S,cyan) >>  {
+    receive_block()
+}
+
+@enduml
\ No newline at end of file
diff --git a/doc/uml/tx_lifecycle.png b/doc/uml/tx_lifecycle.png
index 7f7102c0cac56b9a5aafa90498611f8caa9df429..83b2ea7451902696950ae5b9761f6ca4a225654f 100644
Binary files a/doc/uml/tx_lifecycle.png and b/doc/uml/tx_lifecycle.png differ
diff --git a/gen_resources.py b/gen_resources.py
index 2a269dfe5b9d6c49d537caea6dd20b9eb3d92d4b..b73cfa715e5bd3c4889ec59ed3c91df88195eaad 100644
--- a/gen_resources.py
+++ b/gen_resources.py
@@ -2,6 +2,8 @@
 # -*- coding: utf-8 -*-
 import sys, os, multiprocessing, subprocess
 
+
+sakia = os.path.abspath(os.path.join(os.path.dirname(__file__)))
 resources = os.path.abspath(os.path.join(os.path.dirname(__file__), 'res'))
 gen_ui = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src', 'sakia', 'gen_resources'))
 gen_resources = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src'))
@@ -12,11 +14,15 @@ def convert_ui(args, **kwargs):
 def build_resources():
     try:
         to_process = []
-        for root, dirs, files in os.walk(resources):
+        for root, dirs, files in os.walk(sakia):
             for f in files:
                 if f.endswith('.ui'):
                     source = os.path.join(root, f)
-                    dest = os.path.join(gen_ui, os.path.splitext(os.path.basename(source))[0]+'_uic.py')
+                    if os.path.commonpath([resources, root]) == resources:
+                        dest = os.path.join(gen_ui, os.path.splitext(os.path.basename(source))[0]+'_uic.py')
+                    else:
+                        dest = os.path.join(root, os.path.splitext(os.path.basename(source))[0]+'_uic.py')
+
                     exe = 'pyuic5'
                 elif f.endswith('.qrc'):
                     source = os.path.join(root, f)
diff --git a/requirements.txt b/requirements.txt
index 145df061d3a4c6fa1c9cb843d1b39244403a12bf..34e29cb109b7f7257eb14df7dd4d82fbb4dfac49 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,8 @@
-duniterpy>=0.20.dev0
 git+https://github.com/Insoleet/quamash.git@master
 asynctest
-networkx
\ No newline at end of file
+networkx
+attrs
+duniter-mirage
+duniterpy>=0.40
+pytest
+pytest-asyncio
diff --git a/res/ui/account_cfg.ui b/res/ui/account_cfg.ui
deleted file mode 100644
index d1010bf905e24889be32e482d1e201103fe6910e..0000000000000000000000000000000000000000
--- a/res/ui/account_cfg.ui
+++ /dev/null
@@ -1,623 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>AccountConfigurationDialog</class>
- <widget class="QDialog" name="AccountConfigurationDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>527</width>
-    <height>359</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Add an account</string>
-  </property>
-  <property name="modal">
-   <bool>true</bool>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QStackedWidget" name="stacked_pages">
-     <property name="currentIndex">
-      <number>0</number>
-     </property>
-     <widget class="QWidget" name="page_init">
-      <layout class="QVBoxLayout" name="verticalLayout_4">
-       <item>
-        <widget class="QGroupBox" name="groupBox">
-         <property name="title">
-          <string>Account parameters</string>
-         </property>
-         <layout class="QVBoxLayout" name="verticalLayout_2">
-          <item>
-           <spacer name="verticalSpacer_3">
-            <property name="orientation">
-             <enum>Qt::Vertical</enum>
-            </property>
-            <property name="sizeHint" stdset="0">
-             <size>
-              <width>20</width>
-              <height>40</height>
-             </size>
-            </property>
-           </spacer>
-          </item>
-          <item>
-           <layout class="QHBoxLayout" name="horizontalLayout">
-            <item>
-             <widget class="QLabel" name="label_action">
-              <property name="text">
-               <string>Account name (uid)</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLineEdit" name="edit_account_name"/>
-            </item>
-           </layout>
-          </item>
-          <item>
-           <layout class="QHBoxLayout" name="horizontalLayout_8">
-            <property name="topMargin">
-             <number>6</number>
-            </property>
-            <item>
-             <spacer name="horizontalSpacer_2">
-              <property name="orientation">
-               <enum>Qt::Horizontal</enum>
-              </property>
-              <property name="sizeHint" stdset="0">
-               <size>
-                <width>40</width>
-                <height>20</height>
-               </size>
-              </property>
-             </spacer>
-            </item>
-            <item>
-             <widget class="QPushButton" name="button_delete">
-              <property name="styleSheet">
-               <string notr="true">color: rgb(255, 0, 0);</string>
-              </property>
-              <property name="text">
-               <string>Delete account</string>
-              </property>
-             </widget>
-            </item>
-           </layout>
-          </item>
-          <item>
-           <spacer name="verticalSpacer_4">
-            <property name="orientation">
-             <enum>Qt::Vertical</enum>
-            </property>
-            <property name="sizeHint" stdset="0">
-             <size>
-              <width>20</width>
-              <height>40</height>
-             </size>
-            </property>
-           </spacer>
-          </item>
-         </layout>
-        </widget>
-       </item>
-      </layout>
-     </widget>
-     <widget class="QWidget" name="page_gpg">
-      <layout class="QVBoxLayout" name="verticalLayout_7">
-       <item>
-        <widget class="QLabel" name="label_3">
-         <property name="text">
-          <string>Key parameters</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <spacer name="verticalSpacer_2">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item>
-        <layout class="QHBoxLayout" name="horizontalLayout_2">
-         <item>
-          <layout class="QVBoxLayout" name="verticalLayout_6">
-           <item>
-            <layout class="QHBoxLayout" name="horizontalLayout_5"/>
-           </item>
-           <item>
-            <widget class="QLineEdit" name="edit_salt">
-             <property name="text">
-              <string/>
-             </property>
-             <property name="placeholderText">
-              <string>Secret key</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLineEdit" name="edit_password">
-             <property name="echoMode">
-              <enum>QLineEdit::Password</enum>
-             </property>
-             <property name="placeholderText">
-              <string>Your password</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLineEdit" name="edit_password_repeat">
-             <property name="text">
-              <string/>
-             </property>
-             <property name="echoMode">
-              <enum>QLineEdit::Password</enum>
-             </property>
-             <property name="placeholderText">
-              <string>Please repeat your password</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QGroupBox" name="groupBox_3">
-             <property name="title">
-              <string>Scrypt Params</string>
-             </property>
-             <layout class="QVBoxLayout" name="verticalLayout_8">
-              <item>
-               <layout class="QHBoxLayout" name="horizontalLayout_9">
-                <property name="topMargin">
-                 <number>6</number>
-                </property>
-                <item>
-                 <widget class="QLabel" name="label_5">
-                  <property name="text">
-                   <string>Key strength : </string>
-                  </property>
-                 </widget>
-                </item>
-                <item>
-                 <widget class="QComboBox" name="combo_scrypt">
-                  <item>
-                   <property name="text">
-                    <string>Light</string>
-                   </property>
-                  </item>
-                  <item>
-                   <property name="text">
-                    <string>Secure</string>
-                   </property>
-                  </item>
-                  <item>
-                   <property name="text">
-                    <string>Hardest</string>
-                   </property>
-                  </item>
-                  <item>
-                   <property name="text">
-                    <string>Extreme</string>
-                   </property>
-                  </item>
-                 </widget>
-                </item>
-                <item>
-                 <spacer name="horizontalSpacer_5">
-                  <property name="orientation">
-                   <enum>Qt::Horizontal</enum>
-                  </property>
-                  <property name="sizeHint" stdset="0">
-                   <size>
-                    <width>40</width>
-                    <height>20</height>
-                   </size>
-                  </property>
-                 </spacer>
-                </item>
-                <item>
-                 <widget class="QLabel" name="label">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                  <property name="text">
-                   <string>N: </string>
-                  </property>
-                 </widget>
-                </item>
-                <item>
-                 <widget class="QSpinBox" name="spin_N">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                  <property name="maximum">
-                   <number>0</number>
-                  </property>
-                  <property name="value">
-                   <number>0</number>
-                  </property>
-                  <property name="displayIntegerBase">
-                   <number>10</number>
-                  </property>
-                 </widget>
-                </item>
-                <item>
-                 <widget class="QLabel" name="label_2">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                  <property name="text">
-                   <string>r : </string>
-                  </property>
-                 </widget>
-                </item>
-                <item>
-                 <widget class="QSpinBox" name="spin_r">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                  <property name="displayIntegerBase">
-                   <number>10</number>
-                  </property>
-                 </widget>
-                </item>
-                <item>
-                 <widget class="QLabel" name="label_4">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                  <property name="text">
-                   <string>p : </string>
-                  </property>
-                 </widget>
-                </item>
-                <item>
-                 <widget class="QSpinBox" name="spin_p">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                 </widget>
-                </item>
-               </layout>
-              </item>
-             </layout>
-            </widget>
-           </item>
-           <item>
-            <layout class="QHBoxLayout" name="horizontalLayout_6">
-             <property name="topMargin">
-              <number>5</number>
-             </property>
-             <item>
-              <widget class="QLabel" name="label_info">
-               <property name="text">
-                <string/>
-               </property>
-              </widget>
-             </item>
-             <item>
-              <widget class="QPushButton" name="button_generate">
-               <property name="text">
-                <string>Show public key</string>
-               </property>
-              </widget>
-             </item>
-            </layout>
-           </item>
-          </layout>
-         </item>
-        </layout>
-       </item>
-       <item>
-        <spacer name="verticalSpacer">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-      </layout>
-     </widget>
-     <widget class="QWidget" name="page__communities">
-      <layout class="QVBoxLayout" name="verticalLayout_5">
-       <item>
-        <widget class="QGroupBox" name="groupBox_2">
-         <property name="title">
-          <string>Communities</string>
-         </property>
-         <layout class="QVBoxLayout" name="verticalLayout_3">
-          <item>
-           <widget class="QListView" name="list_communities">
-            <property name="contextMenuPolicy">
-             <enum>Qt::DefaultContextMenu</enum>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <layout class="QHBoxLayout" name="horizontalLayout_3">
-            <item>
-             <widget class="QPushButton" name="button_add_community">
-              <property name="text">
-               <string>Add a community</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QPushButton" name="button_remove_community">
-              <property name="text">
-               <string>Remove selected community</string>
-              </property>
-             </widget>
-            </item>
-           </layout>
-          </item>
-         </layout>
-        </widget>
-       </item>
-      </layout>
-     </widget>
-    </widget>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_4">
-     <property name="topMargin">
-      <number>5</number>
-     </property>
-     <item>
-      <widget class="QPushButton" name="button_previous">
-       <property name="enabled">
-        <bool>false</bool>
-       </property>
-       <property name="text">
-        <string>Previous</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer name="horizontalSpacer">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>20</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-     <item>
-      <widget class="QPushButton" name="button_next">
-       <property name="text">
-        <string>Next</string>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections>
-  <connection>
-   <sender>button_add_community</sender>
-   <signal>clicked()</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>open_process_add_community()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>109</x>
-     <y>237</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_remove_community</sender>
-   <signal>clicked()</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>action_remove_community()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>290</x>
-     <y>237</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>list_communities</sender>
-   <signal>doubleClicked(QModelIndex)</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>open_process_edit_community(QModelIndex)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>199</x>
-     <y>180</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_next</sender>
-   <signal>clicked()</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>next()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>349</x>
-     <y>278</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_previous</sender>
-   <signal>clicked()</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>previous()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>49</x>
-     <y>278</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>edit_salt</sender>
-   <signal>textChanged(QString)</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>action_edit_account_key()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>199</x>
-     <y>69</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>118</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>edit_password</sender>
-   <signal>textChanged(QString)</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>action_edit_account_key()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>199</x>
-     <y>98</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>118</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>edit_password_repeat</sender>
-   <signal>textChanged(QString)</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>action_edit_account_key()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>199</x>
-     <y>127</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>118</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>edit_account_name</sender>
-   <signal>textChanged(QString)</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>action_edit_account_parameters()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>240</x>
-     <y>110</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>118</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_generate</sender>
-   <signal>clicked()</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>action_show_pubkey()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>290</x>
-     <y>161</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>118</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_delete</sender>
-   <signal>clicked()</signal>
-   <receiver>AccountConfigurationDialog</receiver>
-   <slot>action_delete_account()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>325</x>
-     <y>146</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>118</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <slot>open_process_add_community()</slot>
-  <slot>key_changed(int)</slot>
-  <slot>action_remove_community()</slot>
-  <slot>open_process_edit_community(QModelIndex)</slot>
-  <slot>next()</slot>
-  <slot>previous()</slot>
-  <slot>open_import_key()</slot>
-  <slot>open_generate_account_key()</slot>
-  <slot>action_edit_account_key()</slot>
-  <slot>action_edit_account_parameters()</slot>
-  <slot>action_show_pubkey()</slot>
-  <slot>action_delete_account()</slot>
- </slots>
-</ui>
diff --git a/res/ui/certifications_tab.ui b/res/ui/certifications_tab.ui
deleted file mode 100644
index b7e8eacfa6aa09ab59782740abc8f1bd5d9e91e4..0000000000000000000000000000000000000000
--- a/res/ui/certifications_tab.ui
+++ /dev/null
@@ -1,165 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>certificationsTabWidget</class>
- <widget class="QWidget" name="certificationsTabWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>481</width>
-    <height>456</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QGroupBox" name="groupbox_balance">
-     <property name="title">
-      <string>Certifications</string>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout_4">
-      <item>
-       <widget class="QLabel" name="label_resume">
-        <property name="font">
-         <font>
-          <pointsize>22</pointsize>
-          <weight>75</weight>
-          <bold>true</bold>
-         </font>
-        </property>
-        <property name="text">
-         <string>loading...</string>
-        </property>
-        <property name="alignment">
-         <set>Qt::AlignHCenter|Qt::AlignTop</set>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <layout class="QVBoxLayout" name="verticalLayout_3">
-     <item>
-      <layout class="QHBoxLayout" name="horizontalLayout_2">
-       <property name="topMargin">
-        <number>5</number>
-       </property>
-       <item>
-        <widget class="QDateTimeEdit" name="date_from">
-         <property name="displayFormat">
-          <string>dd/MM/yyyy</string>
-         </property>
-         <property name="calendarPopup">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QDateTimeEdit" name="date_to">
-         <property name="displayFormat">
-          <string>dd/MM/yyyy</string>
-         </property>
-         <property name="calendarPopup">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-      </layout>
-     </item>
-     <item>
-      <widget class="QProgressBar" name="progressbar">
-       <property name="maximum">
-        <number>0</number>
-       </property>
-       <property name="value">
-        <number>-1</number>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QTableView" name="table_history">
-       <property name="contextMenuPolicy">
-        <enum>Qt::CustomContextMenu</enum>
-       </property>
-       <property name="alternatingRowColors">
-        <bool>true</bool>
-       </property>
-       <property name="showGrid">
-        <bool>true</bool>
-       </property>
-       <attribute name="horizontalHeaderShowSortIndicator" stdset="0">
-        <bool>true</bool>
-       </attribute>
-       <attribute name="horizontalHeaderStretchLastSection">
-        <bool>true</bool>
-       </attribute>
-       <attribute name="verticalHeaderVisible">
-        <bool>false</bool>
-       </attribute>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <resources>
-  <include location="../icons/icons.qrc"/>
- </resources>
- <connections>
-  <connection>
-   <sender>table_history</sender>
-   <signal>customContextMenuRequested(QPoint)</signal>
-   <receiver>certificationsTabWidget</receiver>
-   <slot>history_context_menu()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>273</x>
-     <y>183</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>830</x>
-     <y>802</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>date_from</sender>
-   <signal>dateChanged(QDate)</signal>
-   <receiver>certificationsTabWidget</receiver>
-   <slot>dates_changed()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>102</x>
-     <y>28</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>date_to</sender>
-   <signal>dateChanged(QDate)</signal>
-   <receiver>certificationsTabWidget</receiver>
-   <slot>dates_changed()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>297</x>
-     <y>28</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <slot>history_context_menu()</slot>
-  <slot>dates_changed()</slot>
- </slots>
-</ui>
diff --git a/res/ui/community_cfg.ui b/res/ui/community_cfg.ui
deleted file mode 100644
index a31cb57384fab2f61fa4dd97c75d6246fb7e6217..0000000000000000000000000000000000000000
--- a/res/ui/community_cfg.ui
+++ /dev/null
@@ -1,333 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>CommunityConfigurationDialog</class>
- <widget class="QDialog" name="CommunityConfigurationDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>400</width>
-    <height>317</height>
-   </rect>
-  </property>
-  <property name="contextMenuPolicy">
-   <enum>Qt::CustomContextMenu</enum>
-  </property>
-  <property name="windowTitle">
-   <string>Add a community</string>
-  </property>
-  <property name="modal">
-   <bool>true</bool>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QStackedWidget" name="stacked_pages">
-     <property name="currentIndex">
-      <number>0</number>
-     </property>
-     <widget class="QWidget" name="page_node">
-      <layout class="QVBoxLayout" name="verticalLayout_4">
-       <item>
-        <spacer name="verticalSpacer_2">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item>
-        <widget class="QLabel" name="label">
-         <property name="text">
-          <string>Please enter the address of a node :</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <layout class="QHBoxLayout" name="horizontalLayout">
-         <property name="rightMargin">
-          <number>5</number>
-         </property>
-         <item>
-          <widget class="QLineEdit" name="lineedit_server"/>
-         </item>
-         <item>
-          <widget class="QLabel" name="label_double_dot">
-           <property name="text">
-            <string>:</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QSpinBox" name="spinbox_port">
-           <property name="maximum">
-            <number>65535</number>
-           </property>
-           <property name="value">
-            <number>8001</number>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item>
-        <spacer name="verticalSpacer">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item>
-        <layout class="QVBoxLayout" name="verticalLayout_5">
-         <property name="topMargin">
-          <number>6</number>
-         </property>
-         <item>
-          <widget class="QPushButton" name="button_register">
-           <property name="text">
-            <string>Register your account</string>
-           </property>
-           <property name="icon">
-            <iconset resource="../icons/icons.qrc">
-             <normaloff>:/icons/new_membership</normaloff>:/icons/new_membership</iconset>
-           </property>
-           <property name="iconSize">
-            <size>
-             <width>32</width>
-             <height>32</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QPushButton" name="button_connect">
-           <property name="text">
-            <string>Connect using your account</string>
-           </property>
-           <property name="icon">
-            <iconset resource="../icons/icons.qrc">
-             <normaloff>:/icons/connect_icon</normaloff>:/icons/connect_icon</iconset>
-           </property>
-           <property name="iconSize">
-            <size>
-             <width>32</width>
-             <height>32</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QPushButton" name="button_guest">
-           <property name="text">
-            <string>Connect as a guest</string>
-           </property>
-           <property name="icon">
-            <iconset resource="../icons/icons.qrc">
-             <normaloff>:/icons/guest_icon</normaloff>:/icons/guest_icon</iconset>
-           </property>
-           <property name="iconSize">
-            <size>
-             <width>32</width>
-             <height>32</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QLabel" name="label_error">
-           <property name="text">
-            <string/>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-      </layout>
-     </widget>
-     <widget class="QWidget" name="page_add_nodes">
-      <layout class="QVBoxLayout" name="verticalLayout_2">
-       <item>
-        <widget class="QGroupBox" name="groupBox_2">
-         <property name="title">
-          <string>Communities nodes</string>
-         </property>
-         <layout class="QVBoxLayout" name="verticalLayout_3">
-          <item>
-           <widget class="QTreeView" name="tree_peers">
-            <property name="contextMenuPolicy">
-             <enum>Qt::CustomContextMenu</enum>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <layout class="QHBoxLayout" name="horizontalLayout_3">
-            <item>
-             <widget class="QLineEdit" name="lineedit_add_address">
-              <property name="text">
-               <string/>
-              </property>
-              <property name="placeholderText">
-               <string>Server</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QSpinBox" name="spinbox_add_port">
-              <property name="minimum">
-               <number>0</number>
-              </property>
-              <property name="maximum">
-               <number>65535</number>
-              </property>
-              <property name="singleStep">
-               <number>1</number>
-              </property>
-              <property name="value">
-               <number>8081</number>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QPushButton" name="button_add">
-              <property name="text">
-               <string>Add</string>
-              </property>
-             </widget>
-            </item>
-           </layout>
-          </item>
-         </layout>
-        </widget>
-       </item>
-      </layout>
-     </widget>
-    </widget>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="layout_previous_next">
-     <item>
-      <widget class="QPushButton" name="button_previous">
-       <property name="enabled">
-        <bool>false</bool>
-       </property>
-       <property name="text">
-        <string>Previous</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer name="horizontalSpacer_2">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>20</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-     <item>
-      <widget class="QPushButton" name="button_next">
-       <property name="enabled">
-        <bool>true</bool>
-       </property>
-       <property name="text">
-        <string>Next</string>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <resources>
-  <include location="../icons/icons.qrc"/>
- </resources>
- <connections>
-  <connection>
-   <sender>button_add</sender>
-   <signal>clicked()</signal>
-   <receiver>CommunityConfigurationDialog</receiver>
-   <slot>add_node()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>337</x>
-     <y>236</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>tree_peers</sender>
-   <signal>customContextMenuRequested(QPoint)</signal>
-   <receiver>CommunityConfigurationDialog</receiver>
-   <slot>showContextMenu(QPoint)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>199</x>
-     <y>128</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_next</sender>
-   <signal>clicked()</signal>
-   <receiver>CommunityConfigurationDialog</receiver>
-   <slot>next()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>349</x>
-     <y>278</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_previous</sender>
-   <signal>clicked()</signal>
-   <receiver>CommunityConfigurationDialog</receiver>
-   <slot>previous()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>49</x>
-     <y>278</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <slot>add_node()</slot>
-  <slot>showContextMenu(QPoint)</slot>
-  <slot>check()</slot>
-  <slot>next()</slot>
-  <slot>previous()</slot>
-  <slot>current_wallet_changed(int)</slot>
-  <slot>remove_node()</slot>
- </slots>
-</ui>
diff --git a/res/ui/community_view.ui b/res/ui/community_view.ui
deleted file mode 100644
index 213060dc7294aa314a1b2b10781c79ac7865d500..0000000000000000000000000000000000000000
--- a/res/ui/community_view.ui
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>CommunityWidget</class>
- <widget class="QWidget" name="CommunityWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>624</width>
-    <height>429</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QFrame" name="frame">
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
-     </property>
-     <layout class="QHBoxLayout" name="horizontalLayout">
-      <item>
-       <widget class="QPushButton" name="button_home">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string/>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/home_icon</normaloff>:/icons/home_icon</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QLabel" name="label_currency">
-        <property name="text">
-         <string/>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="button_send_money">
-        <property name="text">
-         <string>Send money</string>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/payment_icon</normaloff>:/icons/payment_icon</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="button_certification">
-        <property name="text">
-         <string>Certification</string>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/certification_icon</normaloff>:/icons/certification_icon</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="button_membership">
-        <property name="text">
-         <string>Renew membership</string>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/renew_membership</normaloff>:/icons/renew_membership</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QToolButton" name="toolbutton_menu">
-        <property name="text">
-         <string/>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/menu_icon</normaloff>:/icons/menu_icon</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-        <property name="popupMode">
-         <enum>QToolButton::InstantPopup</enum>
-        </property>
-        <property name="autoRaise">
-         <bool>false</bool>
-        </property>
-        <property name="arrowType">
-         <enum>Qt::NoArrow</enum>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QTabWidget" name="tabs"/>
-   </item>
-  </layout>
- </widget>
- <resources>
-  <include location="../icons/icons.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/res/ui/contact.ui b/res/ui/contact.ui
deleted file mode 100644
index 40c059b6b52a119a714aea29dbbac55e43b890fc..0000000000000000000000000000000000000000
--- a/res/ui/contact.ui
+++ /dev/null
@@ -1,128 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ConfigureContactDialog</class>
- <widget class="QDialog" name="ConfigureContactDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>228</width>
-    <height>103</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Add a contact</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_3">
-     <item>
-      <widget class="QLabel" name="label">
-       <property name="text">
-        <string>Name</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QLineEdit" name="edit_name"/>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <item>
-      <widget class="QLabel" name="label_2">
-       <property name="text">
-        <string>Pubkey</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QLineEdit" name="edit_pubkey"/>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="button_box">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections>
-  <connection>
-   <sender>button_box</sender>
-   <signal>accepted()</signal>
-   <receiver>ConfigureContactDialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_box</sender>
-   <signal>rejected()</signal>
-   <receiver>ConfigureContactDialog</receiver>
-   <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>edit_pubkey</sender>
-   <signal>textChanged(QString)</signal>
-   <receiver>ConfigureContactDialog</receiver>
-   <slot>pubkey_edited()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>145</x>
-     <y>52</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>113</x>
-     <y>66</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>edit_name</sender>
-   <signal>textChanged(QString)</signal>
-   <receiver>ConfigureContactDialog</receiver>
-   <slot>name_edited()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>129</x>
-     <y>21</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>113</x>
-     <y>66</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <slot>name_edited()</slot>
-  <slot>pubkey_edited()</slot>
- </slots>
-</ui>
diff --git a/res/ui/create_wallet.ui b/res/ui/create_wallet.ui
deleted file mode 100644
index 4a611d7a5caf318d910868867b3f63d65e2c6efb..0000000000000000000000000000000000000000
--- a/res/ui/create_wallet.ui
+++ /dev/null
@@ -1,169 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>CreateWalletDialog</class>
- <widget class="QDialog" name="CreateWalletDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>400</width>
-    <height>234</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Create a new wallet</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <spacer name="verticalSpacer_2">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>1</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item>
-    <widget class="QStackedWidget" name="stacked_pages">
-     <property name="currentIndex">
-      <number>0</number>
-     </property>
-     <widget class="QWidget" name="page_wallet">
-      <layout class="QVBoxLayout" name="verticalLayout_3">
-       <item>
-        <layout class="QHBoxLayout" name="horizontalLayout">
-         <property name="topMargin">
-          <number>0</number>
-         </property>
-         <item>
-          <widget class="QLabel" name="label">
-           <property name="text">
-            <string>Wallet name :</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QLineEdit" name="edit_name"/>
-         </item>
-        </layout>
-       </item>
-      </layout>
-     </widget>
-    </widget>
-   </item>
-   <item>
-    <widget class="QLabel" name="label_error">
-     <property name="text">
-      <string/>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <spacer name="verticalSpacer_3">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>2</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2">
-     <item>
-      <widget class="QPushButton" name="button_previous">
-       <property name="text">
-        <string>Previous</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer name="horizontalSpacer">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>20</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-     <item>
-      <widget class="QPushButton" name="button_next">
-       <property name="text">
-        <string>Next</string>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections>
-  <connection>
-   <sender>edit_name</sender>
-   <signal>textChanged(QString)</signal>
-   <receiver>CreateWalletDialog</receiver>
-   <slot>check()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>238</x>
-     <y>91</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>116</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_next</sender>
-   <signal>clicked()</signal>
-   <receiver>CreateWalletDialog</receiver>
-   <slot>next()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>349</x>
-     <y>212</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>116</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_previous</sender>
-   <signal>clicked()</signal>
-   <receiver>CreateWalletDialog</receiver>
-   <slot>previous()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>49</x>
-     <y>212</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>116</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <slot>open_import_key()</slot>
-  <slot>open_generate_key()</slot>
-  <slot>check()</slot>
-  <slot>next()</slot>
-  <slot>previous()</slot>
- </slots>
-</ui>
diff --git a/res/ui/currency_tab.ui b/res/ui/currency_tab.ui
deleted file mode 100644
index 96fb8c5971e30da25a72bb274cce7d4ad3b27974..0000000000000000000000000000000000000000
--- a/res/ui/currency_tab.ui
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>CurrencyTabWidget</class>
- <widget class="QWidget" name="CurrencyTabWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>400</width>
-    <height>300</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <property name="windowIcon">
-   <iconset>
-    <normaloff>:/icons/noun_43022_cc.svg</normaloff>:/icons/noun_43022_cc.svg</iconset>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout_4">
-   <item>
-    <widget class="QFrame" name="actionsFrame">
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout">
-      <item>
-       <widget class="QTabWidget" name="tabs_account">
-        <property name="currentIndex">
-         <number>-1</number>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources>
-  <include location="../icons/icons.qrc"/>
- </resources>
- <connections/>
- <slots>
-  <slot>refresh_wallet_content(QModelIndex)</slot>
-  <slot>wallet_context_menu(QPoint)</slot>
-  <slot>dates_changed(QDateTime)</slot>
-  <slot>history_context_menu(QPoint)</slot>
- </slots>
-</ui>
diff --git a/res/ui/explorer_tab.ui b/res/ui/explorer_tab.ui
deleted file mode 100644
index 58aa9891db98f11f4f66fcf45a3be4913937e123..0000000000000000000000000000000000000000
--- a/res/ui/explorer_tab.ui
+++ /dev/null
@@ -1,95 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ExplorerTabWidget</class>
- <widget class="QWidget" name="ExplorerTabWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>522</width>
-    <height>442</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="SearchUserWidget" name="search_user_widget" native="true"/>
-   </item>
-   <item>
-    <widget class="ExplorerView" name="graphicsView">
-     <property name="viewportUpdateMode">
-      <enum>QGraphicsView::BoundingRectViewportUpdate</enum>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <property name="topMargin">
-      <number>6</number>
-     </property>
-     <item>
-      <spacer name="horizontalSpacer">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>20</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-     <item>
-      <widget class="QLabel" name="label">
-       <property name="text">
-        <string>Steps</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QSlider" name="steps_slider">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="tickPosition">
-        <enum>QSlider::TicksBothSides</enum>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QPushButton" name="button_go">
-       <property name="text">
-        <string>Go</string>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <customwidgets>
-  <customwidget>
-   <class>ExplorerView</class>
-   <extends>QGraphicsView</extends>
-   <header>sakia.gui.views</header>
-  </customwidget>
-  <customwidget>
-   <class>SearchUserWidget</class>
-   <extends>QWidget</extends>
-   <header>sakia.gui.widgets.search_user</header>
-   <container>1</container>
-  </customwidget>
- </customwidgets>
- <resources>
-  <include location="../icons/icons.qrc"/>
- </resources>
- <connections/>
- <slots>
-  <slot>reset()</slot>
-  <slot>search()</slot>
-  <slot>select_node()</slot>
- </slots>
-</ui>
diff --git a/res/ui/homescreen.ui b/res/ui/homescreen.ui
deleted file mode 100644
index f326a247071175967a5e8908579dd6ec4ee79212..0000000000000000000000000000000000000000
--- a/res/ui/homescreen.ui
+++ /dev/null
@@ -1,197 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>HomescreenWidget</class>
- <widget class="QWidget" name="HomescreenWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>648</width>
-    <height>472</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QFrame" name="frame_connected">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
-     </property>
-     <layout class="QHBoxLayout" name="horizontalLayout">
-      <property name="sizeConstraint">
-       <enum>QLayout::SetMaximumSize</enum>
-      </property>
-      <item>
-       <widget class="QLabel" name="label_connected">
-        <property name="styleSheet">
-         <string notr="true"> font-size:12pt; font-weight:600;</string>
-        </property>
-        <property name="text">
-         <string>Connected as</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="button_add_community">
-        <property name="text">
-         <string>Add a community</string>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/add_community</normaloff>:/icons/add_community</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QPushButton" name="button_disconnect">
-        <property name="text">
-         <string>Disconnect</string>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/logout</normaloff>:/icons/logout</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <spacer name="horizontalSpacer">
-        <property name="orientation">
-         <enum>Qt::Horizontal</enum>
-        </property>
-        <property name="sizeHint" stdset="0">
-         <size>
-          <width>40</width>
-          <height>20</height>
-         </size>
-        </property>
-       </spacer>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QFrame" name="frame_disconnected">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
-     </property>
-     <layout class="QHBoxLayout" name="horizontalLayout_2">
-      <item>
-       <widget class="QLabel" name="label_disconnected">
-        <property name="text">
-         <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt; font-weight:600;&quot;&gt;Not Connected&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QToolButton" name="toolbutton_connect">
-        <property name="text">
-         <string>Connect</string>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/connect_icon</normaloff>:/icons/connect_icon</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-        <property name="popupMode">
-         <enum>QToolButton::MenuButtonPopup</enum>
-        </property>
-        <property name="toolButtonStyle">
-         <enum>Qt::ToolButtonTextBesideIcon</enum>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QToolButton" name="toolbutton_new_account">
-        <property name="text">
-         <string>New account</string>
-        </property>
-        <property name="icon">
-         <iconset resource="../icons/icons.qrc">
-          <normaloff>:/icons/add_account_icon</normaloff>:/icons/add_account_icon</iconset>
-        </property>
-        <property name="iconSize">
-         <size>
-          <width>32</width>
-          <height>32</height>
-         </size>
-        </property>
-        <property name="popupMode">
-         <enum>QToolButton::MenuButtonPopup</enum>
-        </property>
-        <property name="toolButtonStyle">
-         <enum>Qt::ToolButtonTextBesideIcon</enum>
-        </property>
-        <property name="autoRaise">
-         <bool>false</bool>
-        </property>
-        <property name="arrowType">
-         <enum>Qt::NoArrow</enum>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <spacer name="horizontalSpacer_5">
-        <property name="orientation">
-         <enum>Qt::Horizontal</enum>
-        </property>
-        <property name="sizeHint" stdset="0">
-         <size>
-          <width>40</width>
-          <height>20</height>
-         </size>
-        </property>
-       </spacer>
-      </item>
-     </layout>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources>
-  <include location="../icons/icons.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/res/ui/import_account.ui b/res/ui/import_account.ui
deleted file mode 100644
index 6ca68c0155e14ae4587d38bc51a7a9af3e761eb1..0000000000000000000000000000000000000000
--- a/res/ui/import_account.ui
+++ /dev/null
@@ -1,135 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ImportAccountDialog</class>
- <widget class="QDialog" name="ImportAccountDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>400</width>
-    <height>124</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Import an account</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <item>
-      <widget class="QLineEdit" name="edit_file"/>
-     </item>
-     <item>
-      <widget class="QPushButton" name="button_import">
-       <property name="text">
-        <string>Import a file</string>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2">
-     <item>
-      <widget class="QLabel" name="label">
-       <property name="text">
-        <string>Name of the account :</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QLineEdit" name="edit_name"/>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <widget class="QLabel" name="label_errors">
-     <property name="text">
-      <string/>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="button_box">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections>
-  <connection>
-   <sender>button_box</sender>
-   <signal>accepted()</signal>
-   <receiver>ImportAccountDialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_box</sender>
-   <signal>rejected()</signal>
-   <receiver>ImportAccountDialog</receiver>
-   <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>button_import</sender>
-   <signal>clicked()</signal>
-   <receiver>ImportAccountDialog</receiver>
-   <slot>import_account()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>349</x>
-     <y>21</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>51</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>edit_name</sender>
-   <signal>textEdited(QString)</signal>
-   <receiver>ImportAccountDialog</receiver>
-   <slot>name_changed()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>259</x>
-     <y>52</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>51</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <slot>import_account()</slot>
-  <slot>name_changed()</slot>
- </slots>
-</ui>
diff --git a/res/ui/node_manager.ui b/res/ui/node_manager.ui
deleted file mode 100644
index ef4041a7ec9580838728d1d3066eb14662b72d1f..0000000000000000000000000000000000000000
--- a/res/ui/node_manager.ui
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>NodeManager</class>
- <widget class="QDialog" name="NodeManager">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>1024</width>
-    <height>600</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Node manager</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QWidget" name="web_view" native="true"/>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/res/ui/transfer.ui b/res/ui/transfer.ui
deleted file mode 100644
index 71e077897ca067494ae853a3f50c3981e09a86ac..0000000000000000000000000000000000000000
--- a/res/ui/transfer.ui
+++ /dev/null
@@ -1,336 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>TransferMoneyDialog</class>
- <widget class="QDialog" name="TransferMoneyDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>496</width>
-    <height>485</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Transfer money</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <item>
-    <widget class="QGroupBox" name="groupBox_2">
-     <property name="title">
-      <string>Community</string>
-     </property>
-     <layout class="QHBoxLayout" name="horizontalLayout_4">
-      <item>
-       <widget class="QComboBox" name="combo_community"/>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="groupBox">
-     <property name="title">
-      <string>Transfer money to</string>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout_2">
-      <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>
-         <widget class="QRadioButton" name="radio_pubkey">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>&amp;Recipient public key</string>
-          </property>
-          <property name="checked">
-           <bool>false</bool>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <spacer name="horizontalSpacer_2">
-          <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="QLineEdit" name="edit_pubkey">
-          <property name="enabled">
-           <bool>false</bool>
-          </property>
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="inputMask">
-           <string/>
-          </property>
-          <property name="text">
-           <string/>
-          </property>
-          <property name="placeholderText">
-           <string>Key</string>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_5">
-        <property name="topMargin">
-         <number>6</number>
-        </property>
-        <item>
-         <widget class="QRadioButton" name="radio_search">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="text">
-           <string>Search &amp;user</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <spacer name="horizontalSpacer">
-          <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="SearchUserWidget" name="search_user" native="true">
-          <property name="enabled">
-           <bool>false</bool>
-          </property>
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QFrame" name="frame">
-     <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
-     </property>
-     <property name="frameShadow">
-      <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">
-         <string>Available money : </string>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_3">
-        <item>
-         <widget class="QLabel" name="label_3">
-          <property name="text">
-           <string>Amount</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QDoubleSpinBox" name="spinbox_relative">
-          <property name="suffix">
-           <string> UD</string>
-          </property>
-          <property name="decimals">
-           <number>6</number>
-          </property>
-          <property name="maximum">
-           <double>99999999999999991611392.000000000000000</double>
-          </property>
-          <property name="singleStep">
-           <double>0.010000000000000</double>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QDoubleSpinBox" name="spinbox_amount">
-          <property name="wrapping">
-           <bool>false</bool>
-          </property>
-          <property name="frame">
-           <bool>true</bool>
-          </property>
-          <property name="readOnly">
-           <bool>false</bool>
-          </property>
-          <property name="buttonSymbols">
-           <enum>QAbstractSpinBox::NoButtons</enum>
-          </property>
-          <property name="showGroupSeparator" stdset="0">
-           <bool>false</bool>
-          </property>
-          <property name="decimals">
-           <number>0</number>
-          </property>
-          <property name="value">
-           <double>0.000000000000000</double>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="groupBox_3">
-     <property name="title">
-      <string>Transaction message</string>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout_3">
-      <item>
-       <widget class="QLineEdit" name="edit_message">
-        <property name="inputMask">
-         <string/>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="button_box">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <customwidgets>
-  <customwidget>
-   <class>SearchUserWidget</class>
-   <extends>QWidget</extends>
-   <header>sakia.gui.widgets.search_user</header>
-   <container>1</container>
-  </customwidget>
- </customwidgets>
- <resources/>
- <connections/>
- <slots>
-  <slot>open_manage_wallet_coins()</slot>
-  <slot>change_displayed_wallet(int)</slot>
-  <slot>transfer_mode_changed(bool)</slot>
-  <slot>recipient_mode_changed(bool)</slot>
-  <slot>change_current_community(int)</slot>
-  <slot>amount_changed()</slot>
-  <slot>relative_amount_changed()</slot>
- </slots>
-</ui>
diff --git a/res/ui/wallets_tab.ui b/res/ui/wallets_tab.ui
deleted file mode 100644
index 16b4dd1c96d212b9773b06a202151676c417af27..0000000000000000000000000000000000000000
--- a/res/ui/wallets_tab.ui
+++ /dev/null
@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>WalletsTab</class>
- <widget class="QWidget" name="WalletsTab">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>924</width>
-    <height>920</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Form</string>
-  </property>
-  <property name="styleSheet">
-   <string notr="true">QGroupBox {
-    border: 1px solid gray;
-    border-radius: 9px;
-    margin-top: 0.5em;
-}
-
-QGroupBox::title {
-    subcontrol-origin: margin;
-    left: 10px;
-    padding: 0 3px 0 3px;
-	font-weight: bold;
-}</string>
-  </property>
-  <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="0">
-    <widget class="QGroupBox" name="groupBox_2">
-     <property name="title">
-      <string>Balance</string>
-     </property>
-     <layout class="QVBoxLayout" name="verticalLayout_3">
-      <item>
-       <widget class="QLabel" name="label_balance">
-        <property name="font">
-         <font>
-          <pointsize>22</pointsize>
-          <weight>75</weight>
-          <bold>true</bold>
-         </font>
-        </property>
-        <property name="text">
-         <string>label_balance</string>
-        </property>
-        <property name="alignment">
-         <set>Qt::AlignHCenter|Qt::AlignTop</set>
-        </property>
-       </widget>
-      </item>
-      <item>
-       <widget class="QLabel" name="label_balance_range">
-        <property name="text">
-         <string>label_balance_range</string>
-        </property>
-        <property name="alignment">
-         <set>Qt::AlignCenter</set>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item row="1" column="0">
-    <widget class="QTableView" name="table_wallets">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
-       <horstretch>0</horstretch>
-       <verstretch>1</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="contextMenuPolicy">
-      <enum>Qt::CustomContextMenu</enum>
-     </property>
-     <property name="alternatingRowColors">
-      <bool>true</bool>
-     </property>
-     <property name="selectionMode">
-      <enum>QAbstractItemView::SingleSelection</enum>
-     </property>
-     <property name="selectionBehavior">
-      <enum>QAbstractItemView::SelectRows</enum>
-     </property>
-     <property name="sortingEnabled">
-      <bool>true</bool>
-     </property>
-     <attribute name="horizontalHeaderStretchLastSection">
-      <bool>true</bool>
-     </attribute>
-     <attribute name="verticalHeaderVisible">
-      <bool>false</bool>
-     </attribute>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources>
-  <include location="../icons/icons.qrc"/>
- </resources>
- <connections>
-  <connection>
-   <sender>table_wallets</sender>
-   <signal>customContextMenuRequested(QPoint)</signal>
-   <receiver>WalletsTab</receiver>
-   <slot>wallet_context_menu(QPoint)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>199</x>
-     <y>346</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>225</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <slot>wallet_context_menu(QPoint)</slot>
-  <slot>wallet_changed()</slot>
-  <slot>send_membership_demand()</slot>
-  <slot>send_membership_leaving()</slot>
-  <slot>revoke_uid()</slot>
-  <slot>publish_uid()</slot>
- </slots>
-</ui>
diff --git a/src/sakia/__init__.py b/src/sakia/__init__.py
index 8233ea0a9c3f1c66888ca87348b0e6f469bdfb62..3d6823a6723c6d5bd2718749465240c45419f0f1 100644
--- a/src/sakia/__init__.py
+++ b/src/sakia/__init__.py
@@ -1,2 +1,2 @@
-__version_info__ = ('0', '20', '13')
+__version_info__ = ('0', '30', '0')
 __version__ = '.'.join(__version_info__)
diff --git a/src/sakia/app.py b/src/sakia/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..a88c25a09e8fc8f1758aa5b3e5d4aaada1bd96cd
--- /dev/null
+++ b/src/sakia/app.py
@@ -0,0 +1,231 @@
+import attr
+import datetime
+import logging
+
+import aiohttp
+from PyQt5.QtCore import QObject, pyqtSignal, QTranslator, QCoreApplication, QLocale
+from . import __version__
+from .options import SakiaOptions
+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, Connection, Identity, Dividend
+from sakia.data.processors import BlockchainProcessor, NodesProcessor, IdentitiesProcessor, \
+    CertificationsProcessor, SourcesProcessor, TransactionsProcessor, ConnectionsProcessor, DividendsProcessor
+from sakia.data.files import AppDataFile, UserParametersFile
+from sakia.decorators import asyncify
+from sakia.money import *
+
+
+@attr.s()
+class Application(QObject):
+
+    """
+    Managing core application datas :
+    Accounts list and general configuration
+    Saving and loading the application state
+
+
+    :param QCoreApplication qapp: Qt Application
+    :param quamash.QEventLoop loop: quamash.QEventLoop instance
+    :param sakia.options.SakiaOptions options: the options
+    :param sakia.data.entities.AppData app_data: the application data
+    :param sakia.data.entities.UserParameters parameters: the application current user parameters
+    :param sakia.data.repositories.SakiaDatabase db: The database
+    :param dict network_services: All network services for current currency
+    :param dict blockchain_services: All blockchain services for current currency
+    :param dict identities_services: All identities services for current currency
+    :param dict sources_services: All sources services for current currency
+    :param dict transactions_services: All transactions services for current currency
+    :param sakia.services.DocumentsService documents_service: A service to broadcast documents
+    """
+
+    new_dividend = pyqtSignal(Dividend)
+    new_transfer = pyqtSignal(Transaction)
+    transaction_state_changed = pyqtSignal(Transaction)
+    identity_changed = pyqtSignal(Identity)
+    new_connection = pyqtSignal(Connection)
+    referential_changed = pyqtSignal()
+    sources_refreshed = pyqtSignal()
+
+    qapp = attr.ib()
+    loop = attr.ib()
+    options = attr.ib()
+    app_data = attr.ib()
+    parameters = attr.ib()
+    db = attr.ib()
+    network_services = attr.ib(default=attr.Factory(dict))
+    blockchain_services = attr.ib(default=attr.Factory(dict))
+    identities_services = attr.ib(default=attr.Factory(dict))
+    sources_services = attr.ib(default=attr.Factory(dict))
+    transactions_services = attr.ib(default=attr.Factory(dict))
+    documents_service = attr.ib(default=None)
+    current_ref = attr.ib(default=Relative)
+    _logger = attr.ib(default=attr.Factory(lambda:logging.getLogger('sakia')))
+    available_version = attr.ib(init=False)
+    _translator = attr.ib(init=False)
+
+    def __attrs_post_init__(self):
+        super().__init__()
+        self._translator = QTranslator(self.qapp)
+        self.available_version = True, __version__, ""
+
+    @classmethod
+    def startup(cls, argv, qapp, loop):
+        options = SakiaOptions.from_arguments(argv)
+        app_data = AppDataFile.in_config_path(options.config_path).load_or_init()
+        app = cls(qapp, loop, options, app_data, None, None)
+        #app.set_proxy()
+        app.get_last_version()
+        app.load_profile(app_data.default)
+        app.start_coroutines()
+        app.documents_service = DocumentsService.instanciate(app)
+        app.switch_language()
+        return app
+
+    def load_profile(self, profile_name):
+        """
+        Initialize databases depending on profile loaded
+        :param profile_name:
+        :return:
+        """
+        self.parameters = UserParametersFile.in_config_path(self.options.config_path, profile_name).load_or_init()
+        self.db = SakiaDatabase.load_or_init(self.options, profile_name)
+
+        self.instanciate_services()
+
+    def instanciate_services(self):
+        nodes_processor = NodesProcessor(self.db.nodes_repo)
+        bma_connector = BmaConnector(nodes_processor, self.parameters)
+        connections_processor = ConnectionsProcessor(self.db.connections_repo)
+        identities_processor = IdentitiesProcessor(self.db.identities_repo, self.db.blockchains_repo, bma_connector)
+        certs_processor = CertificationsProcessor(self.db.certifications_repo, self.db.identities_repo, bma_connector)
+        blockchain_processor = BlockchainProcessor.instanciate(self)
+        sources_processor = SourcesProcessor.instanciate(self)
+        transactions_processor = TransactionsProcessor.instanciate(self)
+        dividends_processor = DividendsProcessor.instanciate(self)
+
+        self.blockchain_services = {}
+        self.network_services = {}
+        self.identities_services = {}
+        self.sources_services = {}
+        self.transactions_services = {}
+        self.documents_service = DocumentsService.instanciate(self)
+
+        for currency in self.db.connections_repo.get_currencies():
+            if currency not in self.identities_services:
+                self.identities_services[currency] = IdentitiesService(currency, connections_processor,
+                                                                   identities_processor,
+                                                                   certs_processor, blockchain_processor,
+                                                                   bma_connector)
+
+            if currency not in self.transactions_services:
+                self.transactions_services[currency] = TransactionsService(currency, transactions_processor,
+                                                                           dividends_processor,
+                                                                           identities_processor, connections_processor,
+                                                                           bma_connector)
+
+            if currency not in self.sources_services:
+                self.sources_services[currency] = SourcesServices(currency, sources_processor,
+                                                                  connections_processor, bma_connector)
+
+            if currency not in self.blockchain_services:
+                self.blockchain_services[currency] = BlockchainService(self, currency, blockchain_processor, bma_connector,
+                                                                       self.identities_services[currency],
+                                                                       self.transactions_services[currency],
+                                                                       self.sources_services[currency])
+            if currency not in self.network_services:
+                self.network_services[currency] = NetworkService.load(self, currency, nodes_processor,
+                                                                      self.blockchain_services[currency],
+                                                                      self.identities_services[currency])
+
+    async def remove_connection(self, connection):
+        await self.stop_current_profile()
+        connections_processor = ConnectionsProcessor.instanciate(self)
+        connections_processor.remove_connections(connection)
+
+        if not connections_processor.connections_to(connection.currency):
+            BlockchainProcessor.instanciate(self).remove_blockchain(connection.currency)
+
+        IdentitiesProcessor.instanciate(self).cleanup_connection(connection)
+
+        CertificationsProcessor.instanciate(self).cleanup_connection(connection, connections_processor.pubkeys())
+
+        SourcesProcessor.instanciate(self).drop_all_of(currency=connection.currency, pubkey=connection.pubkey)
+
+        DividendsProcessor.instanciate(self).cleanup_connection(connection)
+
+        TransactionsProcessor.instanciate(self).cleanup_connection(connection, connections_processor.pubkeys())
+
+        if not connections_processor.connections():
+            NodesProcessor.instanciate(self).drop_all()
+
+        self.db.commit()
+        self.start_coroutines()
+
+    def switch_language(self):
+        logging.debug("Loading translations")
+        locale = self.parameters.lang
+        QLocale.setDefault(QLocale(locale))
+        QCoreApplication.removeTranslator(self._translator)
+        self._translator = QTranslator(self.qapp)
+        if locale == "en_GB":
+            QCoreApplication.installTranslator(self._translator)
+        elif self._translator.load(":/i18n/{0}".format(locale)):
+            if QCoreApplication.installTranslator(self._translator):
+                logging.debug("Loaded i18n/{0}".format(locale))
+            else:
+                logging.debug("Couldn't load translation")
+
+    def start_coroutines(self):
+        for currency in self.db.connections_repo.get_currencies():
+            self.network_services[currency].start_coroutines()
+
+    async def stop_current_profile(self, closing=False):
+        """
+        Save the account to the cache
+        and stop the coroutines
+        """
+        for currency in self.db.connections_repo.get_currencies():
+            await self.network_services[currency].stop_coroutines(closing)
+
+    @asyncify
+    async def get_last_version(self):
+        try:
+            with aiohttp.ClientSession() as session:
+                with aiohttp.Timeout(15):
+                    response = await session.get("https://api.github.com/repos/duniter/sakia/releases",
+                                                 proxy=self.parameters.proxy())
+                    if response.status == 200:
+                        releases = await response.json()
+                        latest = None
+                        for r in releases:
+                            if not latest:
+                                latest = r
+                            else:
+                                latest_date = datetime.datetime.strptime(latest['published_at'], "%Y-%m-%dT%H:%M:%SZ")
+                                date = datetime.datetime.strptime(r['published_at'], "%Y-%m-%dT%H:%M:%SZ")
+                                if latest_date < date:
+                                    latest = r
+                        latest_version = latest["tag_name"]
+                        version = (__version__ == latest_version,
+                                   latest_version,
+                                   latest["html_url"])
+                        logging.debug("Found version : {0}".format(latest_version))
+                        logging.debug("Current version : {0}".format(__version__))
+                        self.available_version = version
+        except (aiohttp.errors.ClientError, aiohttp.errors.TimeoutError) as e:
+            self._logger.debug("Could not connect to github : {0}".format(str(e)))
+
+    def save_parameters(self, parameters):
+        self.parameters = UserParametersFile\
+            .in_config_path(self.options.config_path, parameters.profile_name)\
+            .save(parameters)
+
+    def change_referential(self, index):
+        self.current_ref = Referentials[index]
+        self.referential_changed.emit()
+
+    def connection_exists(self):
+        return len(ConnectionsProcessor.instanciate(self).connections()) > 0
diff --git a/src/sakia/constants.py b/src/sakia/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ec1e4f04aa9ec3b15ab952d01a8e5e49504b2ca
--- /dev/null
+++ b/src/sakia/constants.py
@@ -0,0 +1 @@
+MAX_CONFIRMATIONS = 6
diff --git a/src/sakia/core/__init__.py b/src/sakia/core/__init__.py
deleted file mode 100644
index 6de4a62c9259773dd9a301d9f7e5b616c7328489..0000000000000000000000000000000000000000
--- a/src/sakia/core/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .community import Community
-from .wallet import Wallet
-from .account import Account
-from .app import Application
\ No newline at end of file
diff --git a/src/sakia/core/account.py b/src/sakia/core/account.py
deleted file mode 100644
index 643c92b7fcc69e1086e0c8c5f3b9e44b12ec37ad..0000000000000000000000000000000000000000
--- a/src/sakia/core/account.py
+++ /dev/null
@@ -1,647 +0,0 @@
-"""
-Created on 1 févr. 2014
-
-@author: inso
-"""
-
-from duniterpy.documents import Membership, SelfCertification, Certification, Revocation, BlockUID, Block
-from duniterpy.key import SigningKey, ScryptParams
-from duniterpy.api import bma
-from duniterpy.api.bma import PROTOCOL_VERSION
-from duniterpy.api import errors
-
-import logging
-import asyncio
-from pkg_resources import parse_version
-from aiohttp.errors import ClientError
-from PyQt5.QtCore import QObject, pyqtSignal
-
-from . import money
-from .wallet import Wallet
-from .community import Community
-from .registry import LocalState
-from ..tools.exceptions import ContactAlreadyExists, LookupFailureError
-from .. import __version__
-
-
-class Account(QObject):
-    """
-    An account is specific to a key.
-    Each account has only one key, and a key can
-    be locally referenced by only one account.
-    """
-    loading_progressed = pyqtSignal(Community, int, int)
-    loading_finished = pyqtSignal(Community, list)
-    wallets_changed = pyqtSignal()
-    certification_accepted = pyqtSignal()
-    contacts_changed = pyqtSignal()
-
-    def __init__(self, salt, pubkey, scrypt_params, name, communities, wallets, contacts, identities_registry):
-        """
-        Create an account
-
-        :param str salt: The root key salt
-        :param str pubkey: Known account pubkey. Used to check that password \
-         is OK by comparing (salt, given_passwd) = (pubkey, privkey) \
-         with known pubkey
-        :param duniterpy.key.ScryptParams scrypt_params: the scrypt params of the key
-        :param str name: The account name, same as network identity uid
-        :param list of sakia.core.Community communities: Community objects referenced by this account
-        :param list of sakia.core.Wallet wallets: Wallet objects owned by this account
-        :param list of dict contacts: Contacts of this account
-        :param sakia.core.registry.IdentitiesRegistry: The identities registry intance
-
-        .. warnings:: The class methods create and load should be used to create an account
-        """
-        super().__init__()
-        self.salt = salt
-        self.pubkey = pubkey
-        self.scrypt_params = scrypt_params
-        self.name = name
-        self.communities = communities
-        self.wallets = wallets
-        self.contacts = contacts
-        self._identities_registry = identities_registry
-        self._current_ref = 0
-
-        self.notifications = {'membership_expire_soon':
-                                  [
-                                      self.tr("Warning : Your membership is expiring soon."),
-                                      0
-                                   ],
-                            'warning_certifications':
-                                    [
-                                        self.tr("Warning : Your could miss certifications soon."),
-                                        0
-                                    ],
-                            'warning_revokation':
-                                    [
-                                        self.tr("Warning : If you don't renew soon, your identity will be considered revoked."),
-                                        0
-                                    ],
-                            'warning_certifying_first_time': True,
-                            }
-
-    @classmethod
-    def create(cls, name, identities_registry):
-        """
-        Factory method to create an empty account object
-        This new account doesn't have any key and it should be given
-        one later
-        It doesn't have any community nor does it have wallets.
-        Communities could be added later, wallets will be managed
-        by its wallet pool size.
-
-        :param str name: The account name, same as network identity uid
-        :return: A new empty account object
-        """
-        account = cls(None, None, None, name, [], [], [], identities_registry)
-        return account
-
-    @classmethod
-    def load(cls, json_data, identities_registry):
-        """
-        Factory method to create an Account object from its json view.
-        :rtype : sakia.core.account.Account
-        :param dict json_data: The account view as a json dict
-        :param PyQt5.QtNetwork import QNetworkManager: network_manager
-        :param sakia.core.registry.self._identities_registry: identities_registry
-        :return: A new account object created from the json datas
-        """
-        salt = json_data['salt']
-        pubkey = json_data['pubkey']
-        if 'file_version' in json_data:
-            file_version = parse_version(json_data['file_version'])
-        else:
-            file_version = parse_version('0.11.5')
-
-        if file_version <= parse_version('0.20.11'):
-            scrypt_params = ScryptParams(4096, 16, 1)
-        else:
-            scrypt_params = ScryptParams(json_data['scrypt_params']['N'],
-                                         json_data['scrypt_params']['r'],
-                                         json_data['scrypt_params']['p'])
-
-        name = json_data['name']
-        contacts = []
-
-        for contact_data in json_data['contacts']:
-            contacts.append(contact_data)
-
-        wallets = []
-        for data in json_data['wallets']:
-            wallets.append(Wallet.load(data, identities_registry))
-
-        communities = []
-        for data in json_data['communities']:
-            community = Community.load(data, file_version)
-            communities.append(community)
-
-        account = cls(salt, pubkey, scrypt_params, name, communities, wallets,
-                      contacts, identities_registry)
-        return account
-
-    def __eq__(self, other):
-        """
-        :return: True if account.pubkey == other.pubkey
-        """
-        if other is not None:
-            return other.pubkey == self.pubkey
-        else:
-            return False
-
-    def check_password(self, password):
-        """
-        Method to verify the key password validity
-
-        :param str password: The key password
-        :return: True if the generated pubkey is the same as the account
-        .. warnings:: Generates a new temporary SigningKey
-        """
-        key = SigningKey(self.salt, password, self.scrypt_params)
-        return (key.pubkey == self.pubkey)
-
-    def add_contact(self, new_contact):
-        same_contact = [contact for contact in self.contacts
-                        if new_contact['pubkey'] == contact['pubkey']]
-
-        if len(same_contact) > 0:
-            raise ContactAlreadyExists(new_contact['name'], same_contact[0]['name'])
-        self.contacts.append(new_contact)
-        self.contacts_changed.emit()
-
-    def edit_contact(self, index, new_data):
-        self.contacts[index] = new_data
-        self.contacts_changed.emit()
-
-    def remove_contact(self, contact):
-        self.contacts.remove(contact)
-        self.contacts_changed.emit()
-
-    def add_community(self, community):
-        """
-        Add a community to the account
-
-        :param community: A community object to add
-        """
-        self.communities.append(community)
-        return community
-
-    def refresh_transactions(self, app, community):
-        """
-        Refresh the local account cache
-        This needs n_wallets * n_communities cache refreshing to end
-
-        .. note:: emit the Account pyqtSignal loading_progressed during refresh
-        """
-        logging.debug("Start refresh transactions")
-        loaded_wallets = 0
-        received_list = []
-        values = {}
-        maximums = {}
-
-        def progressing(value, maximum, hash):
-            #logging.debug("Loading = {0} : {1} : {2}".format(value, maximum, loaded_wallets))
-            values[hash] = value
-            maximums[hash] = maximum
-            account_value = sum(values.values())
-            account_max = sum(maximums.values())
-            self.loading_progressed.emit(community, account_value, account_max)
-
-        def wallet_finished(received):
-            logging.debug("Finished loading wallet")
-            nonlocal loaded_wallets
-            loaded_wallets += 1
-            if loaded_wallets == len(self.wallets):
-                logging.debug("All wallets loaded")
-                self._refreshing = False
-                self.loading_finished.emit(community, received_list)
-                for w in self.wallets:
-                    w.refresh_progressed.disconnect(progressing)
-                    w.refresh_finished.disconnect(wallet_finished)
-
-        for w in self.wallets:
-            w.refresh_progressed.connect(progressing)
-            w.refresh_finished.connect(wallet_finished)
-            w.init_cache(app, community)
-            w.refresh_transactions(community, received_list)
-
-    def rollback_transaction(self, app, community):
-        """
-        Refresh the local account cache
-        This needs n_wallets * n_communities cache refreshing to end
-
-        .. note:: emit the Account pyqtSignal loading_progressed during refresh
-        """
-        logging.debug("Start refresh transactions")
-        loaded_wallets = 0
-        received_list = []
-        values = {}
-        maximums = {}
-
-        def progressing(value, maximum, hash):
-            #logging.debug("Loading = {0} : {1} : {2}".format(value, maximum, loaded_wallets))
-            values[hash] = value
-            maximums[hash] = maximum
-            account_value = sum(values.values())
-            account_max = sum(maximums.values())
-            self.loading_progressed.emit(community, account_value, account_max)
-
-        def wallet_finished(received):
-            logging.debug("Finished loading wallet")
-            nonlocal loaded_wallets
-            loaded_wallets += 1
-            if loaded_wallets == len(self.wallets):
-                logging.debug("All wallets loaded")
-                self._refreshing = False
-                self.loading_finished.emit(community, received_list)
-                for w in self.wallets:
-                    w.refresh_progressed.disconnect(progressing)
-                    w.refresh_finished.disconnect(wallet_finished)
-
-        for w in self.wallets:
-            w.refresh_progressed.connect(progressing)
-            w.refresh_finished.connect(wallet_finished)
-            w.init_cache(app, community)
-            w.rollback_transactions(community, received_list)
-
-    def set_display_referential(self, index):
-        self._current_ref = index
-
-    def set_scrypt_infos(self, salt, password, scrypt_params):
-        """
-        Change the size of the wallet pool
-        :param int size: The new size of the wallet pool
-        :param str password: The password of the account, same for all wallets
-        :param duniterpy.key.ScryptParams scrypt_params: The scrypt parameters
-        """
-        self.salt = salt
-        self.scrypt_params = scrypt_params
-        self.pubkey = SigningKey(self.salt, password, scrypt_params).pubkey
-        wallet = Wallet.create(0, self.salt, password, scrypt_params,
-                               "Wallet", self._identities_registry)
-        self.wallets.append(wallet)
-
-    async def identity(self, community):
-        """
-        Get the account identity in the specified community
-        :param sakia.core.community.Community community: The community where to look after the identity
-        :return: The account identity in the community
-        :rtype: sakia.core.registry.Identity
-        """
-        identity = await self._identities_registry.future_find(self.pubkey, community)
-        if identity.local_state == LocalState.NOT_FOUND:
-            identity.uid = self.name
-        return identity
-
-    @property
-    def current_ref(self):
-        return money.Referentials[self._current_ref]
-
-    def transfers(self, community):
-        """
-        Get all transfers done in a community by all the wallets
-        owned by this account
-
-        :param community: The target community of this request
-        :return: All account wallets transfers
-        """
-        sent = []
-        for w in self.wallets:
-            sent.extend(w.transfers(community))
-        return sent
-
-    def dividends(self, community):
-        """
-        Get all dividends received in this community
-        by the first wallet of this account
-
-        :param community: The target community
-        :return: All account dividends
-        """
-        return self.wallets[0].dividends(community)
-
-    async def future_amount(self, community):
-        """
-        Get amount of money owned in a community by all the wallets
-        owned by this account
-
-        :param community: The target community of this request
-        :return: The value of all wallets values accumulated
-        """
-        value = 0
-        for w in self.wallets:
-            val = await w.future_value(community)
-            value += val
-        return value
-
-    async def amount(self, community):
-        """
-        Get amount of money owned in a community by all the wallets
-        owned by this account
-
-        :param community: The target community of this request
-        :return: The value of all wallets values accumulated
-        """
-        value = 0
-        for w in self.wallets:
-            val = await w.value(community)
-            value += val
-        return value
-
-    async def check_registered(self, community):
-        """
-        Checks for the pubkey and the uid of an account in a community
-        :param sakia.core.Community community: The community we check for registration
-        :return: (True if found, local value, network value)
-        """
-        def _parse_uid_certifiers(data):
-            return self.name == data['uid'], self.name, data['uid']
-
-        def _parse_uid_lookup(data):
-            timestamp = BlockUID.empty()
-            found_uid = ""
-            for result in data['results']:
-                if result["pubkey"] == self.pubkey:
-                    uids = result['uids']
-                    for uid_data in uids:
-                        if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
-                            timestamp = uid_data["meta"]["timestamp"]
-                            found_uid = uid_data["uid"]
-            return self.name == found_uid, self.name, found_uid
-
-        def _parse_pubkey_certifiers(data):
-            return self.pubkey == data['pubkey'], self.pubkey, data['pubkey']
-
-        def _parse_pubkey_lookup(data):
-            timestamp = BlockUID.empty()
-            found_uid = ""
-            found_result = ["", ""]
-            for result in data['results']:
-                uids = result['uids']
-                for uid_data in uids:
-                    if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
-                        timestamp = uid_data["meta"]["timestamp"]
-                        found_uid = uid_data["uid"]
-                if found_uid == self.name:
-                    found_result = result['pubkey'], found_uid
-            if found_result[1] == self.name:
-                return self.pubkey == found_result[0], self.pubkey, found_result[0]
-            else:
-                return False, self.pubkey, None
-
-        async def execute_requests(parsers, search):
-            tries = 0
-            request = bma.wot.CertifiersOf
-            nonlocal registered
-            #TODO: The algorithm is quite dirty
-            #Multiplying the tries without any reason...
-            while tries < 3 and not registered[0] and not registered[2]:
-                try:
-                    data = await community.bma_access.simple_request(request,
-                                                                          req_args={'search': search})
-                    if data:
-                        registered = parsers[request](data)
-                    tries += 1
-                except errors.DuniterError as e:
-                    if e.ucode in (errors.NO_MEMBER_MATCHING_PUB_OR_UID,
-                                   e.ucode == errors.NO_MATCHING_IDENTITY):
-                        if request == bma.wot.CertifiersOf:
-                            request = bma.wot.Lookup
-                            tries = 0
-                        else:
-                            tries += 1
-                    else:
-                        tries += 1
-                except asyncio.TimeoutError:
-                    tries += 1
-                except ClientError:
-                    tries += 1
-
-        registered = (False, self.name, None)
-        # We execute search based on pubkey
-        # And look for account UID
-        uid_parsers = {
-                    bma.wot.CertifiersOf: _parse_uid_certifiers,
-                    bma.wot.Lookup: _parse_uid_lookup
-                   }
-        await execute_requests(uid_parsers, self.pubkey)
-
-        # If the uid wasn't found when looking for the pubkey
-        # We look for the uid and check for the pubkey
-        if not registered[0] and not registered[2]:
-            pubkey_parsers = {
-                        bma.wot.CertifiersOf: _parse_pubkey_certifiers,
-                        bma.wot.Lookup: _parse_pubkey_lookup
-                       }
-            await execute_requests(pubkey_parsers, self.name)
-
-        return registered
-
-    async def send_selfcert(self, password, community):
-        """
-        Send our self certification to a target community
-
-        :param str password: The account SigningKey password
-        :param community: The community target of the self certification
-        """
-        try:
-            block_data = await community.bma_access.simple_request(bma.blockchain.Current)
-            signed_raw = "{0}{1}\n".format(block_data['raw'], block_data['signature'])
-            block_uid = Block.from_signed_raw(signed_raw).blockUID
-        except errors.DuniterError as e:
-            if e.ucode == errors.NO_CURRENT_BLOCK:
-                block_uid = BlockUID.empty()
-            else:
-                raise
-        selfcert = SelfCertification(PROTOCOL_VERSION,
-                                     community.currency,
-                                     self.pubkey,
-                                     self.name,
-                                     block_uid,
-                                     None)
-        key = SigningKey(self.salt, password, self.scrypt_params)
-        selfcert.sign([key])
-        logging.debug("Key publish : {0}".format(selfcert.signed_raw()))
-
-        responses = await community.bma_access.broadcast(bma.wot.Add, {}, {'identity': selfcert.signed_raw()})
-        result = (False, "")
-        for r in responses:
-            if r.status == 200:
-                result = (True, (await r.json()))
-            elif not result[0]:
-                result = (False, (await r.text()))
-            else:
-                await r.release()
-        if result[0]:
-            (await self.identity(community)).sigdate = block_uid
-        return result
-
-    async def send_membership(self, password, community, mstype):
-        """
-        Send a membership document to a target community.
-        Signal "document_broadcasted" is emitted at the end.
-
-        :param str password: The account SigningKey password
-        :param community: The community target of the membership document
-        :param str mstype: The type of membership demand. "IN" to join, "OUT" to leave
-        """
-        logging.debug("Send membership")
-
-        blockUID = community.network.current_blockUID
-        self_identity = await self._identities_registry.future_find(self.pubkey, community)
-        selfcert = await self_identity.selfcert(community)
-
-        membership = Membership(PROTOCOL_VERSION, community.currency,
-                                selfcert.pubkey, blockUID, mstype, selfcert.uid,
-                                selfcert.timestamp, None)
-        key = SigningKey(self.salt, password, self.scrypt_params)
-        membership.sign([key])
-        logging.debug("Membership : {0}".format(membership.signed_raw()))
-        responses = await community.bma_access.broadcast(bma.blockchain.Membership, {},
-                        {'membership': membership.signed_raw()})
-        result = (False, "")
-        for r in responses:
-            if r.status == 200:
-                result = (True, (await r.json()))
-            elif not result[0]:
-                result = (False, (await r.text()))
-            else:
-                await r.release()
-        return result
-
-    async def certify(self, password, community, pubkey):
-        """
-        Certify an other identity
-
-        :param str password: The account SigningKey password
-        :param sakia.core.community.Community community: The community target of the certification
-        :param str pubkey: The certified identity pubkey
-        """
-        logging.debug("Certdata")
-        blockUID = community.network.current_blockUID
-        try:
-            identity = await self._identities_registry.future_find(pubkey, community)
-            selfcert = await identity.selfcert(community)
-        except LookupFailureError as e:
-            return False, str(e)
-
-        if selfcert:
-            certification = Certification(PROTOCOL_VERSION, community.currency,
-                                          self.pubkey, pubkey, blockUID, None)
-
-            key = SigningKey(self.salt, password, self.scrypt_params)
-            certification.sign(selfcert, [key])
-            signed_cert = certification.signed_raw(selfcert)
-            logging.debug("Certification : {0}".format(signed_cert))
-
-            data = {'cert': certification.signed_raw(selfcert)}
-            logging.debug("Posted data : {0}".format(data))
-            responses = await community.bma_access.broadcast(bma.wot.Certify, {}, data)
-            result = (False, "")
-            for r in responses:
-                if r.status == 200:
-                    result = (True, (await r.json()))
-                    # signal certification to all listeners
-                    self.certification_accepted.emit()
-                elif not result[0]:
-                    result = (False, (await r.text()))
-                else:
-                    await r.release()
-            return result
-        else:
-            return False, self.tr("Could not find user self certification.")
-
-    async def revoke(self, password, community):
-        """
-        Revoke self-identity on server, not in blockchain
-
-        :param str password: The account SigningKey password
-        :param sakia.core.community.Community community: The community target of the revokation
-        """
-        revoked = await self._identities_registry.future_find(self.pubkey, community)
-
-        revokation = Revocation(PROTOCOL_VERSION, community.currency, None)
-        selfcert = await revoked.selfcert(community)
-
-        key = SigningKey(self.salt, password, self.scrypt_params)
-        revokation.sign(selfcert, [key])
-
-        logging.debug("Self-Revokation Document : \n{0}".format(revokation.raw(selfcert)))
-        logging.debug("Signature : \n{0}".format(revokation.signatures[0]))
-
-        data = {
-            'pubkey': revoked.pubkey,
-            'self_': selfcert.signed_raw(),
-            'sig': revokation.signatures[0]
-        }
-        logging.debug("Posted data : {0}".format(data))
-        responses = await community.bma_access.broadcast(bma.wot.Revoke, {}, data)
-        result = (False, "")
-        for r in responses:
-            if r.status == 200:
-                result = (True, (await r.json()))
-            elif not result[0]:
-                result = (False, (await r.text()))
-            else:
-                await r.release()
-        return result
-
-    async def generate_revokation(self, community, password):
-        """
-        Generate account revokation document for given community
-        :param sakia.core.Community community: the community
-        :param str password: the password
-        :return: the revokation document
-        :rtype: duniterpy.documents.certification.Revocation
-        """
-        document = Revocation(PROTOCOL_VERSION, community.currency, self.pubkey, "")
-        identity = await self.identity(community)
-        selfcert = await identity.selfcert(community)
-
-        key = SigningKey(self.salt, password, self.scrypt_params)
-
-        document.sign(selfcert, [key])
-        return document.signed_raw(selfcert)
-
-    def start_coroutines(self):
-        for c in self.communities:
-            c.start_coroutines()
-
-    async def stop_coroutines(self, closing=False):
-        logging.debug("Stop communities coroutines")
-        for c in self.communities:
-            await c.stop_coroutines(closing)
-
-        logging.debug("Stop wallets coroutines")
-        for w in self.wallets:
-            w.stop_coroutines(closing)
-        logging.debug("Account coroutines stopped")
-
-    def jsonify(self):
-        """
-        Get the account in a json format.
-
-        :return: A dict view of the account to be saved as json
-        """
-        data_communities = []
-        for c in self.communities:
-            data_communities.append(c.jsonify())
-
-        data_wallets = []
-        for w in self.wallets:
-            data_wallets.append(w.jsonify())
-
-        data = {'name': self.name,
-                'salt': self.salt,
-                'pubkey': self.pubkey,
-                'scrypt_params': {
-                    'N': self.scrypt_params.N,
-                    'r': self.scrypt_params.r,
-                    'p': self.scrypt_params.p,
-                },
-                'communities': data_communities,
-                'wallets': data_wallets,
-                'contacts': self.contacts,
-                'file_version': __version__}
-        return data
diff --git a/src/sakia/core/app.py b/src/sakia/core/app.py
deleted file mode 100644
index 70666599b366fca4df2f24712f82a51843629067..0000000000000000000000000000000000000000
--- a/src/sakia/core/app.py
+++ /dev/null
@@ -1,552 +0,0 @@
-"""
-Created on 1 févr. 2014
-
-@author: inso
-"""
-
-import os
-import logging
-import tarfile
-import shutil
-import json
-import datetime
-import aiohttp
-import asyncio
-from pkg_resources import parse_version
-
-from PyQt5.QtCore import QObject, pyqtSignal, QTranslator, QCoreApplication, QLocale
-from duniterpy.api.bma import API
-from aiohttp.connector import ProxyConnector
-from . import config
-from .account import Account
-from .registry import IdentitiesRegistry, Identity
-from .. import __version__
-from ..tools.exceptions import NameAlreadyExists, BadAccountFile
-from ..tools.decorators import asyncify
-import i18n_rc
-
-
-class Application(QObject):
-
-    """
-    Managing core application datas :
-    Accounts list and general configuration
-    Saving and loading the application state
-    """
-
-    version_requested = pyqtSignal()
-    view_identity_in_wot = pyqtSignal(Identity)
-    refresh_transfers = pyqtSignal()
-    account_imported = pyqtSignal(str)
-    account_changed = pyqtSignal()
-
-    def __init__(self, qapp, loop, identities_registry):
-        """
-        Init a new "sakia" application
-        :param QCoreApplication qapp: Qt Application
-        :param quamash.QEventLoop loop: quamash.QEventLoop instance
-        :param sakia.core.registry.IdentitiesRegistry identities_registry: IdentitiesRegistry instance
-        :return:
-        """
-
-        super().__init__()
-        self.qapp = qapp
-        self.accounts = {}
-        self._current_account = None
-        self.loop = loop
-        self.available_version = (True,
-                                  __version__,
-                                  "")
-        self._translator = QTranslator(self.qapp)
-        self._identities_registry = identities_registry
-        self.preferences = {'account': "",
-                            'lang': 'en_GB',
-                            'ref': 0,
-                            'expert_mode': False,
-                            'digits_after_comma': 6,
-                            'maximized': False,
-                            'notifications': True,
-                            'enable_proxy': False,
-                            'proxy_type': "HTTP",
-                            'proxy_address': "",
-                            'proxy_port': 8080,
-                            'international_system_of_units': True,
-                            'auto_refresh': False,
-                            'forgetfulness':False
-                            }
-
-    @classmethod
-    def startup(cls, argv, qapp, loop):
-        config.parse_arguments(argv)
-        identities_registry = IdentitiesRegistry()
-        app = cls(qapp, loop, identities_registry)
-        app.load()
-        app.switch_language()
-        app.set_proxy()
-        app.get_last_version()
-        if app.preferences["account"] != "":
-            account = app.get_account(app.preferences["account"])
-            app.change_current_account(account)
-        # no default account...
-        else:
-            # if at least one account exists, set it as default...
-            if len(app.accounts) > 0:
-                # capture names sorted alphabetically
-                names = list(app.accounts.keys())
-                names.sort()
-                # set first name in list as default in preferences
-                app.preferences['account'] = names[0]
-                app.save_preferences(app.preferences)
-                # open it
-                logging.debug("No default account in preferences. Set %s as default account." % names[0])
-
-        return app
-
-    def set_proxy(self):
-        if self.preferences['enable_proxy'] is True:
-            API.aiohttp_connector = ProxyConnector("http://{0}:{1}".format(
-                                    self.preferences['proxy_address'],
-                                    self.preferences['proxy_port']))
-        else:
-            API.aiohttp_connector = None
-
-    def switch_language(self):
-        logging.debug("Loading translations")
-        locale = self.preferences['lang']
-        QLocale.setDefault(QLocale(locale))
-        QCoreApplication.removeTranslator(self._translator)
-        self._translator = QTranslator(self.qapp)
-        if locale == "en_GB":
-            QCoreApplication.installTranslator(self._translator)
-        elif self._translator.load(":/i18n/{0}".format(locale)):
-            if QCoreApplication.installTranslator(self._translator):
-                logging.debug("Loaded i18n/{0}".format(locale))
-            else:
-                logging.debug("Couldn't load translation")
-
-    @property
-    def current_account(self):
-        return self._current_account
-
-    def get_account(self, name):
-        """
-        Load an account then return it
-
-        :param str name: The account name
-        :return: The loaded account if it's a success, else return None
-        """
-        if name in self.accounts.keys():
-            self.load_account(name)
-            return self.accounts[name]
-        else:
-            return None
-
-    def create_account(self, name):
-        """
-        Create a new account from its name
-
-        :param str name: The account name
-        :return: The new account
-        :raise: NameAlreadyExists if the account name is already used locally
-        """
-        for a in self.accounts:
-            if a == name:
-                raise NameAlreadyExists(a)
-
-        account = Account.create(name, self._identities_registry)
-
-        return account
-
-    @property
-    def identities_registry(self):
-        return self._identities_registry
-
-    def add_account(self, account):
-        self.accounts[account.name] = account
-
-    @asyncify
-    async def delete_account(self, account):
-        """
-        Delete an account.
-        Current account changes to None if it is deleted.
-        """
-        await account.stop_coroutines()
-        self.accounts.pop(account.name)
-        if self._current_account == account:
-            self._current_account = None
-        with open(config.parameters['data'], 'w') as outfile:
-            json.dump(self.jsonify(), outfile, indent=4, sort_keys=True)
-        if self.preferences['account'] == account.name:
-            self.preferences['account'] = ""
-            self.save_preferences(self.preferences)
-
-    @asyncify
-    async def change_current_account(self, account):
-        """
-        Change current account displayed and refresh its cache.
-
-        :param sakia.core.Account account: The account object to display
-        .. note:: Emits the application pyqtSignal loading_progressed
-        during cache refresh
-        """
-        if self._current_account is not None:
-            await self.stop_current_account()
-
-        self._current_account = account
-        if self._current_account is not None:
-            self._current_account.start_coroutines()
-        self.account_changed.emit()
-
-    async def stop_current_account(self, closing=False):
-        """
-        Save the account to the cache
-        and stop the coroutines
-        """
-        self.save_cache(self._current_account)
-        self.save_notifications(self._current_account)
-        await self._current_account.stop_coroutines(closing)
-
-    def load(self):
-        """
-        Load a saved application state from the data file.
-        Loads only jsonified objects but not their cache.
-
-        If the standard application state file can't be found,
-        no error is raised.
-        """
-        self.load_registries()
-        self.load_preferences()
-        try:
-            logging.debug("Loading data...")
-            with open(config.parameters['data'], 'r') as json_data:
-                data = json.load(json_data)
-                for account_name in data['local_accounts']:
-                    self.accounts[account_name] = None
-        except FileNotFoundError:
-            pass
-
-    def load_registries(self):
-        """
-        Load the Person instances of the person module.
-        Each instance is unique, and can be find by its public key.
-        """
-        try:
-            identities_path = os.path.join(config.parameters['home'],
-                                        '__identities__')
-            with open(identities_path, 'r') as identities_data:
-                data = json.load(identities_data)
-                self._identities_registry.load_json(data)
-        except FileNotFoundError:
-            pass
-
-    def load_account(self, account_name):
-        """
-        Load an account from its name
-
-        :param str account_name: The account name
-        """
-        account_path = os.path.join(config.parameters['home'],
-                                    account_name, 'properties')
-        with open(account_path, 'r') as json_data:
-            data = json.load(json_data)
-            account = Account.load(data, self._identities_registry)
-            self.load_cache(account)
-            self.accounts[account_name] = account
-
-            for community in account.communities:
-                community.network.blockchain_rollback.connect(community.rollback_cache)
-                community.network.new_block_mined.connect(lambda b, co=community:
-                                                          account.refresh_transactions(self, co))
-                community.network.blockchain_rollback.connect(lambda b, co=community:
-                                                              account.rollback_transaction(self, co))
-                community.network.root_nodes_changed.connect(lambda acc=account: self.save(acc))
-
-        account_notifications_path = os.path.join(config.parameters['home'],
-                                    account_name, '__notifications__')
-
-        try:
-            with open(account_notifications_path, 'r') as json_data:
-                data = json.load(json_data)
-                for notification in data:
-                    if notification in account.notifications:
-                        account.notifications[notification] = data[notification]
-        except FileNotFoundError:
-            logging.debug("Could not find notifications file")
-            pass
-
-    def load_cache(self, account):
-        """
-        Load an account cache
-
-        :param account: The account object to load the cache
-        """
-        for community in account.communities:
-            bma_path = os.path.join(config.parameters['home'],
-                                        account.name, '__cache__',
-                                        community.currency + '_bma')
-
-            network_path = os.path.join(config.parameters['home'],
-                                        account.name, '__cache__',
-                                        community.currency + '_network')
-
-            if os.path.exists(network_path):
-                with open(network_path, 'r') as json_data:
-                    data = json.load(json_data)
-                    community.network.merge_with_json(data['network'], parse_version(data['version']))
-
-            if os.path.exists(bma_path):
-                with open(bma_path, 'r') as json_data:
-                    data = json.load(json_data)
-                    community.bma_access.load_from_json(data['cache'])
-
-        for wallet in account.wallets:
-            for c in account.communities:
-                wallet.init_cache(self, c)
-            wallet_path = os.path.join(config.parameters['home'],
-                                        account.name, '__cache__', wallet.pubkey + "_wal")
-            if os.path.exists(wallet_path):
-                with open(wallet_path, 'r') as json_data:
-                    data = json.load(json_data)
-                    wallet.load_caches(self, data)
-
-    def load_preferences(self):
-        """
-        Load the preferences.
-        """
-        try:
-            preferences_path = os.path.join(config.parameters['home'],
-                                            'preferences')
-            with open(preferences_path, 'r') as json_data:
-                data = json.load(json_data)
-                for key in data:
-                    self.preferences[key] = data[key]
-        except FileNotFoundError:
-            pass
-
-    def save_preferences(self, preferences):
-        """
-        Save the preferences.
-
-        :param preferences: A dict containing the keys/values of the preferences
-        """
-        assert('lang' in preferences)
-        assert('account' in preferences)
-        assert('ref' in preferences)
-
-        self.preferences = preferences
-        preferences_path = os.path.join(config.parameters['home'],
-                                        'preferences')
-        with open(preferences_path, 'w') as outfile:
-            json.dump(preferences, outfile, indent=4)
-
-        self.set_proxy()
-
-    def save(self, account):
-        """
-        Save an account
-
-        :param account: The account object to save
-        """
-        with open(config.parameters['data'], 'w') as outfile:
-            json.dump(self.jsonify(), outfile, indent=4, sort_keys=True)
-        account_path = os.path.join(config.parameters['home'],
-                                account.name)
-        if account.name in self.accounts:
-            properties_path = os.path.join(account_path, 'properties')
-            if not os.path.exists(account_path):
-                logging.info("Creating account directory")
-                os.makedirs(account_path)
-            with open(properties_path, 'w') as outfile:
-                json.dump(account.jsonify(), outfile, indent=4, sort_keys=True)
-        else:
-            account_path = os.path.join(config.parameters['home'], account.name)
-            shutil.rmtree(account_path)
-
-    def save_notifications(self, account):
-        """
-        Save an account notifications
-
-        :param account: The account object to save
-        """
-        account_path = os.path.join(config.parameters['home'],
-                                account.name)
-        notifications_path = os.path.join(account_path, '__notifications__')
-        with open(notifications_path, 'w') as outfile:
-            json.dump(account.notifications, outfile, indent=4, sort_keys=True)
-
-    def save_registries(self):
-        """
-        Save the registries
-        """
-        identities_path = os.path.join(config.parameters['home'],
-                                    '__identities__')
-        buffer_path = identities_path + ".buf"
-        with open(buffer_path, 'w') as outfile:
-            data = self.identities_registry.jsonify()
-            data['version'] = __version__
-            for chunk in json.JSONEncoder().iterencode(data):
-                outfile.write(chunk)
-        shutil.move(buffer_path, identities_path)
-
-    def save_wallet(self, account, wallet):
-        """
-        Save wallet of account in cache
-
-        :param sakia.core.account.Account account: Account instance
-        :param sakia.core.wallet.Wallet wallet: Wallet instance
-        """
-        if not os.path.exists(os.path.join(config.parameters['home'],
-                                           account.name, '__cache__')):
-            os.makedirs(os.path.join(config.parameters['home'],
-                                     account.name, '__cache__'))
-        wallet_path = os.path.join(config.parameters['home'],
-                                   account.name, '__cache__', wallet.pubkey + "_wal")
-        buffer_path = wallet_path + ".buf"
-        with open(buffer_path, 'w') as outfile:
-            data = wallet.jsonify_caches()
-            data['version'] = __version__
-            for chunk in json.JSONEncoder().iterencode(data):
-                outfile.write(chunk)
-        shutil.move(buffer_path, wallet_path)
-
-    def save_cache(self, account):
-        """
-        Save the cache of an account
-
-        :param account: The account object to save the cache
-        """
-        if not os.path.exists(os.path.join(config.parameters['home'],
-                                        account.name, '__cache__')):
-            os.makedirs(os.path.join(config.parameters['home'],
-                                        account.name, '__cache__'))
-        for wallet in account.wallets:
-            self.save_wallet(account, wallet)
-
-        for community in account.communities:
-            bma_path = os.path.join(config.parameters['home'],
-                                        account.name, '__cache__',
-                                        community.currency + '_bma')
-
-            network_path = os.path.join(config.parameters['home'],
-                                        account.name, '__cache__',
-                                        community.currency + '_network')
-            buffer_path = network_path + ".buf"
-
-            with open(buffer_path, 'w') as outfile:
-                data = dict()
-                data['network'] = community.network.jsonify()
-                data['version'] = __version__
-                for chunk in json.JSONEncoder().iterencode(data):
-                    outfile.write(chunk)
-            shutil.move(buffer_path, network_path)
-
-            buffer_path = bma_path + ".buf"
-
-            with open(buffer_path, 'w') as outfile:
-                data['cache'] = community.bma_access.jsonify()
-                data['version'] = __version__
-                for chunk in json.JSONEncoder().iterencode(data):
-                    outfile.write(chunk)
-            shutil.move(buffer_path, bma_path)
-
-    def import_account(self, file, name):
-        """
-        Import an account from a tar file and open it
-
-        :param str file: The file path of the tar file
-        :param str name: The account name
-        """
-        with tarfile.open(file, "r") as tar:
-            path = os.path.join(config.parameters['home'],
-                                name)
-            for obj in ["properties"]:
-                try:
-                    tar.getmember(obj)
-                except KeyError:
-                    raise BadAccountFile(file)
-            tar.extractall(path)
-
-        account_path = os.path.join(config.parameters['home'],
-                                    name, 'properties')
-        json_data = open(account_path, 'r')
-        data = json.load(json_data)
-        account = Account.load(data, self._identities_registry)
-        account.name = name
-        self.add_account(account)
-        self.save(account)
-        self.account_imported.emit(account.name)
-
-    def export_account(self, file, account):
-        """
-        Export an account to a tar file
-
-        :param str file: The filepath of the tar file
-        :param account: The account object to export
-        """
-        with tarfile.open(file, "w") as tar:
-            for file in ["properties"]:
-                path = os.path.join(config.parameters['home'],
-                                    account.name, file)
-                tar.add(path, file)
-
-    def jsonify_accounts(self):
-        """
-        Jsonify an account
-
-        :return: The account as a dict to format as json
-        """
-        data = []
-        logging.debug("{0}".format(self.accounts))
-        for account in self.accounts:
-            data.append(account)
-        return data
-
-    def jsonify(self):
-        """
-        Jsonify the app datas
-
-        :return: The accounts of the app to format as json
-        """
-        data = {'local_accounts': self.jsonify_accounts()}
-        return data
-
-    async def stop(self):
-        if self._current_account:
-            await self.stop_current_account(closing=True)
-        await asyncio.sleep(0)
-        self.save_registries()
-
-    @asyncify
-    async def get_last_version(self):
-        if self.preferences['enable_proxy'] is True:
-            connector = ProxyConnector("http://{0}:{1}".format(
-                                    self.preferences['proxy_address'],
-                                    self.preferences['proxy_port']))
-        else:
-            connector = None
-        try:
-            with aiohttp.Timeout(15):
-                response = await aiohttp.get("https://api.github.com/repos/duniter/sakia/releases", connector=connector)
-                if response.status == 200:
-                    releases = await response.json()
-                    latest = None
-                    for r in releases:
-                        if not latest:
-                            latest = r
-                        else:
-                            latest_date = datetime.datetime.strptime(latest['published_at'], "%Y-%m-%dT%H:%M:%SZ")
-                            date = datetime.datetime.strptime(r['published_at'], "%Y-%m-%dT%H:%M:%SZ")
-                            if latest_date < date:
-                                latest = r
-                    latest_version = latest["tag_name"]
-                    version = (__version__ == latest_version,
-                               latest_version,
-                               latest["html_url"])
-                    logging.debug("Found version : {0}".format(latest_version))
-                    logging.debug("Current version : {0}".format(__version__))
-                    self.available_version = version
-                self.version_requested.emit()
-        except (aiohttp.errors.ClientError, aiohttp.errors.TimeoutError) as e:
-            logging.debug("Could not connect to github : {0}".format(str(e)))
-        except Exception as e:
-            pass
diff --git a/src/sakia/core/community.py b/src/sakia/core/community.py
deleted file mode 100644
index 285b7ef7f017a70f7a83a01bc559181ae99f1665..0000000000000000000000000000000000000000
--- a/src/sakia/core/community.py
+++ /dev/null
@@ -1,342 +0,0 @@
-"""
-Created on 1 févr. 2014
-
-@author: inso
-"""
-
-import logging
-import re
-import math
-
-from PyQt5.QtCore import QObject
-
-from ..tools.exceptions import NoPeerAvailable
-from .net.network import Network
-from duniterpy.api import bma, errors
-from .net.api.bma.access import BmaAccess
-
-
-class Community(QObject):
-    """
-    A community is a group of nodes using the same currency.
-
-    .. warning:: The currency name is supposed to be unique in sakia
-    but nothing exists in duniter to assert that a currency name is unique.
-    """
-    def __init__(self, currency, network, bma_access):
-        """
-        Initialize community attributes with a currency and a network.
-
-        :param str currency: The currency name of the community.
-        :param sakia.core.net.network.Network network: The network of the community
-        :param sakia.core.net.api.bma.access.BmaAccess bma_access: The BMA Access object
-
-        .. warning:: The community object should be created using its factory
-        class methods.
-        """
-        super().__init__()
-        self.currency = currency
-        self._network = network
-        self._bma_access = bma_access
-
-    @classmethod
-    def create(cls, node):
-        """
-        Create a community from its first node.
-
-        :param node: The first Node of the community
-        """
-        network = Network.create(node)
-        bma_access = BmaAccess.create(network)
-        community = cls(node.currency, network, bma_access)
-        logging.debug("Creating community")
-        return community
-
-    @classmethod
-    def load(cls, json_data, file_version):
-        """
-        Load a community from json
-
-        :param dict json_data: The community as a dict in json format
-        :param NormalizedVersion file_version: the file sakia version
-        """
-        currency = json_data['currency']
-        network = Network.from_json(currency, json_data['peers'], file_version)
-        bma_access = BmaAccess.create(network)
-        community = cls(currency, network, bma_access)
-        return community
-
-    @property
-    def name(self):
-        """
-        The community name is its currency name.
-
-        :return: The community name
-        """
-        return self.currency
-
-    @property
-    def short_currency(self):
-        """
-        Format the currency name to a short one
-
-        :return: The currency name in a shot format.
-        """
-        words = re.split('[_\W]+', self.currency)
-        shortened = ""
-        if len(words) > 1:
-            shortened = ''.join([w[0] for w in words])
-        else:
-            vowels = ('a', 'e', 'i', 'o', 'u', 'y')
-            shortened = self.currency
-            shortened = ''.join([c for c in shortened if c not in vowels])
-        return shortened.upper()
-
-    @property
-    def currency_symbol(self):
-        """
-        Format the currency name to a symbol one.
-
-        :return: The currency name as a utf-8 circled symbol.
-        """
-        letter = self.currency[0]
-        u = ord('\u24B6') + ord(letter) - ord('A')
-        return chr(u)
-
-    async def dividend(self, block_number=None):
-        """
-        Get the last generated community universal dividend before block_number.
-        If block_number is None, returns the last block_number.
-
-        :param int block_number: The block at which we get the latest dividend
-
-        :return: The last UD or 1 if no UD was generated.
-        """
-        block = await self.get_ud_block(block_number=block_number)
-        if block:
-            return block['dividend'] * math.pow(10, block['unitbase'])
-        else:
-            return 1
-
-    async def computed_dividend(self):
-        """
-        Get the computed community universal dividend.
-
-        Calculation based on t = last UD block time and on values from the that block :
-
-        UD(computed) = CEIL(MAX(UD(t) ; c * M(t) / N(t)))
-
-        :return: The computed UD or 1 if no UD was generated.
-        """
-        block = await self.get_ud_block()
-        if block:
-            parameters = await self.parameters()
-            return math.ceil(
-                max(
-                    (await self.dividend()),
-                    float(0) if block['membersCount'] == 0 else
-                    parameters['c'] * block['monetaryMass'] / block['membersCount']
-                )
-            )
-
-        else:
-            return 1
-
-    async def get_ud_block(self, x=0, block_number=None):
-        """
-        Get a block with universal dividend
-        If x and block_number are passed to the result,
-        it returns the 'x' older block with UD in it BEFORE block_number
-
-        :param int x: Get the 'x' older block with UD in it
-        :param int block_number: Get the latest dividend before this block number
-        :return: The last block with universal dividend.
-        :rtype: dict
-        """
-        try:
-            udblocks = await self.bma_access.future_request(bma.blockchain.UD)
-            blocks = udblocks['result']['blocks']
-            if block_number:
-                blocks = [b for b in blocks if b <= block_number]
-            if len(blocks) > 0:
-                index = len(blocks) - (1+x)
-                if index < 0:
-                    index = 0
-                block_number = blocks[index]
-                block = await self.bma_access.future_request(bma.blockchain.Block,
-                                     req_args={'number': block_number})
-                return block
-            else:
-                return None
-        except errors.DuniterError as e:
-            logging.debug(str(e))
-            return None
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-            return None
-
-    async def monetary_mass(self):
-        """
-        Get the community monetary mass
-
-        :return: The monetary mass value
-        """
-        # Get cached block by block number
-        block_number = self.network.current_blockUID.number
-        if block_number:
-            block = await self.bma_access.future_request(bma.blockchain.Block,
-                                 req_args={'number': block_number})
-            return block['monetaryMass']
-        else:
-            return 0
-
-    async def nb_members(self):
-        """
-        Get the community members number
-
-        :return: The community members number
-        """
-        try:
-            # Get cached block by block number
-            block_number = self.network.current_blockUID.number
-            block = await self.bma_access.future_request(bma.blockchain.Block,
-                                 req_args={'number': block_number})
-            return block['membersCount']
-        except errors.DuniterError as e:
-            if e.ucode == errors.BLOCK_NOT_FOUND:
-                return 0
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-            return 0
-
-    async def time(self, block_number=None):
-        """
-        Get the blockchain time
-        :param block_number: The block number, None if current block
-        :return: The community blockchain time
-        :rtype: int
-        """
-        try:
-            # Get cached block by block number
-            if block_number is None:
-                block_number = self.network.current_blockUID.number
-            block = await self.bma_access.future_request(bma.blockchain.Block,
-                                 req_args={'number': block_number})
-            return block['medianTime']
-        except errors.DuniterError as e:
-            logging.debug(str(e))
-            return 0
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-            return 0
-
-    @property
-    def network(self):
-        """
-        Get the community network instance.
-
-        :return: The community network instance.
-        :rtype: sakia.core.net.Network
-        """
-        return self._network
-
-    @property
-    def bma_access(self):
-        """
-        Get the community bma_access instance
-
-        :return: The community bma_access instace
-        :rtype: sakia.core.net.api.bma.access.BmaAccess
-        """
-        return self._bma_access
-
-    async def parameters(self):
-        """
-        Return community parameters in bma format
-        """
-        return await self.bma_access.future_request(bma.blockchain.Parameters)
-
-    async def certification_expired(self, cert_time):
-        """
-        Return True if the certificaton time is too old
-
-        :param int cert_time: the timestamp of the certification
-        """
-        parameters = await self.parameters()
-        blockchain_time = await self.time()
-        return blockchain_time - cert_time > parameters['sigValidity']
-
-    async def certification_writable(self, cert_time):
-        """
-        Return True if the certificaton time is too old
-
-        :param int cert_time: the timestamp of the certification
-        """
-        parameters = await self.parameters()
-        blockchain_time = await self.time()
-        return blockchain_time - cert_time < parameters['sigWindow'] * parameters['avgGenTime']
-
-    def add_node(self, node):
-        """
-        Add a peer to the community.
-
-        :param peer: The new peer as a duniterpy Peer object.
-        """
-        self._network.add_root_node(node)
-
-    def remove_node(self, index):
-        """
-        Remove a node from the community.
-
-        :param index: The index of the removed node.
-        """
-        self._network.remove_root_node(index)
-
-    async def get_block(self, number=None):
-        """
-        Get a block
-
-        :param int number: The block number. If none, returns current block.
-        """
-        if number is None:
-            block_number = self.network.current_blockUID.number
-            data = await self.bma_access.future_request(bma.blockchain.Block,
-                                 req_args={'number': block_number})
-        else:
-            logging.debug("Requesting block {0}".format(number))
-            data = await self.bma_access.future_request(bma.blockchain.Block,
-                                req_args={'number': number})
-        return data
-
-    async def members_pubkeys(self):
-        """
-        Listing members pubkeys of a community
-
-        :return: All members pubkeys.
-        """
-        memberships = await self.bma_access.future_request(bma.wot.Members)
-        return [m['pubkey'] for m in memberships["results"]]
-
-    def start_coroutines(self):
-        self.network.start_coroutines()
-
-    async def stop_coroutines(self, closing=False):
-        await self.network.stop_coroutines(closing)
-
-    def rollback_cache(self):
-        self._bma_access.rollback()
-
-    def jsonify(self):
-        """
-        Jsonify the community datas.
-
-        :return: The community as a dict in json format.
-        """
-
-        nodes_data = []
-        for node in self._network.root_nodes:
-            nodes_data.append(node.jsonify_root_node())
-
-        data = {'currency': self.currency,
-                'peers': nodes_data}
-        return data
diff --git a/src/sakia/core/config.py b/src/sakia/core/config.py
deleted file mode 100644
index 50a9ecc011676a8064061c7280f08bbb2d18c538..0000000000000000000000000000000000000000
--- a/src/sakia/core/config.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-Created on 7 févr. 2014
-
-@author: inso
-"""
-
-import logging
-from logging import FileHandler
-from optparse import OptionParser
-from os import environ, path, makedirs
-
-
-if "XDG_CONFIG_HOME" in environ:
-    config_path = environ["XDG_CONFIG_HOME"]
-elif "HOME" in environ:
-    config_path = path.join(environ["HOME"], ".config")
-elif "APPDATA" in environ:
-    config_path = environ["APPDATA"]
-else:
-    config_path = path.dirname(__file__)
-
-parameters = {'home': path.join(config_path, 'sakia'),
-              'data': path.join(config_path, 'sakia', 'data')}
-
-
-if not path.exists(parameters['home']):
-    logging.info("Creating home directory")
-    makedirs((parameters['home']))
-
-
-def parse_arguments(argv):
-    parser = OptionParser()
-
-    parser.add_option("-v", "--verbose",
-                      action="store_true", dest="verbose", default=False,
-                      help="Print INFO messages to stdout")
-
-    parser.add_option("-d", "--debug",
-                      action="store_true", dest="debug", default=False,
-                      help="Print DEBUG messages to stdout")
-
-    (options, args) = parser.parse_args(argv)
-
-    if options.debug:
-        logging.basicConfig(format='%(levelname)s:%(module)s:%(funcName)s:%(message)s',
-            level=logging.DEBUG)
-    elif options.verbose:
-        logging.basicConfig(format='%(levelname)s:%(message)s',
-            level=logging.INFO)
-    else:
-        logging.getLogger().propagate = False
-    logging.getLogger('quamash').setLevel(logging.INFO)
-    logfile = FileHandler(path.join(parameters['home'], 'sakia.log'))
-    logging.getLogger().addHandler(logfile)
diff --git a/src/sakia/core/graph/__init__.py b/src/sakia/core/graph/__init__.py
deleted file mode 100644
index 21318ddce99b0385dca8da9f9e90ca2314b4df3e..0000000000000000000000000000000000000000
--- a/src/sakia/core/graph/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .base_graph import BaseGraph
-from .wot_graph import WoTGraph
-from .explorer_graph import ExplorerGraph
\ No newline at end of file
diff --git a/src/sakia/core/graph/base_graph.py b/src/sakia/core/graph/base_graph.py
deleted file mode 100644
index d320660b3348fab71d0d767456ab0d2fcf4650ca..0000000000000000000000000000000000000000
--- a/src/sakia/core/graph/base_graph.py
+++ /dev/null
@@ -1,189 +0,0 @@
-import logging
-import time
-import networkx
-from PyQt5.QtCore import QLocale, QDateTime, QObject
-from ...tools.exceptions import NoPeerAvailable
-from ..net.network import MAX_CONFIRMATIONS
-from .constants import EdgeStatus, NodeStatus
-
-
-class BaseGraph(QObject):
-    def __init__(self, app, community, nx_graph=None):
-        """
-        Init Graph instance
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.community.Community community: Community instance
-        :param networkx.Graph nx_graph: The networkx graph
-        :return:
-        """
-        super().__init__()
-        self.app = app
-        self.community = community
-        # graph empty if None parameter
-        self.nx_graph = nx_graph if nx_graph else networkx.DiGraph()
-
-    async def arc_status(self, cert_time):
-        """
-        Get arc status of a certification
-        :param int cert_time: the timestamp of the certification
-        :return: the certification time
-        """
-        parameters = await self.community.parameters()
-        signature_validity = parameters['sigValidity']
-        #  arc considered strong during 75% of signature validity time
-        arc_strong = int(signature_validity * 0.75)
-        # display validity status
-        ts = time.time()
-        if (time.time() - cert_time) > arc_strong:
-            return EdgeStatus.WEAK
-        else:
-            return EdgeStatus.STRONG
-
-    async def node_status(self, node_identity, account_identity):
-        """
-        Return the status of the node depending
-        :param sakia.core.registry.Identity node_identity: The identity of the node
-        :param sakia.core.registry.Identity account_identity: The identity of the account displayed
-        :return: HIGHLIGHTED if node_identity is account_identity and OUT if the node_identity is not a member
-        :rtype: sakia.core.graph.constants.NodeStatus
-        """
-        # new node
-        node_status = NodeStatus.NEUTRAL
-        is_member = await node_identity.is_member(self.community)
-        if node_identity.pubkey == account_identity.pubkey:
-            node_status += NodeStatus.HIGHLIGHTED
-        if is_member is False:
-            node_status += NodeStatus.OUT
-        return node_status
-
-    def confirmation_text(self, block_number):
-        """
-        Build confirmation text of an arc
-        :param int block_number: the block number of the certification
-        :return: the confirmation text
-        :rtype: str
-        """
-        try:
-            current_confirmations = self.community.network.confirmations(block_number)
-
-            if MAX_CONFIRMATIONS > current_confirmations:
-                if self.app.preferences['expert_mode']:
-                    return "{0}/{1}".format(current_confirmations,
-                                                              MAX_CONFIRMATIONS)
-                else:
-                    confirmation = current_confirmations / MAX_CONFIRMATIONS * 100
-                    return "{0} %".format(QLocale().toString(float(confirmation), 'f', 0))
-        except ValueError:
-            pass
-        return None
-
-    def is_sentry(self, nb_certs, nb_members):
-        """
-        Check if it is a sentry or not
-        :param int nb_certs: the number of certs
-        :param int nb_members: the number of members
-        :return: True if a sentry
-        """
-        Y = {
-            10: 2,
-            100: 4,
-            1000: 6,
-            10000: 12,
-            100000: 20
-        }
-        for k in reversed(sorted(Y.keys())):
-            if nb_members >= k:
-                return nb_certs >= Y[k]
-        return False
-
-    async def add_certifier_list(self, certifier_list, identity, account_identity):
-        """
-        Add list of certifiers to graph
-        :param list certifier_list: List of certifiers from api
-        :param sakia.core.registry.Identity identity:   identity instance which is certified
-        :param sakia.core.registry.Identity account_identity:   Account identity instance
-        :return:
-        """
-        if self.community:
-            try:
-                #  add certifiers of uid
-                for certifier in tuple(certifier_list):
-                    node_status = await self.node_status(certifier['identity'], account_identity)
-                    metadata = {
-                        'text': certifier['identity'].uid,
-                        'tooltip': certifier['identity'].pubkey,
-                        'status': node_status
-                    }
-                    self.nx_graph.add_node(certifier['identity'].pubkey, attr_dict=metadata)
-
-                    arc_status = await self.arc_status(certifier['cert_time'])
-                    sig_validity = (await self.community.parameters())['sigValidity']
-                    arc = {
-                        'status': arc_status,
-                        'tooltip': QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(certifier['cert_time'] + sig_validity).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        ),
-                        'cert_time': certifier['cert_time'],
-                        'confirmation_text': self.confirmation_text(certifier['block_number'])
-                    }
-
-                    self.nx_graph.add_edge(certifier['identity'].pubkey, identity.pubkey, attr_dict=arc, weight=len(certifier_list))
-            except NoPeerAvailable as e:
-                logging.debug(str(e))
-
-    async def add_certified_list(self, certified_list, identity, account_identity):
-        """
-        Add list of certified from api to graph
-        :param list certified_list: List of certified from api
-        :param identity identity:   identity instance which is certifier
-        :param identity account_identity:   Account identity instance
-        :return:
-        """
-
-        if self.community:
-            try:
-                # add certified by uid
-                for certified in tuple(certified_list):
-                    node_status = await self.node_status(certified['identity'], account_identity)
-                    metadata = {
-                        'text': certified['identity'].uid,
-                        'tooltip': certified['identity'].pubkey,
-                        'status': node_status
-                    }
-                    self.nx_graph.add_node(certified['identity'].pubkey, attr_dict=metadata)
-
-                    arc_status = await self.arc_status(certified['cert_time'])
-                    sig_validity = (await self.community.parameters())['sigValidity']
-                    arc = {
-                        'status': arc_status,
-                        'tooltip': QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(certified['cert_time'] + sig_validity).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        ),
-                        'cert_time': certified['cert_time'],
-                        'confirmation_text': self.confirmation_text(certified['block_number'])
-                    }
-
-                    self.nx_graph.add_edge(identity.pubkey, certified['identity'].pubkey, attr_dict=arc,
-                                           weight=len(certified_list))
-
-            except NoPeerAvailable as e:
-                logging.debug(str(e))
-
-    def add_identity(self, identity, status):
-        """
-        Add identity as a new node in graph
-        :param identity identity: identity instance
-        :param int status:  Optional, default=None, Node status (see sakia.gui.views.wot)
-        :param list edges:  Optional, default=None, List of edges (certified by identity)
-        :param list connected:  Optional, default=None, Public key list of the connected nodes around the identity
-        """
-        metadata = {
-            'text': identity.uid,
-            'tooltip': identity.pubkey,
-            'status': status
-        }
-        self.nx_graph.add_node(identity.pubkey, attr_dict=metadata)
diff --git a/src/sakia/core/graph/explorer_graph.py b/src/sakia/core/graph/explorer_graph.py
deleted file mode 100644
index 856ae22f593ac14d2f8ec988dbbcba8e12f19351..0000000000000000000000000000000000000000
--- a/src/sakia/core/graph/explorer_graph.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import logging
-import networkx
-import asyncio
-from PyQt5.QtCore import pyqtSignal
-from .base_graph import BaseGraph
-from ..graph.constants import EdgeStatus, NodeStatus
-
-
-class ExplorerGraph(BaseGraph):
-
-    graph_changed = pyqtSignal()
-    current_identity_changed = pyqtSignal(str)
-
-    def __init__(self, app, community, nx_graph=None):
-        """
-        Init ExplorerGraph instance
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.community.Community community: Community instance
-        :param networkx.Graph nx_graph: The networkx graph
-        :return:
-        """
-        super().__init__(app, community, nx_graph)
-        self.exploration_task = None
-        self.explored_identity = None
-        self.steps = 0
-
-    def start_exploration(self, identity, steps):
-        """
-        Start exploration of the wot from given identity
-        :param sakia.core.registry.Identity identity: The identity source of exploration
-        :param int steps: The number of steps from identity to explore
-        """
-        if self.exploration_task:
-            if self.explored_identity is not identity or steps != self.steps:
-                self.exploration_task.cancel()
-            else:
-                return
-        self.nx_graph.clear()
-        self.explored_identity = identity
-        self.steps = steps
-        self.exploration_task = asyncio.ensure_future(self._explore(identity, steps))
-
-    def stop_exploration(self):
-        """
-        Stop current exploration task, if present.
-        """
-        if self.exploration_task:
-            self.exploration_task.cancel()
-            self.exploration_task = None
-
-    async def _explore(self, identity, steps):
-        """
-        Scan graph recursively
-        :param sakia.core.registry.Identity identity:   identity instance from where we start
-        :param int steps: The number of steps from given identity to explore
-        :return: False when the identity is added in the graph
-        """
-        # functions keywords args are persistent... Need to reset it with None trick
-        logging.debug("search %s in " % identity.uid)
-
-        explored = []
-        explorable = {0: [identity]}
-        current_identity = identity
-        self.nx_graph.clear()
-        self.add_identity(current_identity, NodeStatus.HIGHLIGHTED)
-        self.nx_graph.node[current_identity.pubkey]['is_sentry'] = False
-        self.graph_changed.emit()
-        for step in range(1, steps + 1):
-            explorable[step] = []
-
-        for step in range(0, steps):
-            while len(explorable[step]) > 0:
-                current_identity = explorable[step].pop()
-                # for each pubkey connected...
-                if current_identity not in explored:
-                    self.current_identity_changed.emit(current_identity.pubkey)
-                    node = self.add_identity(current_identity, NodeStatus.NEUTRAL)
-                    self.nx_graph.node[current_identity.pubkey]['is_sentry'] = False
-                    logging.debug("New identity explored : {pubkey}".format(pubkey=current_identity.pubkey[:5]))
-                    self.graph_changed.emit()
-
-                    certifier_list = await current_identity.unique_valid_certifiers_of(self.app.identities_registry,
-                                                                                             self.community)
-                    await self.add_certifier_list(certifier_list, current_identity, identity)
-                    logging.debug("New identity certifiers : {pubkey}".format(pubkey=current_identity.pubkey[:5]))
-
-                    is_sentry = self.is_sentry(len(certifier_list), await self.community.nb_members())
-                    self.nx_graph.node[current_identity.pubkey]['is_sentry'] = is_sentry
-
-                    self.graph_changed.emit()
-
-
-                    certified_list = await current_identity.unique_valid_certified_by(self.app.identities_registry,
-                                                                                            self.community)
-                    await self.add_certified_list(certified_list, current_identity, identity)
-                    logging.debug("New identity certified : {pubkey}".format(pubkey=current_identity.pubkey[:5]))
-                    self.graph_changed.emit()
-
-                    for cert in certified_list + certifier_list:
-                        if cert['identity'] not in explorable[step + 1]:
-                            explorable[step + 1].append(cert['identity'])
-
-                    explored.append(current_identity)
-                    logging.debug("New identity explored : {pubkey}".format(pubkey=current_identity.pubkey[:5]))
-                    self.graph_changed.emit()
-        self.current_identity_changed.emit("")
diff --git a/src/sakia/core/graph/wot_graph.py b/src/sakia/core/graph/wot_graph.py
deleted file mode 100644
index 60ab1cd28b9ece3c61eab094f059ddda35be54ab..0000000000000000000000000000000000000000
--- a/src/sakia/core/graph/wot_graph.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import logging
-import asyncio
-import networkx
-from .base_graph import BaseGraph
-from .constants import NodeStatus
-
-
-class WoTGraph(BaseGraph):
-    def __init__(self, app, community, nx_graph=None):
-        """
-        Init WoTGraph instance
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.community.Community community: Community instance
-        :param networkx.Graph nx_graph: The networkx graph
-        :return:
-        """
-        super().__init__(app, community, nx_graph)
-
-    async def initialize(self, center_identity, account_identity):
-        node_status = await self.node_status(center_identity, account_identity)
-
-        self.add_identity(center_identity, node_status)
-
-        # create Identity from node metadata
-        certifier_coro = asyncio.ensure_future(center_identity.unique_valid_certifiers_of(self.app.identities_registry,
-                                                                        self.community))
-        certified_coro = asyncio.ensure_future(center_identity.unique_valid_certified_by(self.app.identities_registry,
-                                                                       self.community))
-
-        certifier_list, certified_list = await asyncio.gather(certifier_coro, certified_coro)
-
-        # populate graph with certifiers-of
-        certifier_coro = asyncio.ensure_future(self.add_certifier_list(certifier_list,
-                                                                       center_identity, account_identity))
-        # populate graph with certified-by
-        certified_coro = asyncio.ensure_future(self.add_certified_list(certified_list,
-                                                                       center_identity, account_identity))
-
-        await asyncio.gather(certifier_coro, certified_coro)
-
-    async def get_shortest_path_to_identity(self, from_identity, to_identity):
-        """
-        Return path list of nodes from from_identity to to_identity
-        :param identity from_identity:
-        :param identity to_identity:
-        :return:
-        """
-        path = list()
-
-        logging.debug("path between %s to %s..." % (from_identity.uid, to_identity.uid))
-        self.add_identity(from_identity, NodeStatus.HIGHLIGHTED)
-
-        # recursively feed graph searching for account node...
-        await self.explore_to_find_member(from_identity, to_identity)
-
-        # calculate path of nodes between identity and to_identity
-        try:
-            path = networkx.shortest_path(self.nx_graph, from_identity.pubkey, to_identity.pubkey)
-        except networkx.exception.NetworkXException as e:
-            logging.debug(str(e))
-
-        return path
-
-    async def explore_to_find_member(self, account_identity, to_identity):
-        """
-        Scan graph to find identity
-        :param sakia.core.registry.Identity from_identity: Scan starting point
-        :param sakia.core.registry.Identity to_identity: Scan goal
-        """
-        explored = []
-        explorable = [account_identity]
-
-        while len(explorable) > 0:
-            current = explorable.pop()
-            certified_list = await current.unique_valid_certified_by(self.app.identities_registry,
-                                                                                    self.community)
-
-            await self.add_certified_list(certified_list, current, account_identity)
-            if to_identity.pubkey in [data['identity'].pubkey for data in certified_list]:
-                return True
-
-            explored.append(current)
-            for entry in certified_list:
-                if entry['identity'] not in explored + explorable:
-                    explorable.append(entry['identity'])
-
-        return False
diff --git a/src/sakia/core/money/base_referential.py b/src/sakia/core/money/base_referential.py
deleted file mode 100644
index 57adf6715c7316015c565d037af0a602c661a79a..0000000000000000000000000000000000000000
--- a/src/sakia/core/money/base_referential.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, QObject, QLocale
-import asyncio
-
-
-class BaseReferential:
-    """
-    Interface to all referentials
-    """
-    def __init__(self, amount, community, app, block_number=None):
-        self.amount = amount
-        self.community = community
-        self.app = app
-        self._block_number = block_number
-
-    @classmethod
-    def instance(cls, amount, community, app, block_number=None):
-        return cls(amount, community, app, block_number)
-
-    @classmethod
-    def translated_name(self):
-        pass
-
-    @property
-    def units(self):
-        pass
-
-    @property
-    def diff_units(self):
-        pass
-
-    async def value(self):
-        pass
-
-    async def differential(self):
-        pass
-
-    @staticmethod
-    def to_si(value, digits):
-        pass
-
-    async def localized(self, units=False, international_system=False):
-        pass
-
-    async def diff_localized(self, units=False, international_system=False):
-        pass
diff --git a/src/sakia/core/money/dividend_per_day.py b/src/sakia/core/money/dividend_per_day.py
deleted file mode 100644
index 7528c867120fd6c249b4e3ba98466c966f2987e2..0000000000000000000000000000000000000000
--- a/src/sakia/core/money/dividend_per_day.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from PyQt5.QtCore import QObject, QCoreApplication, QT_TRANSLATE_NOOP, QLocale
-from .base_referential import BaseReferential
-from .udd_to_past import UDDToPast
-
-from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, QLocale
-
-
-class DividendPerDay(BaseReferential):
-    _NAME_STR_ = QT_TRANSLATE_NOOP('DividendPerDay', 'UDD')
-    _REF_STR_ = QT_TRANSLATE_NOOP('DividendPerDay', "{0} {1}UDD {2}")
-    _UNITS_STR_ = QT_TRANSLATE_NOOP('DividendPerDay', "UDD {0}")
-    _FORMULA_STR_ = QT_TRANSLATE_NOOP('DividendPerDay',
-                                      """UDD(t) = (Q * 100) / (UD(t) / DT)
-                                        <br >
-                                        <table>
-                                        <tr><td>R</td><td>Dividend per day in percent</td></tr>
-                                        <tr><td>t</td><td>Last UD time</td></tr>
-                                        <tr><td>Q</td><td>Quantitative value</td></tr>
-                                        <tr><td>UD</td><td>Universal Dividend</td></tr>
-                                        <tr><td>DT</td><td>Delay between two UD in days</td></tr>
-                                        </table>"""
-                                      )
-    _DESCRIPTION_STR_ = QT_TRANSLATE_NOOP('DividendPerDay',
-                                          """Universal Dividend per Day displayed in percent.
-                                          The purpose is to have a default unit that is easy to use and understand.
-                                          100 UDD equal the Universal Dividend created per day.
-                                          """.replace('\n', '<br >'))
-
-    def __init__(self, amount, community, app, block_number=None):
-        super().__init__(amount, community, app, block_number)
-
-    @classmethod
-    def instance(cls, amount, community, app, block_number=None):
-        if app.preferences['forgetfulness']:
-            return cls(amount, community, app, block_number)
-        else:
-            return UDDToPast(amount, community, app, block_number)
-
-    @classmethod
-    def translated_name(cls):
-        return QCoreApplication.translate('DividendPerDay', DividendPerDay._NAME_STR_)
-
-    @property
-    def units(self):
-        return QCoreApplication.translate("DividendPerDay", DividendPerDay._UNITS_STR_).format(self.community.short_currency)
-
-    @property
-    def formula(self):
-        return QCoreApplication.translate('DividendPerDay', DividendPerDay._FORMULA_STR_)
-
-    @property
-    def description(self):
-        return QCoreApplication.translate("DividendPerDay", DividendPerDay._DESCRIPTION_STR_)
-
-    @property
-    def diff_units(self):
-        return self.units
-
-    async def value(self):
-        """
-        Return relative value of amount
-
-        value = (Q * 100) / R
-        Q = Quantitative value
-        R = UD(t) of one day
-        t = last UD block time
-
-        :param int amount:   Value
-        :param sakia.core.community.Community community: Community instance
-        :return: float
-        """
-        dividend = await self.community.dividend()
-        params = await self.community.parameters()
-        if dividend > 0:
-            return (self.amount * 100) / (float(dividend) / (params['dt'] / 86400))
-        else:
-            return self.amount
-
-    async def differential(self):
-        return await self.value()
-
-    async def localized(self, units=False, international_system=False):
-        value = await self.value()
-        prefix = ""
-        localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
-
-        if units or international_system:
-            return QCoreApplication.translate("Relative", DividendPerDay._REF_STR_) \
-                .format(localized_value,
-                        prefix,
-                        self.community.short_currency if units else "")
-        else:
-            return localized_value
-
-    async def diff_localized(self, units=False, international_system=False):
-        value = await self.differential()
-        prefix = ""
-        localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
-
-        if units or international_system:
-            return QCoreApplication.translate("Relative", DividendPerDay._REF_STR_) \
-                .format(localized_value,
-                        prefix,
-                        self.community.short_currency if units else "")
-        else:
-            return localized_value
diff --git a/src/sakia/core/money/relative_to_past.py b/src/sakia/core/money/relative_to_past.py
deleted file mode 100644
index 83229eb2a1bc054831a3006265c804ec793ffc05..0000000000000000000000000000000000000000
--- a/src/sakia/core/money/relative_to_past.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from PyQt5.QtCore import QObject, QCoreApplication, QT_TRANSLATE_NOOP, QLocale, QDateTime
-from .base_referential import BaseReferential
-
-
-class RelativeToPast(BaseReferential):
-    _NAME_STR_ = QT_TRANSLATE_NOOP('RelativeToPast', 'Past UD')
-    _REF_STR_ = QT_TRANSLATE_NOOP('RelativeToPast', "{0} {1}UD({2}) {3}")
-    _UNITS_STR_ = QT_TRANSLATE_NOOP('RelativeToPast', "UD({0}) {1}")
-    _FORMULA_STR_ = QT_TRANSLATE_NOOP('RelativeToPast',
-                                      """R = Q / UD(t)
-                                        <br >
-                                        <table>
-                                        <tr><td>R</td><td>Relative value</td></tr>
-                                        <tr><td>Q</td><td>Quantitative value</td></tr>
-                                        <tr><td>UD</td><td>Universal Dividend</td></tr>
-                                        <tr><td>t</td><td>Time when the value appeared</td></tr>
-                                        </table>"""
-                                      )
-    _DESCRIPTION_STR_ = QT_TRANSLATE_NOOP('RelativeToPast',
-                                          """Relative referential using UD at the Time when the value appeared.
-                                          Relative value R is calculated by dividing the quantitative value Q by the
-                                           Universal Dividend UD at the Time when the value appeared.
-                                          All past UD created are displayed with a value of 1 UD.
-                                          This referential is practical to remember what was the value at the Time.
-                                          """.replace('\n', '<br >'))
-
-    def __init__(self, amount, community, app, block_number=None):
-        super().__init__(amount, community, app, block_number)
-
-    @classmethod
-    def translated_name(cls):
-        return QCoreApplication.translate('RelativeToPast', RelativeToPast._NAME_STR_)
-
-    @property
-    def units(self):
-        return QCoreApplication.translate("RelativeToPast", RelativeToPast._UNITS_STR_).format('t',
-                                                                                               self.community.short_currency)
-    @property
-    def formula(self):
-        return QCoreApplication.translate('RelativeToPast', RelativeToPast._FORMULA_STR_)
-
-    @property
-    def description(self):
-        return QCoreApplication.translate("RelativeToPast", RelativeToPast._DESCRIPTION_STR_)
-
-    @property
-    def diff_units(self):
-        return self.units
-
-    async def value(self):
-        """
-        Return relative to past value of amount
-        :return: float
-        """
-        dividend = await self.community.dividend()
-        if dividend > 0:
-            return self.amount / float(dividend)
-        else:
-            return self.amount
-
-    async def differential(self):
-        """
-        Return relative to past differential value of amount
-        :return: float
-        """
-        dividend = await self.community.dividend(self._block_number)
-        if dividend > 0:
-            return self.amount / float(dividend)
-        else:
-            return self.amount
-
-    async def localized(self, units=False, international_system=False):
-        from . import Relative
-        value = await self.value()
-        block = await self.community.get_ud_block()
-        prefix = ""
-        if international_system:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
-
-        if units or international_system:
-            return QCoreApplication.translate("RelativeToPast", RelativeToPast._REF_STR_) \
-                .format(localized_value,
-                        prefix,
-                        QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(block['medianTime']).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        ),
-                        self.community.short_currency if units else "")
-        else:
-            return localized_value
-
-    async def diff_localized(self, units=False, international_system=False):
-        from . import Relative
-        value = await self.differential()
-        block = await self.community.get_ud_block(0, self._block_number)
-        if block:
-            date = QLocale.toString(
-                        QLocale(),
-                        QDateTime.fromTime_t(block['medianTime']).date(),
-                        QLocale.dateFormat(QLocale(), QLocale.ShortFormat))
-        else:
-            date = "###"
-        prefix = ""
-        if international_system and value != 0:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
-
-        if units or international_system:
-            return QCoreApplication.translate("RelativeToPast", RelativeToPast._REF_STR_)\
-                .format(localized_value,prefix,date,
-                    self.community.short_currency if units else "")
-        else:
-            return localized_value
diff --git a/src/sakia/core/money/udd_to_past.py b/src/sakia/core/money/udd_to_past.py
deleted file mode 100644
index fc85821f9b243d41c2e77dda3e731942e440253a..0000000000000000000000000000000000000000
--- a/src/sakia/core/money/udd_to_past.py
+++ /dev/null
@@ -1,136 +0,0 @@
-from PyQt5.QtCore import QObject, QCoreApplication, QT_TRANSLATE_NOOP, QLocale, QDateTime
-from .base_referential import BaseReferential
-
-
-class UDDToPast(BaseReferential):
-    _NAME_STR_ = QT_TRANSLATE_NOOP('UDDToPast', 'Past UUD')
-    _REF_STR_ = QT_TRANSLATE_NOOP('UDDToPast', "{0} {1}UUD({2}) {3}")
-    _UNITS_STR_ = QT_TRANSLATE_NOOP('UDDToPast', "UUD({0}) {1}")
-    _FORMULA_STR_ = QT_TRANSLATE_NOOP('UDDToPast',
-                                      """R = Q / UD(t)
-                                        <br >
-                                        <table>
-                                        <tr><td>R</td><td>Dividend per day in percent</td></tr>
-                                        <tr><td>t</td><td>Last UD time</td></tr>
-                                        <tr><td>Q</td><td>Quantitative value</td></tr>
-                                        <tr><td>UD</td><td>Universal Dividend</td></tr>
-                                        <tr><td>t</td><td>Time when the value appeared</td></tr>
-                                        <tr><td>DT</td><td>Delay between two UD in days</td></tr>
-                                        </table>>"""
-                                      )
-    _DESCRIPTION_STR_ = QT_TRANSLATE_NOOP('UDDToPast',
-                                          """Universal Dividend per Day displayed in percent, using UD at the Time
-                                          when the value appeared.
-                                          The purpose is to have a default unit that is easy to use and understand.
-                                          100 UDD equal the Universal Dividend created per day.
-                                          Relative referential
-                                          Relative value R is calculated by dividing the quantitative value Q by the
-                                          """.replace('\n', '<br >'))
-
-    def __init__(self, amount, community, app, block_number=None):
-        super().__init__(amount, community, app, block_number)
-
-    @classmethod
-    def translated_name(cls):
-        return QCoreApplication.translate('UDDToPast', UDDToPast._NAME_STR_)
-
-    @property
-    def units(self):
-        return QCoreApplication.translate("UDDToPast", UDDToPast._UNITS_STR_).format('t', self.community.short_currency)
-    @property
-    def formula(self):
-        return QCoreApplication.translate('UDDToPast', UDDToPast._FORMULA_STR_)
-
-    @property
-    def description(self):
-        return QCoreApplication.translate("UDDToPast", UDDToPast._DESCRIPTION_STR_)
-
-    @property
-    def diff_units(self):
-        return self.units
-
-    async def value(self):
-        """
-        Return relative value of amount
-
-        value = (Q * 100) / R
-        Q = Quantitative value
-        R = UD(t) of one day
-        t = last UD block time
-
-        :param int amount:   Value
-        :param sakia.core.community.Community community: Community instance
-        :return: float
-        """
-        dividend = await self.community.dividend()
-        params = await self.community.parameters()
-        if dividend > 0:
-            return (self.amount * 100) / (float(dividend) / (params['dt'] / 86400))
-        else:
-            return self.amount
-
-    async def differential(self):
-        """
-        Return relative value of amount
-
-        value = (Q * 100) / R
-        Q = Quantitative value
-        R = UD(t) of one day
-        t = UD block time of when the value was created
-
-        :param int amount:   Value
-        :param sakia.core.community.Community community: Community instance
-        :return: float
-        """
-        dividend = await self.community.dividend(self._block_number)
-        params = await self.community.parameters()
-        if dividend > 0:
-            return (self.amount * 100) / (float(dividend) / (params['dt'] / 86400))
-        else:
-            return self.amount
-
-    async def localized(self, units=False, international_system=False):
-        from . import Relative
-        value = await self.value()
-        block = await self.community.get_block()
-        prefix = ""
-        if international_system:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
-
-        if units or international_system:
-            return QCoreApplication.translate("UDDToPast", UDDToPast._REF_STR_) \
-                .format(localized_value,
-                        prefix,
-                        QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(block['medianTime']).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        ),
-                        self.community.short_currency if units else "")
-        else:
-            return localized_value
-
-    async def diff_localized(self, units=False, international_system=False):
-        from . import Relative
-        value = await self.differential()
-        block = await self.community.get_block(self._block_number)
-        prefix = ""
-        if international_system and value != 0:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
-
-        if units or international_system:
-            return QCoreApplication.translate("UDDToPast", UDDToPast._REF_STR_)\
-                .format(localized_value,
-                    prefix,
-                    QLocale.toString(
-                        QLocale(),
-                        QDateTime.fromTime_t(block['medianTime']).date(),
-                        QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                    ),
-                    self.community.short_currency if units else "")
-        else:
-            return localized_value
diff --git a/src/sakia/core/net/__init__.py b/src/sakia/core/net/__init__.py
deleted file mode 100644
index 6ea6364fb9f0854431d8352f088cf781026b1a1d..0000000000000000000000000000000000000000
--- a/src/sakia/core/net/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .node import Node
-from .network import Network
\ No newline at end of file
diff --git a/src/sakia/core/net/api/bma/access.py b/src/sakia/core/net/api/bma/access.py
deleted file mode 100644
index 36d08852067ff17b2a794e29ee21b78dbbd99a15..0000000000000000000000000000000000000000
--- a/src/sakia/core/net/api/bma/access.py
+++ /dev/null
@@ -1,315 +0,0 @@
-from PyQt5.QtCore import QObject, pyqtSlot
-from duniterpy.api import bma
-from duniterpy.api import errors
-from .....tools.exceptions import NoPeerAvailable
-from ..... import __version__
-import logging
-from aiohttp.errors import ClientError, ServerDisconnectedError
-import asyncio
-import random
-from socket import gaierror
-import jsonschema
-from pkg_resources import parse_version
-import copy
-
-
-class BmaAccess(QObject):
-    """
-    This class is used to access BMA API.
-    """
-
-    __saved_requests = [str(bma.blockchain.Block), str(bma.blockchain.Parameters)]
-
-    def __init__(self, data, network):
-        """
-        Constructor of a network
-
-        :param dict data: The data present in this cache
-        :param sakia.core.net.network.Network network: The network used to connect
-        """
-        super().__init__()
-        self._data = data
-        self._rollback_to = None
-        self._pending_requests = {}
-        self._network = network
-
-    @classmethod
-    def create(cls, network):
-        """
-        Initialize a new BMAAccess object with empty data.
-
-        :param sakia.core.net.network.Network network:
-        :return: A new BmaAccess object
-        :rtype: sakia.core.net.api.bma.access.BmaAccess
-        """
-        return cls({}, network)
-
-    @property
-    def data(self):
-        return self._data.copy()
-
-    def load_from_json(self, json_data):
-        """
-        Put data in the cache from json datas.
-
-        :param dict data: The cache in json format
-        """
-        data = {}
-        for entry in json_data['entries']:
-            key = entry['key']
-            cache_key = (key[0], key[1], key[2], key[3], key[4])
-            data[cache_key] = entry['value']
-        self._data = data
-        self._rollback_to = json_data['rollback']
-
-    def jsonify(self):
-        """
-        Get the cache in json format
-
-        :return: The cache as a dict in json format
-        """
-        data = {k: self._data[k] for k in self._data.keys()}
-        entries = []
-        for d in data:
-            entries.append({'key': d,
-                            'value': data[d]})
-        return {'rollback': self._rollback_to,
-                'entries': entries}
-
-    @staticmethod
-    def _gen_cache_key(request, req_args, get_args):
-        return (str(request),
-                str(tuple(frozenset(sorted(req_args.keys())))),
-                str(tuple(frozenset(sorted(req_args.values())))),
-                str(tuple(frozenset(sorted(get_args.keys())))),
-                str(tuple(frozenset(sorted(get_args.values())))))
-
-    def _compare_json(self, first, second):
-        """
-        Compare two json dicts
-        :param first: the first dictionnary
-        :param second: the second dictionnary
-        :return: True if the json dicts are the same
-        :rtype: bool
-        """
-        def ordered(obj):
-            if isinstance(obj, dict):
-                try:
-                    return sorted((k, ordered(v)) for k, v in obj.items())
-                except TypeError:
-                    return obj
-            if isinstance(obj, list):
-                try:
-                    return sorted(ordered(x) for x in obj)
-                except TypeError:
-                    return obj
-            else:
-                return obj
-        return ordered(first) == ordered(second)
-
-    def _get_from_cache(self, request, req_args, get_args):
-        """
-        Get data from the cache
-        :param request: The requested data
-        :param cache_key: The key
-        :rtype: tuple[bool, dict]
-        """
-        cache_key = BmaAccess._gen_cache_key(request, req_args, get_args)
-        if cache_key in self._data.keys():
-            cached_data = self._data[cache_key]
-            need_reload = True
-            # If we detected a rollback
-            # We reload if we don't know if this block changed or not
-            if self._rollback_to:
-                if request is bma.blockchain.Block:
-                    if get_args["number"] >= self._rollback_to:
-                        need_reload = True
-                if request is bma.blockchain.Parameters and self._rollback_to == 0:
-                    need_reload = True
-            elif str(request) in BmaAccess.__saved_requests \
-                or cached_data['metadata']['block_hash'] == self._network.current_blockUID.sha_hash:
-                need_reload = False
-            ret_data = copy.deepcopy(cached_data['value'])
-        else:
-            need_reload = True
-            ret_data = None
-        return need_reload, ret_data
-
-    def _update_rollback(self, request, req_args, get_args, data):
-        """
-        Update the rollback
-
-        If the request is a bma/blockchain/Block, we check if
-        the hash answered is the same as our hash, in which case,
-        we know that the rollback didn't reset blocks before this one
-        :param class request: A bma request class calling for data
-        :param dict req_args: Arguments to pass to the request constructor
-        :param dict get_args: Arguments to pass to the request __get__ method
-        :param dict data: Json data got from the blockchain
-        """
-        if self._rollback_to and request is bma.blockchain.Block:
-            if get_args['number'] >= self._rollback_to:
-                cache_key = BmaAccess._gen_cache_key(request, req_args, get_args)
-                if cache_key in self._data and self._data[cache_key]['value']['hash'] == data['hash']:
-                    self._rollback_to = get_args['number']
-
-    def _update_cache(self, request, req_args, get_args, data):
-        """
-        Update data in cache and returns True if cached data changed
-        :param class request: A bma request class calling for data
-        :param dict req_args: Arguments to pass to the request constructor
-        :param dict get_args: Arguments to pass to the request __get__ method
-        :param dict data: Json data to save in cache
-        :return: True if data changed
-        :rtype: bool
-        """
-        self._update_rollback(request, req_args, get_args, data)
-
-        cache_key = BmaAccess._gen_cache_key(request, req_args, get_args)
-        if cache_key not in self._data:
-            self._data[cache_key] = {'metadata': {},
-                                     'value': {}}
-
-        self._data[cache_key]['metadata']['block_number'] = self._network.current_blockUID.number
-        self._data[cache_key]['metadata']['block_hash'] = self._network.current_blockUID.sha_hash
-        self._data[cache_key]['metadata']['sakia_version'] = __version__
-        if not self._compare_json(self._data[cache_key]['value'], data):
-            self._data[cache_key]['value'] = copy.deepcopy(data)
-            return True
-        return False
-
-    def _invalidate_cache(self, post_request):
-        """
-        Invalidate data depending on posted request
-        :param class post_request: The posted request
-        """
-        invalidated = {bma.wot.Add: bma.wot.Lookup}
-        if post_request in invalidated:
-            invalidated_cache = self._data.copy()
-            for data in self._data:
-                if data[0] == str(invalidated[post_request]):
-                    invalidated_cache.pop(data)
-            self._data = invalidated_cache
-
-    def rollback(self):
-        """
-        When a rollback is detected, we move the rollback cursor to 0
-        """
-        self._rollback_to = 0
-
-    def filter_nodes(self, request, nodes):
-        def compare_versions(node, version):
-            if node.version and node.version != '':
-                try:
-                    return parse_version(node.version) >= parse_version(version)
-                except TypeError:
-                    return False
-            else:
-                return True
-        filters = {
-            bma.ud.History: lambda n: compare_versions(n, "0.11.0"),
-            bma.tx.History: lambda n: compare_versions(n, "0.11.0"),
-            bma.blockchain.Membership: lambda n: compare_versions(n, "0.14")
-        }
-        if request in filters:
-            return [n for n in nodes if filters[request](n)]
-        else:
-            return nodes
-
-    async def future_request(self, request, req_args={}, get_args={}):
-        """
-        Start a request to the network and returns a future.
-
-        :param class request: A bma request class calling for data
-        :param dict req_args: Arguments to pass to the request constructor
-        :param dict get_args: Arguments to pass to the request __get__ method
-        :return: The future data
-        :rtype: dict
-        """
-        data = self._get_from_cache(request, req_args, get_args)
-        need_reload = data[0]
-        json_data = data[1]
-
-        nodes = self.filter_nodes(request, self._network.synced_nodes)
-        if need_reload and len(nodes) > 0:
-            tries = 0
-            while tries < 3:
-                node = random.choice(nodes)
-                conn_handler = node.endpoint.conn_handler()
-                req = request(conn_handler, **req_args)
-                try:
-                    json_data = await req.get(**get_args, session=self._network.session)
-                    self._update_cache(request, req_args, get_args, json_data)
-                    return json_data
-                except (ClientError, ServerDisconnectedError, gaierror, asyncio.TimeoutError, ValueError) as e:
-                    tries += 1
-                except jsonschema.ValidationError as e:
-                    logging.debug(str(e))
-                    tries += 1
-        if len(nodes) == 0 or json_data is None:
-            raise NoPeerAvailable("", len(nodes))
-        return json_data
-
-    async def simple_request(self, request, req_args={}, get_args={}):
-        """
-        Start a request to the network but don't cache its result.
-
-        :param class request: A bma request class calling for data
-        :param dict req_args: Arguments to pass to the request constructor
-        :param dict get_args: Arguments to pass to the request __get__ method
-        :return: The returned data
-        """
-        nodes = self.filter_nodes(request, self._network.synced_nodes)
-        if len(nodes) > 0:
-            tries = 0
-            json_data = None
-            while tries < 3:
-                node = random.choice(nodes)
-                req = request(node.endpoint.conn_handler(), **req_args)
-                try:
-                    json_data = await req.get(**get_args, session=self._network.session)
-                    return json_data
-                except (ClientError, ServerDisconnectedError, gaierror, asyncio.TimeoutError, ValueError) as e:
-                    tries += 1
-                #except jsonschema.ValidationError as e:
-                #    logging.debug(str(e))
-                #    tries += 1
-        if len(nodes) == 0 or not json_data:
-            raise NoPeerAvailable("", len(nodes))
-        return json_data
-
-    async def broadcast(self, request, req_args={}, post_args={}):
-        """
-        Broadcast data to a network.
-        Sends the data to all knew nodes.
-
-        :param request: A duniterpy bma request class
-        :param req_args: Arguments to pass to the request constructor
-        :param post_args: Arguments to pass to the request __post__ method
-        :return: All nodes replies
-        :rtype: tuple of aiohttp replies
-
-        .. note:: If one node accept the requests (returns 200),
-        the broadcast should be considered accepted by the network.
-        """
-        nodes = random.sample(self._network.synced_nodes, 6) \
-            if len(self._network.synced_nodes) > 6 \
-            else self._network.synced_nodes
-        replies = []
-        if len(nodes) > 0:
-            for node in nodes:
-                logging.debug("Trying to connect to : " + node.pubkey)
-                conn_handler = node.endpoint.conn_handler()
-                req = request(conn_handler, **req_args)
-                reply = asyncio.ensure_future(req.post(**post_args, session=self._network.session))
-                replies.append(reply)
-            self._invalidate_cache(request)
-        else:
-            raise NoPeerAvailable("", len(nodes))
-
-        try:
-            result = await asyncio.gather(*replies)
-            return tuple(result)
-        except (ClientError, ServerDisconnectedError, gaierror, asyncio.TimeoutError, ValueError) as e:
-            pass
-        return ()
diff --git a/src/sakia/core/net/network.py b/src/sakia/core/net/network.py
deleted file mode 100644
index 6fa6608e1b387a0a9537ad367b60e64c77a724da..0000000000000000000000000000000000000000
--- a/src/sakia/core/net/network.py
+++ /dev/null
@@ -1,441 +0,0 @@
-"""
-Created on 24 févr. 2015
-
-@author: inso
-"""
-from .node import Node
-from ...tools.exceptions import InvalidNodeCurrency
-from ...tools.decorators import asyncify
-import logging
-import aiohttp
-import time
-import asyncio
-from duniterpy.documents import Peer,  Block, BlockUID, MalformedDocumentError
-from duniterpy.key import VerifyingKey
-from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer
-from collections import Counter
-
-MAX_CONFIRMATIONS = 6
-
-
-class Network(QObject):
-    """
-    A network is managing nodes polling and crawling of a
-    given community.
-    """
-    nodes_changed = pyqtSignal()
-    root_nodes_changed = pyqtSignal()
-    new_block_mined = pyqtSignal(int)
-    blockchain_rollback = pyqtSignal(int)
-
-    def __init__(self, currency, nodes, session):
-        """
-        Constructor of a network
-
-        :param str currency: The currency name of the community
-        :param list nodes: The root nodes of the network
-        """
-        super().__init__()
-        self._root_nodes = nodes
-        self._nodes = []
-        for n in nodes:
-            self.add_node(n)
-        self.currency = currency
-        self._must_crawl = False
-        self._block_found = self.current_blockUID
-        self._timer = QTimer()
-        self._client_session = session
-        self._discovery_stack = []
-
-    @classmethod
-    def create(cls, node):
-        """
-        Create a new network with one knew node
-        Crawls the nodes from the first node to build the
-        community network
-
-        :param node: The first knew node of the network
-        """
-        nodes = [node]
-        network = cls(node.currency, nodes, node.session)
-        return network
-
-    def merge_with_json(self, json_data, file_version):
-        """
-        We merge with knew nodes when we
-        last stopped sakia
-
-        :param dict json_data: Nodes in json format
-        :param NormalizedVersion file_version: The node version
-        """
-        for data in json_data:
-            try:
-                node = Node.from_json(self.currency, data, file_version, self.session)
-                if node.pubkey not in [n.pubkey for n in self.nodes]:
-                    self.add_node(node)
-                    logging.debug("Loading : {:}".format(data['pubkey']))
-                else:
-                    other_node = [n for n in self.nodes if n.pubkey == node.pubkey][0]
-                    other_node._uid = node.uid
-                    other_node._version = node.version
-                    other_node._software = node.software
-                    other_node._peer = node.peer
-                    switch = False
-                    if other_node.block and node.block:
-                        if other_node.block['hash'] != node.block['hash']:
-                            switch = True
-                    else:
-                        switch = True
-                    if switch:
-                        other_node.set_block(node.block)
-                        other_node.last_change = node.last_change
-                        other_node.state = node.state
-            except MalformedDocumentError:
-                logging.debug("Could not load node {0}".format(data))
-
-    @classmethod
-    def from_json(cls, currency, json_data, file_version):
-        """
-        Load a network from a configured community
-
-        :param str currency: The currency name of a community
-        :param dict json_data: A json_data view of a network
-        :param NormalizedVersion file_version: the version of the json file
-        """
-        session = aiohttp.ClientSession()
-        nodes = []
-        for data in json_data:
-            try:
-                node = Node.from_json(currency, data, file_version, session)
-                nodes.append(node)
-            except MalformedDocumentError:
-                logging.debug("Could not load node {0}".format(data))
-        network = cls(currency, nodes, session)
-        return network
-
-    def jsonify(self):
-        """
-        Get the network in json format.
-
-        :return: The network as a dict in json format.
-        """
-        data = []
-        for node in self.nodes:
-            data.append(node.jsonify())
-        return data
-
-    @property
-    def quality(self):
-        """
-        Get a ratio of the synced nodes vs the rest
-        """
-        synced = len(self.synced_nodes)
-        total = len(self.nodes)
-        if total == 0:
-            ratio_synced = 0
-        else:
-            ratio_synced = synced / total
-        return ratio_synced
-
-    def start_coroutines(self):
-        """
-        Start network nodes crawling
-        :return:
-        """
-        asyncio.ensure_future(self.discover_network())
-
-    async def stop_coroutines(self, closing=False):
-        """
-        Stop network nodes crawling.
-        """
-        self._must_crawl = False
-        close_tasks = []
-        logging.debug("Start closing")
-        for node in self.nodes:
-            close_tasks.append(asyncio.ensure_future(node.close_ws()))
-        logging.debug("Closing {0} websockets".format(len(close_tasks)))
-        if len(close_tasks) > 0:
-            await asyncio.wait(close_tasks, timeout=15)
-        if closing:
-            logging.debug("Closing client session")
-            await self._client_session.close()
-        logging.debug("Closed")
-
-    @property
-    def session(self):
-        return self._client_session
-
-    def continue_crawling(self):
-        return self._must_crawl
-
-    @property
-    def synced_nodes(self):
-        """
-        Get nodes which are in the ONLINE state.
-        """
-        return [n for n in self.nodes if n.state == Node.ONLINE]
-
-    @property
-    def online_nodes(self):
-        """
-        Get nodes which are in the ONLINE state.
-        """
-        return [n for n in self.nodes if n.state in (Node.ONLINE, Node.DESYNCED)]
-
-    @property
-    def nodes(self):
-        """
-        Get all knew nodes.
-        """
-        return self._nodes
-
-    @property
-    def root_nodes(self):
-        """
-        Get root nodes.
-        """
-        return self._root_nodes
-
-    @property
-    def current_blockUID(self):
-        """
-        Get the latest block considered valid
-        It is the most frequent last block of every known nodes
-        """
-        blocks = [n.block for n in self.synced_nodes if n.block]
-        if len(blocks) > 0:
-            return BlockUID(blocks[0]['number'], blocks[0]['hash'])
-        else:
-            return BlockUID.empty()
-
-    def _check_nodes_sync(self):
-        """
-        Check nodes sync with the following rules :
-        1 : The block of the majority
-        2 : The more last different issuers
-        3 : The more difficulty
-        4 : The biggest number or timestamp
-        """
-        # rule number 1 : block of the majority
-        blocks = [n.block['hash'] for n in self.online_nodes if n.block]
-        blocks_occurences = Counter(blocks)
-        blocks_by_occurences = {}
-        for key, value in blocks_occurences.items():
-            the_block = [n.block for n in self.online_nodes if n.block and n.block['hash'] == key][0]
-            if value not in blocks_by_occurences:
-                blocks_by_occurences[value] = [the_block]
-            else:
-                blocks_by_occurences[value].append(the_block)
-
-        if len(blocks_by_occurences) == 0:
-            for n in [n for n in self.online_nodes if n.state in (Node.ONLINE, Node.DESYNCED)]:
-                n.state = Node.ONLINE
-            return
-
-        most_present = max(blocks_by_occurences.keys())
-
-        if len(blocks_by_occurences[most_present]) > 1:
-            # rule number 2 : more last different issuers
-            # not possible atm
-            blocks_by_issuers = blocks_by_occurences.copy()
-            most_issuers = max(blocks_by_issuers.keys())
-            if len(blocks_by_issuers[most_issuers]) > 1:
-                # rule number 3 : biggest PowMin
-                blocks_by_powmin = {}
-                for block in blocks_by_issuers[most_issuers]:
-                    if block['powMin'] in blocks_by_powmin:
-                        blocks_by_powmin[block['powMin']].append(block)
-                    else:
-                        blocks_by_powmin[block['powMin']] = [block]
-                bigger_powmin = max(blocks_by_powmin.keys())
-                if len(blocks_by_powmin[bigger_powmin]) > 1:
-                    # rule number 3 : latest timestamp
-                    blocks_by_ts = {}
-                    for block in blocks_by_powmin[bigger_powmin]:
-                        blocks_by_ts[block['time']] = block
-                    latest_ts = max(blocks_by_ts.keys())
-                    synced_block_hash = blocks_by_ts[latest_ts]['hash']
-                else:
-                    synced_block_hash = blocks_by_powmin[bigger_powmin][0]['hash']
-            else:
-                synced_block_hash = blocks_by_issuers[most_issuers][0]['hash']
-        else:
-            synced_block_hash = blocks_by_occurences[most_present][0]['hash']
-
-        for n in self.online_nodes:
-            if n.block and n.block['hash'] == synced_block_hash:
-                n.state = Node.ONLINE
-            else:
-                n.state = Node.DESYNCED
-
-    def _check_nodes_unique(self):
-        """
-        Check that all nodes are unique by them pubkeys
-        """
-        pubkeys = set()
-        unique_nodes = []
-        for n in self.nodes:
-            if n.pubkey not in pubkeys:
-                unique_nodes.append(n)
-                pubkeys.add(n.pubkey)
-
-        self._nodes = unique_nodes
-
-    def confirmations(self, block_number):
-        """
-        Get the number of confirmations of a data
-        :param int block_number: The block number of the data
-        :return: the number of confirmations of a data
-        :rtype: int
-        """
-        if block_number is not None:
-            if block_number > self.current_blockUID.number:
-                raise ValueError("Could not compute confirmations : data block number is after current block")
-            return self.current_blockUID.number - block_number + 1
-        else:
-            return 0
-
-    def add_node(self, node):
-        """
-        Add a nod to the network.
-        """
-        self._nodes.append(node)
-        node.changed.connect(self.handle_change)
-        node.error.connect(self.handle_error)
-        node.identity_changed.connect(self.handle_identity_change)
-        node.neighbour_found.connect(self.handle_new_node)
-        logging.debug("{:} connected".format(node.pubkey[:5]))
-
-    def add_root_node(self, node):
-        """
-        Add a node to the root nodes list
-        """
-        self._root_nodes.append(node)
-        self.root_nodes_changed.emit()
-
-    def remove_root_node(self, index):
-        """
-        Remove a node from the root nodes list
-        """
-        self._root_nodes.pop(index)
-        self.root_nodes_changed.emit()
-
-    def is_root_node(self, node):
-        """
-        Check if this node is in the root nodes
-        """
-        return node in self._root_nodes
-
-    def root_node_index(self, index):
-        """
-        Get the index of a root node from its index
-        in all nodes list
-        """
-        node = self.nodes[index]
-        return self._root_nodes.index(node)
-
-    @asyncify
-    async def refresh_once(self):
-        for node in self._nodes:
-            await asyncio.sleep(1)
-            node.refresh(manual=True)
-
-    async def discover_network(self):
-        """
-        Start crawling which never stops.
-        To stop this crawling, call "stop_crawling" method.
-        """
-        self._must_crawl = True
-        first_loop = True
-        asyncio.ensure_future(self.pop_discovery_stack())
-        while self.continue_crawling():
-            for node in self.nodes:
-                if self.continue_crawling():
-                    node.refresh()
-                    if not first_loop:
-                        await asyncio.sleep(15)
-            first_loop = False
-            await asyncio.sleep(15)
-
-        logging.debug("End of network discovery")
-
-    async def pop_discovery_stack(self):
-        """
-        Handle poping of nodes in discovery stack
-        :return:
-        """
-        while self.continue_crawling():
-            try:
-                await asyncio.sleep(1)
-                peer = self._discovery_stack.pop()
-                pubkeys = [n.pubkey for n in self.nodes]
-                if peer.pubkey not in pubkeys:
-                    logging.debug("New node found : {0}".format(peer.pubkey[:5]))
-                    try:
-                        node = Node.from_peer(self.currency, peer, self.session)
-                        node.refresh(manual=True)
-                        self.add_node(node)
-                        self.nodes_changed.emit()
-                    except InvalidNodeCurrency as e:
-                        logging.debug(str(e))
-                else:
-                    node = [n for n in self.nodes if n.pubkey == peer.pubkey][0]
-                    if node.peer.blockUID.number < peer.blockUID.number:
-                        logging.debug("Update node : {0}".format(peer.pubkey[:5]))
-                        node.peer = peer
-            except IndexError:
-                await asyncio.sleep(2)
-
-    def handle_new_node(self, peer):
-        key = VerifyingKey(peer.pubkey)
-        if key.verify_document(peer):
-            if len(self._discovery_stack) < 1000 \
-                and peer.signatures[0] not in [p.signatures[0] for p in self._discovery_stack]:
-                logging.debug("Stacking new peer document : {0}".format(peer.pubkey))
-                self._discovery_stack.append(peer)
-        else:
-            logging.debug("Wrong document received : {0}".format(peer.signed_raw()))
-
-    @pyqtSlot()
-    def handle_identity_change(self):
-        node = self.sender()
-        self._check_nodes_unique()
-        if node in self._root_nodes:
-            self.root_nodes_changed.emit()
-        self.nodes_changed.emit()
-
-    @pyqtSlot()
-    def handle_error(self):
-        node = self.sender()
-        if node.state in (Node.OFFLINE, Node.CORRUPTED) and \
-                                node.last_change + 3600 < time.time():
-            node.disconnect()
-            self.nodes.remove(node)
-            self.nodes_changed.emit()
-
-    @pyqtSlot()
-    def handle_change(self):
-        node = self.sender()
-
-        if node.state in (Node.ONLINE, Node.DESYNCED):
-            self._check_nodes_sync()
-        self._check_nodes_unique()
-        self.nodes_changed.emit()
-
-        if node.state == Node.ONLINE:
-            logging.debug("{0} -> {1}".format(self._block_found.sha_hash[:10], self.current_blockUID.sha_hash[:10]))
-            if self._block_found.sha_hash != self.current_blockUID.sha_hash:
-                logging.debug("Latest block changed : {0}".format(self.current_blockUID.number))
-                # If new latest block is lower than the previously found one
-                # or if the previously found block is different locally
-                # than in the main chain, we declare a rollback
-                if self._block_found.number and \
-                                self.current_blockUID.number <= self._block_found.number \
-                        or node.main_chain_previous_block and \
-                                        node.main_chain_previous_block['hash'] != self._block_found.sha_hash:
-
-                    self._block_found = self.current_blockUID
-                    self.blockchain_rollback.emit(self.current_blockUID.number)
-                else:
-                    self._block_found = self.current_blockUID
-                    self.new_block_mined.emit(self.current_blockUID.number)
diff --git a/src/sakia/core/net/node.py b/src/sakia/core/net/node.py
deleted file mode 100644
index 9ab058cad3b8db0309af78909203ac1e1e09e4f9..0000000000000000000000000000000000000000
--- a/src/sakia/core/net/node.py
+++ /dev/null
@@ -1,643 +0,0 @@
-"""
-Created on 21 févr. 2015
-
-@author: inso
-"""
-
-from duniterpy.documents.peer import Peer, Endpoint, BMAEndpoint
-from duniterpy.documents import Block, BlockUID, MalformedDocumentError
-from ...tools.exceptions import InvalidNodeCurrency
-from ...tools.decorators import asyncify
-from duniterpy.api import bma, errors
-from duniterpy.api.bma import ConnectionHandler
-
-from aiohttp.errors import WSServerHandshakeError, ClientResponseError
-from aiohttp.errors import ClientError, DisconnectedError
-from asyncio import TimeoutError
-import logging
-import time
-import jsonschema
-import asyncio
-import aiohttp
-from pkg_resources import parse_version
-from socket import gaierror
-
-from PyQt5.QtCore import QObject, pyqtSignal
-
-
-class Node(QObject):
-    """
-    A node is a peer send from the client point of view.
-    This node can have multiple states :
-    - ONLINE : The node is available for requests
-    - OFFLINE: The node is disconnected
-    - DESYNCED : The node is online but is desynced from the network
-    - CORRUPTED : The node is corrupted, some weird behaviour is going on
-    """
-
-    ONLINE = 1
-    OFFLINE = 2
-    DESYNCED = 3
-    CORRUPTED = 4
-
-    changed = pyqtSignal()
-    error = pyqtSignal()
-    identity_changed = pyqtSignal()
-    neighbour_found = pyqtSignal(Peer)
-
-    def __init__(self, peer, uid, pubkey, block,
-                 state, last_change, last_merkle,
-                 software, version, fork_window,
-                 session):
-        """
-        Constructor
-        """
-        super().__init__()
-        self._peer = peer
-        self._uid = uid
-        self._pubkey = pubkey
-        self._block = block
-        self.main_chain_previous_block = None
-        self._state = state
-        self._neighbours = []
-        self._last_change = last_change
-        self._last_merkle = last_merkle
-        self._software = software
-        self._version = version
-        self._fork_window = fork_window
-        self._refresh_counter = 19
-        self._ws_tasks = {'block': None,
-                    'peer': None}
-        self._connected = {'block': False,
-                    'peer': False}
-        self._session = session
-
-    def __del__(self):
-        for ws in self._ws_tasks.values():
-            if ws:
-                ws.cancel()
-
-    @classmethod
-    async def from_address(cls, currency, address, port, session):
-        """
-        Factory method to get a node from a given address
-
-        :param str currency: The node currency. None if we don't know\
-         the currency it should have, for example if its the first one we add
-        :param str address: The node address
-        :param int port: The node port
-        :return: A new node
-        :rtype: sakia.core.net.Node
-        """
-        peer_data = await bma.network.Peering(ConnectionHandler(address, port)).get(session)
-
-        peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'],
-                                                  peer_data['signature']))
-
-        if currency is not None:
-            if peer.currency != currency:
-                raise InvalidNodeCurrency(peer.currency, currency)
-
-        node = cls(peer,
-                   "", peer.pubkey, None, Node.ONLINE, time.time(),
-                   {'root': "", 'leaves': []}, "", "", 0, session)
-        logging.debug("Node from address : {:}".format(str(node)))
-        return node
-
-    @classmethod
-    def from_peer(cls, currency, peer, session):
-        """
-        Factory method to get a node from a peer document.
-
-        :param str currency: The node currency. None if we don't know\
-         the currency it should have, for example if its the first one we add
-        :param peer: The peer document
-        :return: A new node
-        :rtype: sakia.core.net.Node
-        """
-        if currency is not None:
-            if peer.currency != currency:
-                raise InvalidNodeCurrency(peer.currency, currency)
-
-        node = cls(peer, "", peer.pubkey, None,
-                   Node.OFFLINE, time.time(),
-                   {'root': "", 'leaves': []},
-                   "", "", 0, session)
-        logging.debug("Node from peer : {:}".format(str(node)))
-        return node
-
-    @classmethod
-    def from_json(cls, currency, data, file_version, session):
-        """
-        Loads a node from json data
-
-        :param str currency: the currency of the community
-        :param dict data: the json data of the node
-        :param NormalizedVersion file_version: the version of the file
-        :return: A new node
-        :rtype: Node
-        """
-        endpoints = []
-        uid = ""
-        pubkey = ""
-        software = ""
-        version = ""
-        fork_window = 0
-        block = None
-        last_change = time.time()
-        state = Node.OFFLINE
-        if 'uid' in data:
-            uid = data['uid']
-
-        if 'pubkey' in data:
-            pubkey = data['pubkey']
-
-        if 'last_change' in data:
-            last_change = data['last_change']
-
-        if 'block' in data:
-            block = data['block']
-
-        if 'state' in data:
-            state = data['state']
-
-        if 'software' in data:
-            software = data['software']
-
-        if 'version' in data:
-            version = data['version']
-
-        if 'fork_window' in data:
-            fork_window = data['fork_window']
-
-        if parse_version("0.11") <= file_version < parse_version("0.12dev1") :
-            for endpoint_data in data['endpoints']:
-                endpoints.append(Endpoint.from_inline(endpoint_data))
-
-            if currency in data:
-                currency = data['currency']
-
-            peer = Peer(2, currency, pubkey, BlockUID(0, Block.Empty_Hash), endpoints, "SOMEFAKESIGNATURE")
-        else:
-            peer = Peer.from_signed_raw(data['peer'])
-
-        node = cls(peer, uid, pubkey, block,
-                   state, last_change,
-                   {'root': "", 'leaves': []},
-                   software, version, fork_window, session)
-
-        logging.debug("Node from json : {:}".format(str(node)))
-        return node
-
-    def jsonify_root_node(self):
-        logging.debug("Saving root node : {:}".format(str(self)))
-        data = {'pubkey': self._pubkey,
-                'uid': self._uid,
-                'peer': self._peer.signed_raw()}
-        return data
-
-    def jsonify(self):
-        logging.debug("Saving node : {:}".format(str(self)))
-        data = {'pubkey': self._pubkey,
-                'uid': self._uid,
-                'peer': self._peer.signed_raw(),
-                'state': self._state,
-                'last_change': self._last_change,
-                'block': self.block,
-                'software': self._software,
-                'version': self._version,
-                'fork_window': self._fork_window
-                }
-        return data
-
-    async def close_ws(self):
-        for ws in self._ws_tasks.values():
-            if ws:
-                ws.cancel()
-                await asyncio.sleep(0)
-        closed = False
-        while not closed:
-            for ws in self._ws_tasks.values():
-                if ws:
-                    closed = False
-                    break
-            else:
-                closed = True
-            await asyncio.sleep(0)
-        await asyncio.sleep(0)
-
-    @property
-    def session(self):
-        return self._session
-
-    @property
-    def pubkey(self):
-        return self._pubkey
-
-    @property
-    def endpoint(self) -> BMAEndpoint:
-        return next((e for e in self._peer.endpoints if type(e) is BMAEndpoint))
-
-    @property
-    def block(self):
-        return self._block
-
-    def set_block(self, block):
-        self._block = block
-
-    @property
-    def state(self):
-        return self._state
-
-    @property
-    def currency(self):
-        return self._peer.currency
-
-    @property
-    def neighbours(self):
-        return self._neighbours
-
-    @property
-    def uid(self):
-        return self._uid
-
-    @property
-    def last_change(self):
-        return self._last_change
-
-    @property
-    def software(self):
-        return self._software
-
-    @property
-    def peer(self):
-        return self._peer
-
-    @peer.setter
-    def peer(self, new_peer):
-        if self._peer != new_peer:
-            self._peer = new_peer
-            self.changed.emit()
-
-    @software.setter
-    def software(self, new_soft):
-        if self._software != new_soft:
-            self._software = new_soft
-            self.changed.emit()
-
-    @property
-    def version(self):
-        return self._version
-
-    @version.setter
-    def version(self, new_version):
-        if self._version != new_version:
-            self._version = new_version
-            self.changed.emit()
-
-    @last_change.setter
-    def last_change(self, val):
-        #logging.debug("{:} | Changed state : {:}".format(self.pubkey[:5],
-        #                                                val))
-        self._last_change = val
-
-    @state.setter
-    def state(self, new_state):
-        #logging.debug("{:} | Last state : {:} / new state : {:}".format(self.pubkey[:5],
-        #                                                               self.state, new_state))
-
-        if self._state != new_state:
-            self.last_change = time.time()
-            self._state = new_state
-            self.changed.emit()
-        if new_state in (Node.OFFLINE, Node.ONLINE):
-            self.error.emit()
-
-    @property
-    def fork_window(self):
-        return self._fork_window
-
-    @fork_window.setter
-    def fork_window(self, new_fork_window):
-        if self._fork_window != new_fork_window:
-            self._fork_window = new_fork_window
-            self.changed.emit()
-
-    def refresh(self, manual=False):
-        """
-        Refresh all data of this node
-        :param bool manual: True if the refresh was manually initiated
-        """
-        if not self._ws_tasks['block']:
-            self._ws_tasks['block'] = asyncio.ensure_future(self.connect_current_block())
-
-        if not self._ws_tasks['peer']:
-            self._ws_tasks['peer'] = asyncio.ensure_future(self.connect_peers())
-
-        if manual:
-            asyncio.ensure_future(self.request_peers())
-
-        if self._refresh_counter % 20 == 0 or manual:
-            self.refresh_informations()
-            self.refresh_uid()
-            self.refresh_summary()
-            self._refresh_counter = self._refresh_counter if manual else 1
-        else:
-            self._refresh_counter += 1
-
-    async def connect_current_block(self):
-        """
-        Connects to the websocket entry point of the node
-        If the connection fails, it tries the fallback mode on HTTP GET
-        """
-        if not self._connected['block']:
-            try:
-                conn_handler = self.endpoint.conn_handler()
-                block_websocket = bma.ws.Block(conn_handler)
-                ws_connection = block_websocket.connect(self._session)
-                async with ws_connection as ws:
-                    self._connected['block'] = True
-                    logging.debug("Connected successfully to block ws : {0}".format(self.pubkey[:5]))
-                    async for msg in ws:
-                        if msg.tp == aiohttp.MsgType.text:
-                            logging.debug("Received a block : {0}".format(self.pubkey[:5]))
-                            block_data = block_websocket.parse_text(msg.data)
-                            await self.refresh_block(block_data)
-                        elif msg.tp == aiohttp.MsgType.closed:
-                            break
-                        elif msg.tp == aiohttp.MsgType.error:
-                            break
-            except (WSServerHandshakeError, ClientResponseError, ValueError) as e:
-                logging.debug("Websocket block {0} : {1} - {2}".format(type(e).__name__, str(e), self.pubkey[:5]))
-                await self.request_current_block()
-            except (ClientError, gaierror, TimeoutError, DisconnectedError) as e:
-                logging.debug("{0} : {1}".format(str(e), self.pubkey[:5]))
-                self.state = Node.OFFLINE
-            except jsonschema.ValidationError as e:
-                logging.debug(str(e))
-                logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-                self.state = Node.CORRUPTED
-            finally:
-                self._connected['block'] = False
-                self._ws_tasks['block'] = None
-
-    async def request_current_block(self):
-        """
-        Request a node on the HTTP GET interface
-        If an error occurs, the node is considered offline
-        """
-        try:
-            conn_handler = self.endpoint.conn_handler()
-            block_data = await bma.blockchain.Current(conn_handler).get(self._session)
-            await self.refresh_block(block_data)
-        except errors.DuniterError as e:
-            if e.ucode == errors.BLOCK_NOT_FOUND:
-                self.main_chain_previous_block = None
-                self.set_block(None)
-            else:
-                self.state = Node.OFFLINE
-            logging.debug("Error in block reply :  {0}".format(self.pubkey[:5]))
-            logging.debug(str(e))
-            self.changed.emit()
-        except (ClientError, gaierror, TimeoutError, DisconnectedError, ValueError) as e:
-            logging.debug("{0} : {1}".format(str(e), self.pubkey[:5]))
-            self.state = Node.OFFLINE
-        except jsonschema.ValidationError as e:
-            logging.debug(str(e))
-            logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-            self.state = Node.CORRUPTED
-
-    async def refresh_block(self, block_data):
-        """
-        Refresh the blocks of this node
-        :param dict block_data: The block data in json format
-        """
-        conn_handler = self.endpoint.conn_handler()
-
-        logging.debug("Requesting {0}".format(conn_handler))
-        block_hash = block_data['hash']
-        self.state = Node.ONLINE
-
-        if not self.block or block_hash != self.block['hash']:
-            try:
-                if self.block:
-                    self.main_chain_previous_block = await bma.blockchain.Block(conn_handler,
-                                                                                 self.block['number']).get(self._session)
-            except errors.DuniterError as e:
-                if e.ucode == errors.BLOCK_NOT_FOUND:
-                    self.main_chain_previous_block = None
-                else:
-                    self.state = Node.OFFLINE
-                logging.debug("Error in previous block reply :  {0}".format(self.pubkey[:5]))
-                logging.debug(str(e))
-                self.changed.emit()
-            except (ClientError, gaierror, TimeoutError, DisconnectedError, ValueError) as e:
-                logging.debug("{0} : {1}".format(str(e), self.pubkey[:5]))
-                self.state = Node.OFFLINE
-            except jsonschema.ValidationError as e:
-                logging.debug(str(e))
-                logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-                self.state = Node.CORRUPTED
-            finally:
-                self.set_block(block_data)
-                logging.debug("Changed block {0} -> {1}".format(self.block['number'],
-                                                                block_data['number']))
-                self.changed.emit()
-
-    @asyncify
-    async def refresh_informations(self):
-        """
-        Refresh basic information (pubkey and currency)
-        """
-        conn_handler = self.endpoint.conn_handler()
-
-        try:
-            peering_data = await bma.network.Peering(conn_handler).get(self._session)
-            node_pubkey = peering_data["pubkey"]
-            node_currency = peering_data["currency"]
-            self.state = Node.ONLINE
-
-            if peering_data['raw'] != self.peer.raw():
-                peer = Peer.from_signed_raw("{0}{1}\n".format(peering_data['raw'], peering_data['signature']))
-                if peer.blockUID.number > peer.blockUID.number:
-                    self.peer = Peer.from_signed_raw("{0}{1}\n".format(peering_data['raw'], peering_data['signature']))
-
-            if node_pubkey != self.pubkey:
-                self._pubkey = node_pubkey
-                self.identity_changed.emit()
-
-            if node_currency != self.currency:
-                self.state = Node.CORRUPTED
-                logging.debug("Change : new state corrupted")
-                self.changed.emit()
-
-        except errors.DuniterError as e:
-            if e.ucode == errors.PEER_NOT_FOUND:
-                logging.debug("Error in peering reply : {0}".format(str(e)))
-                self.state = Node.OFFLINE
-                self.changed.emit()
-        except (ClientError, gaierror, TimeoutError, DisconnectedError, ValueError) as e:
-            logging.debug("{0} : {1}".format(type(e).__name__, self.pubkey[:5]))
-            self.state = Node.OFFLINE
-        except (MalformedDocumentError, jsonschema.ValidationError) as e:
-            logging.debug(str(e))
-            logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-            self.state = Node.CORRUPTED
-
-    @asyncify
-    async def refresh_summary(self):
-        """
-        Refresh the summary of this node
-        """
-        conn_handler = self.endpoint.conn_handler()
-
-        try:
-            summary_data = await bma.node.Summary(conn_handler).get(self._session)
-            self.software = summary_data["duniter"]["software"]
-            self.version = summary_data["duniter"]["version"]
-            self.state = Node.ONLINE
-            if "forkWindowSize" in summary_data["duniter"]:
-                self.fork_window = summary_data["duniter"]["forkWindowSize"]
-            else:
-                self.fork_window = 0
-        except (ClientError, gaierror, TimeoutError, DisconnectedError, ValueError) as e:
-            logging.debug("{0} : {1}".format(type(e).__name__, self.pubkey[:5]))
-            self.state = Node.OFFLINE
-        except jsonschema.ValidationError as e:
-            logging.debug(str(e))
-            logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-            self.state = Node.CORRUPTED
-
-    @asyncify
-    async def refresh_uid(self):
-        """
-        Refresh the node UID
-        """
-        conn_handler = self.endpoint.conn_handler()
-        try:
-            data = await bma.wot.Lookup(conn_handler, self.pubkey).get(self._session)
-            self.state = Node.ONLINE
-            timestamp = BlockUID.empty()
-            uid = ""
-            for result in data['results']:
-                if result["pubkey"] == self.pubkey:
-                    uids = result['uids']
-                    for uid in uids:
-                        if BlockUID.from_str(uid["meta"]["timestamp"]) >= timestamp:
-                            timestamp = uid["meta"]["timestamp"]
-                            uid = uid["uid"]
-            if self._uid != uid:
-                self._uid = uid
-                self.identity_changed.emit()
-        except errors.DuniterError as e:
-            if e.ucode == errors.NO_MATCHING_IDENTITY:
-                logging.debug("UID not found : {0}".format(self.pubkey[:5]))
-            else:
-                logging.debug("error in uid reply : {0}".format(self.pubkey[:5]))
-                self.state = Node.OFFLINE
-                self.identity_changed.emit()
-        except (ClientError, gaierror, TimeoutError, DisconnectedError, ValueError) as e:
-            logging.debug("{0} : {1}".format(type(e).__name__, self.pubkey[:5]))
-            self.state = Node.OFFLINE
-        except jsonschema.ValidationError as e:
-            logging.debug(str(e))
-            logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-            self.state = Node.CORRUPTED
-
-    async def connect_peers(self):
-        """
-        Connects to the peer websocket entry point
-        If the connection fails, it tries the fallback mode on HTTP GET
-        """
-        if not self._connected['peer']:
-            try:
-                conn_handler = self.endpoint.conn_handler()
-                peer_websocket = bma.ws.Peer(conn_handler)
-                ws_connection = peer_websocket.connect(self._session)
-                async with ws_connection as ws:
-                    self._connected['peer'] = True
-                    logging.debug("Connected successfully to peer ws : {0}".format(self.pubkey[:5]))
-                    async for msg in ws:
-                        if msg.tp == aiohttp.MsgType.text:
-                            logging.debug("Received a peer : {0}".format(self.pubkey[:5]))
-                            peer_data = peer_websocket.parse_text(msg.data)
-                            self.refresh_peer_data(peer_data)
-                        elif msg.tp == aiohttp.MsgType.closed:
-                            break
-                        elif msg.tp == aiohttp.MsgType.error:
-                            break
-            except (WSServerHandshakeError, ClientResponseError, ValueError) as e:
-                logging.debug("Websocket peer {0} : {1} - {2}".format(type(e).__name__, str(e), self.pubkey[:5]))
-                await self.request_peers()
-            except (ClientError, gaierror, TimeoutError, DisconnectedError) as e:
-                logging.debug("{0} : {1}".format(str(e), self.pubkey[:5]))
-                self.state = Node.OFFLINE
-            except jsonschema.ValidationError as e:
-                logging.debug(str(e))
-                logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-                self.state = Node.CORRUPTED
-            finally:
-                self._connected['peer'] = False
-                self._ws_tasks['peer'] = None
-
-    async def request_peers(self):
-        """
-        Refresh the list of peers knew by this node
-        """
-        conn_handler = self.endpoint.conn_handler()
-
-        try:
-            peers_data = await bma.network.peering.Peers(conn_handler).get(leaves='true', session=self._session)
-            self.state = Node.ONLINE
-            if peers_data['root'] != self._last_merkle['root']:
-                leaves = [leaf for leaf in peers_data['leaves']
-                          if leaf not in self._last_merkle['leaves']]
-                for leaf_hash in leaves:
-                    try:
-                        leaf_data = await bma.network.peering.Peers(conn_handler).get(leaf=leaf_hash,
-                                                                                      session=self._session)
-                        self.refresh_peer_data(leaf_data['leaf']['value'])
-                    except (AttributeError, ValueError, errors.DuniterError) as e:
-                        logging.debug("{pubkey} : Incorrect peer data in {leaf}".format(pubkey=self.pubkey[:5],
-                                                                                        leaf=leaf_hash))
-                        self.state = Node.OFFLINE
-                        self.changed.emit()
-                    except (ClientError, gaierror, TimeoutError, DisconnectedError, ValueError) as e:
-                        logging.debug("{0} : {1}".format(type(e).__name__, self.pubkey[:5]))
-                        self.state = Node.OFFLINE
-                    except jsonschema.ValidationError as e:
-                        logging.debug(str(e))
-                        logging.debug("Validation error : {0}".format(self.pubkey[:5]))
-                        self.state = Node.CORRUPTED
-                self._last_merkle = {'root' : peers_data['root'],
-                                     'leaves': peers_data['leaves']}
-        except errors.DuniterError as e:
-            if e.ucode == errors.PEER_NOT_FOUND:
-                logging.debug("Error in peers reply")
-                self.state = Node.OFFLINE
-                self.changed.emit()
-        except (ClientError, gaierror, TimeoutError, DisconnectedError) as e:
-            logging.debug("{0} : {1}".format(type(e).__name__, self.pubkey))
-            self.state = Node.OFFLINE
-        except jsonschema.ValidationError as e:
-            logging.debug(str(e))
-            logging.debug("Validation error : {0}".format(self.pubkey))
-            self.state = Node.CORRUPTED
-
-    def refresh_peer_data(self, peer_data):
-        if "raw" in peer_data:
-            try:
-                str_doc = "{0}{1}\n".format(peer_data['raw'],
-                                            peer_data['signature'])
-                peer_doc = Peer.from_signed_raw(str_doc)
-                self.neighbour_found.emit(peer_doc)
-            except MalformedDocumentError as e:
-                logging.debug(str(e))
-        else:
-            logging.debug("Incorrect leaf reply")
-
-    def __str__(self):
-        return ','.join([str(self.pubkey), str(self.endpoint.server),
-                         str(self.endpoint.ipv4), str(self.endpoint.port),
-                         str(self.block['number'] if self.block else "None"),
-                         str(self.currency), str(self.state), str(self.neighbours)])
diff --git a/src/sakia/core/registry/__init__.py b/src/sakia/core/registry/__init__.py
deleted file mode 100644
index 5180c368bb56a87483d542c19bb83c71133d1a94..0000000000000000000000000000000000000000
--- a/src/sakia/core/registry/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .identities import IdentitiesRegistry
-from .identity import Identity, LocalState, BlockchainState
\ No newline at end of file
diff --git a/src/sakia/core/registry/identities.py b/src/sakia/core/registry/identities.py
deleted file mode 100644
index cd0bb3623a8e843c7bf038f0fe4f15c306b54dd9..0000000000000000000000000000000000000000
--- a/src/sakia/core/registry/identities.py
+++ /dev/null
@@ -1,163 +0,0 @@
-from duniterpy.api import bma, errors
-from duniterpy.documents import BlockUID
-from .identity import Identity, LocalState, BlockchainState
-from pkg_resources import parse_version
-import asyncio
-from aiohttp.errors import ClientError
-from ...tools.exceptions import NoPeerAvailable
-
-
-class IdentitiesRegistry:
-    """
-    Core class to handle identities lookup
-    """
-    def __init__(self, instances={}):
-        """
-        Initializer of the IdentitiesRegistry
-
-        :param dict instances: A dictionary containing identities based on communities
-        :return: An IdentitiesRegistry object
-        :rtype: IdentitiesRegistry
-        """
-        self._instances = instances
-
-    def load_json(self, json_data):
-        """
-        Load json data
-
-        :param dict json_data: The identities in json format
-        """
-        instances = {}
-        version = parse_version(json_data['version'])
-        for currency in json_data['registry']:
-            instances[currency] = {}
-            for person_data in json_data['registry'][currency]:
-                pubkey = person_data['pubkey']
-                if pubkey not in instances:
-                    person = Identity.from_json(person_data, version)
-                    instances[currency][person.pubkey] = person
-        self._instances = instances
-
-    def jsonify(self):
-        communities_json = {}
-        for currency in self._instances:
-            identities_json = []
-            for identity in self._instances[currency].values():
-                identities_json.append(identity.jsonify())
-            communities_json[currency] = identities_json
-        return {'registry': communities_json}
-
-    def _identities(self, community):
-        """
-        If the registry do not have data for this community
-        Create a new dict and return it
-        :param  sakia.core.Community community: the community
-        :return: The identities of the community
-        :rtype: dict
-        """
-        try:
-            return self._instances[community.currency]
-        except KeyError:
-            self._instances[community.currency] = {}
-            return self._identities(community)
-
-    async def _find_by_lookup(self, pubkey, community):
-        identity = self._identities(community)[pubkey]
-        lookup_tries = 0
-        while lookup_tries < 3:
-            try:
-                data = await community.bma_access.simple_request(bma.wot.Lookup,
-                                                            req_args={'search': pubkey})
-                timestamp = BlockUID.empty()
-                for result in data['results']:
-                    if result["pubkey"] == identity.pubkey:
-                        uids = result['uids']
-                        for uid_data in uids:
-                            if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
-                                identity.sigdate = BlockUID.from_str(uid_data["meta"]["timestamp"])
-                                identity.uid = uid_data["uid"]
-                                identity.blockchain_state = BlockchainState.BUFFERED
-                                identity.local_state = LocalState.PARTIAL
-                                timestamp = identity.sigdate
-                return identity
-            except errors.DuniterError as e:
-                lookup_tries += 1
-            except asyncio.TimeoutError:
-                lookup_tries += 1
-            except ClientError:
-                lookup_tries += 1
-            except NoPeerAvailable:
-                return identity
-        return identity
-
-    async def future_find(self, pubkey, community):
-        """
-
-        :param pubkey: The pubkey we look for
-        :param community: The community where we look for the identity
-        :return: The identity found
-        :rtype: sakia.core.registry.Identity
-        """
-        if pubkey in self._identities(community):
-            identity = self._identities(community)[pubkey]
-        else:
-            identity = Identity.empty(pubkey)
-            self._identities(community)[pubkey] = identity
-            tries = 0
-            while tries < 3 and identity.local_state == LocalState.NOT_FOUND:
-                try:
-                    data = await community.bma_access.simple_request(bma.blockchain.Membership,
-                                                                          req_args={'search': pubkey})
-                    identity.uid = data['uid']
-                    identity.sigdate = BlockUID.from_str(data['sigDate'])
-                    identity.local_state = LocalState.PARTIAL
-                    identity.blockchain_state = BlockchainState.VALIDATED
-                except errors.DuniterError as e:
-                    if errors.NO_MEMBER_MATCHING_PUB_OR_UID:
-                        identity = await self._find_by_lookup(pubkey, community)
-                        return identity
-                    else:
-                        tries += 1
-                except asyncio.TimeoutError:
-                    tries += 1
-                except ClientError:
-                    tries += 1
-                except NoPeerAvailable:
-                    return identity
-        return identity
-
-    def from_handled_data(self, uid, pubkey, sigdate, blockchain_state, community):
-        """
-        Get a person from a metadata dict.
-        A metadata dict has a 'text' key corresponding to the person uid,
-        and a 'id' key corresponding to the person pubkey.
-
-        :param str uid: The person uid, also known as its uid on the network
-        :param str pubkey: The person pubkey
-        :param BlockUID sig_date: The date of signature of the self certification
-        :param LocalState local_state: The local status of the identity
-        :param sakia.core.Community community: The community from which we found data
-        :rtype: sakia.core.registry.Identity
-        """
-        identities = self._identities(community)
-        if pubkey in identities:
-            if identities[pubkey].blockchain_state == BlockchainState.NOT_FOUND:
-                identities[pubkey].blockchain_state = blockchain_state
-            elif identities[pubkey].blockchain_state != BlockchainState.VALIDATED \
-                    and blockchain_state == BlockchainState.VALIDATED:
-                identities[pubkey].blockchain_state = blockchain_state
-
-            if identities[pubkey].uid != uid:
-                identities[pubkey].uid = uid
-
-            if sigdate and identities[pubkey].sigdate != sigdate:
-                identities[pubkey].sigdate = sigdate
-
-            if identities[pubkey].local_state == LocalState.NOT_FOUND:
-                identities[pubkey].local_state = LocalState.COMPLETED
-
-            return identities[pubkey]
-        else:
-            identity = Identity.from_handled_data(uid, pubkey, sigdate, blockchain_state)
-            self._identities(community)[pubkey] = identity
-            return identity
diff --git a/src/sakia/core/registry/identity.py b/src/sakia/core/registry/identity.py
deleted file mode 100644
index fcc16720a99c12e2f79b5aea89fc323a79452c91..0000000000000000000000000000000000000000
--- a/src/sakia/core/registry/identity.py
+++ /dev/null
@@ -1,569 +0,0 @@
-"""
-Created on 11 févr. 2014
-
-@author: inso
-"""
-
-import logging
-import time
-from enum import Enum
-from pkg_resources import parse_version
-
-from duniterpy.documents import BlockUID, SelfCertification, MalformedDocumentError
-from duniterpy.api import bma, errors
-from duniterpy.api.bma import PROTOCOL_VERSION
-
-from ...tools.exceptions import Error, NoPeerAvailable,\
-                                        MembershipNotFoundError, LookupFailureError
-from PyQt5.QtCore import QObject, pyqtSignal
-
-
-class LocalState(Enum):
-    """
-    The local state describes how the identity exists locally :
-    COMPLETED means all its related datas (certifiers, certified...)
-    were succefully downloaded
-    PARTIAL means not all data are present locally
-    NOT_FOUND means it could not be found anywhere
-    """
-    NOT_FOUND = 0
-    PARTIAL = 1
-    COMPLETED = 2
-
-
-class BlockchainState(Enum):
-    """
-    The blockchain state describes how the identity
-    was found :
-    VALIDATED means it was found in the blockchain
-    BUFFERED means it was found via a lookup but not in the
-    blockchain
-    NOT_FOUND means it could not be found anywhere
-    """
-    NOT_FOUND = 0
-    BUFFERED = 1
-    VALIDATED = 2
-
-
-class Identity(QObject):
-    """
-    A person with a uid and a pubkey
-    """
-    def __init__(self, uid, pubkey, sigdate, local_state, blockchain_state):
-        """
-        Initializing a person object.
-
-        :param str uid: The identity uid, also known as its uid on the network
-        :param str pubkey: The identity pubkey
-        :parma BlockUID sig_date: The date of signature of the self certification
-        :param LocalState local_state: The local status of the identity
-        :param BlockchainState blockchain_state: The blockchain status of the identity
-        """
-        if sigdate:
-            assert type(sigdate) is BlockUID
-        super().__init__()
-        self.uid = uid
-        self.pubkey = pubkey
-        self._sigdate = sigdate
-        self.local_state = local_state
-        self.blockchain_state = blockchain_state
-
-    @classmethod
-    def empty(cls, pubkey):
-        return cls("", pubkey, None, LocalState.NOT_FOUND, BlockchainState.NOT_FOUND)
-
-    @classmethod
-    def from_handled_data(cls, uid, pubkey, sigdate, blockchain_state):
-        return cls(uid, pubkey, sigdate, LocalState.COMPLETED, blockchain_state)
-
-    @classmethod
-    def from_json(cls, json_data, version):
-        """
-        Create a person from json data
-
-        :param dict json_data: The person as a dict in json format
-        :return: A new person if pubkey wasn't known, else a new person instance.
-        """
-        pubkey = json_data['pubkey']
-        uid = json_data['uid']
-        local_state = LocalState[json_data['local_state']]
-        blockchain_state = BlockchainState[json_data['blockchain_state']]
-        if version >= parse_version("0.20.0dev0") and json_data['sigdate']:
-            sigdate = BlockUID.from_str(json_data['sigdate'])
-        else:
-            sigdate = BlockUID.empty()
-
-        return cls(uid, pubkey, sigdate, local_state, blockchain_state)
-
-    @property
-    def sigdate(self):
-        return self._sigdate
-
-    @sigdate.setter
-    def sigdate(self, sigdate):
-        assert type(sigdate) is BlockUID
-        self._sigdate = sigdate
-
-    async def selfcert(self, community):
-        """
-        Get the identity self certification.
-        This request is not cached in the person object.
-
-        :param sakia.core.community.Community community: The community target to request the self certification
-        :return: A SelfCertification duniterpy object
-        :rtype: duniterpy.documents.certification.SelfCertification
-        """
-        try:
-            timestamp = BlockUID.empty()
-            lookup_data = await community.bma_access.future_request(bma.wot.Lookup,
-                                                                         req_args={'search': self.pubkey})
-
-            for result in lookup_data['results']:
-                if result["pubkey"] == self.pubkey:
-                    uids = result['uids']
-                    for uid_data in uids:
-                        # If the sigDate was written in the blockchain
-                        if self._sigdate and BlockUID.from_str(uid_data["meta"]["timestamp"]) == self._sigdate:
-                            timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"])
-                            uid = uid_data["uid"]
-                            signature = uid_data["self"]
-                        # Else we choose the latest one found
-                        elif BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
-                            timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"])
-                            uid = uid_data["uid"]
-                            signature = uid_data["self"]
-
-                    if not self.sigdate:
-                        self.sigdate = timestamp
-
-                    return SelfCertification(PROTOCOL_VERSION,
-                                             community.currency,
-                                             self.pubkey,
-                                             uid,
-                                             timestamp,
-                                             signature)
-        except errors.DuniterError as e:
-            if e.ucode == errors.NO_MATCHING_IDENTITY:
-                raise LookupFailureError(self.pubkey, community)
-        except MalformedDocumentError:
-            raise LookupFailureError(self.pubkey, community)
-        except NoPeerAvailable:
-            logging.debug("No peer available")
-
-    async def get_join_date(self, community):
-        """
-        Get the person join date.
-        This request is not cached in the person object.
-
-        :param sakia.core.community.Community community: The community target to request the join date
-        :return: A datetime object
-        """
-        try:
-            search = await community.bma_access.future_request(bma.blockchain.Membership,
-                                                                    {'search': self.pubkey})
-            if len(search['memberships']) > 0:
-                membership_data = search['memberships'][0]
-                block = await community.bma_access.future_request(bma.blockchain.Block,
-                                req_args={'number': membership_data['blockNumber']})
-                return block['medianTime']
-        except errors.DuniterError as e:
-            if e.ucode == errors.NO_MEMBER_MATCHING_PUB_OR_UID:
-                raise MembershipNotFoundError(self.pubkey, community.name)
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-            raise MembershipNotFoundError(self.pubkey, community.name)
-
-    async def get_expiration_date(self, community):
-        try:
-            membership = await self.membership(community)
-            join_block_number = membership['blockNumber']
-            try:
-                join_block = await community.bma_access.future_request(bma.blockchain.Block,
-                                req_args={'number': join_block_number})
-
-                parameters = await community.bma_access.future_request(bma.blockchain.Parameters)
-                join_date = join_block['medianTime']
-                expiration_date = join_date + parameters['sigValidity']
-            except NoPeerAvailable:
-                expiration_date = None
-            except errors.DuniterError as e:
-                logging.debug("Expiration date not found")
-                expiration_date = None
-        except MembershipNotFoundError:
-            expiration_date = None
-        return expiration_date
-
-
-#TODO: Manage 'OUT' memberships ? Maybe ?
-    async def membership(self, community):
-        """
-        Get the person last membership document.
-
-        :param sakia.core.community.Community community: The community target to request the join date
-        :return: The membership data in BMA json format
-        :rtype: dict
-        """
-        try:
-            search = await community.bma_access.future_request(bma.blockchain.Membership,
-                                           {'search': self.pubkey})
-            block_number = -1
-            membership_data = None
-
-            for ms in search['memberships']:
-                if ms['blockNumber'] > block_number:
-                    block_number = ms['blockNumber']
-                    if 'type' in ms:
-                        if ms['type'] is 'IN':
-                            membership_data = ms
-                    else:
-                        membership_data = ms
-            if membership_data:
-                return membership_data
-            else:
-                raise MembershipNotFoundError(self.pubkey, community.name)
-
-        except errors.DuniterError as e:
-            if e.ucode == errors.NO_MEMBER_MATCHING_PUB_OR_UID:
-                raise MembershipNotFoundError(self.pubkey, community.name)
-            else:
-                logging.debug(str(e))
-                raise MembershipNotFoundError(self.pubkey, community.name)
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-            raise MembershipNotFoundError(self.pubkey, community.name)
-
-    async def published_uid(self, community):
-        try:
-            data = await community.bma_access.future_request(bma.wot.Lookup,
-                                 req_args={'search': self.pubkey})
-            timestamp = BlockUID.empty()
-
-            for result in data['results']:
-                if result["pubkey"] == self.pubkey:
-                    uids = result['uids']
-                    person_uid = ""
-                    for uid_data in uids:
-                        if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
-                            timestamp = uid_data["meta"]["timestamp"]
-                            person_uid = uid_data["uid"]
-                        if person_uid == self.uid:
-                            return True
-        except errors.DuniterError as e:
-            logging.debug("Lookup error : {0}".format(str(e)))
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        return False
-
-    async def uid_is_revokable(self, community):
-        published = await self.published_uid(community)
-        if published:
-            try:
-                await community.bma_access.future_request(bma.wot.CertifiersOf,
-                                                               {'search': self.pubkey})
-            except errors.DuniterError as e:
-                if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
-                    logging.debug("Certifiers of error : {0}".format(str(e)))
-            except NoPeerAvailable as e:
-                logging.debug(str(e))
-        return False
-
-    async def is_member(self, community):
-        """
-        Check if the person is a member of a community
-
-        :param sakia.core.community.Community community: The community target to request the join date
-        :return: True if the person is a member of a community
-        """
-        try:
-            certifiers = await community.bma_access.future_request(bma.wot.CertifiersOf,
-                                                                        {'search': self.pubkey})
-            return certifiers['isMember']
-        except errors.DuniterError as e:
-            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
-                pass
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        return False
-
-    async def certifiers_of(self, identities_registry, community):
-        """
-        Get the list of this person certifiers
-
-        :param sakia.core.registry.identities.IdentitiesRegistry identities_registry: The identities registry
-        :param sakia.core.community.Community community: The community target
-        :return: The list of the certifiers of this community
-        :rtype: list
-        """
-        certifiers = list()
-        try:
-            data = await community.bma_access.future_request(bma.wot.CertifiersOf,
-                                                                  {'search': self.pubkey})
-
-            for certifier_data in data['certifications']:
-                certifier = {}
-                certifier['identity'] = identities_registry.from_handled_data(certifier_data['uid'],
-                                                                              certifier_data['pubkey'],
-                                                                              None,
-                                                                              BlockchainState.VALIDATED,
-                                                                              community)
-                certifier['cert_time'] = certifier_data['cert_time']['medianTime']
-                if certifier_data['written']:
-                    certifier['block_number'] = certifier_data['written']['number']
-                else:
-                    certifier['block_number'] = None
-
-                certifiers.append(certifier)
-        except errors.DuniterError as e:
-            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
-                logging.debug("Certifiers of error : {0}".format(str(e)))
-            else:
-                logging.debug(str(e))
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-
-        try:
-            data = await community.bma_access.future_request(bma.wot.Lookup, {'search': self.pubkey})
-            for result in data['results']:
-                if result["pubkey"] == self.pubkey:
-                    self._refresh_uid(result['uids'])
-                    for uid_data in result['uids']:
-                        for certifier_data in uid_data['others']:
-                            for uid in certifier_data['uids']:
-                                # add a certifier
-                                certifier = {}
-                                certifier['identity'] = identities_registry.\
-                                    from_handled_data(uid,
-                                                      certifier_data['pubkey'],
-                                                      None,
-                                                      BlockchainState.BUFFERED,
-                                                      community)
-                                certifier['cert_time'] = await community.time(certifier_data['meta']['block_number'])
-                                certifier['block_number'] = None
-
-                                certifiers.append(certifier)
-        except errors.DuniterError as e:
-            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
-                logging.debug("Lookup error : {0}".format(str(e)))
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        return certifiers
-
-    async def certified_by(self, identities_registry, community):
-        """
-        Get the list of persons certified by this person
-        :param sakia.core.registry.IdentitiesRegistry identities_registry: The registry
-        :param sakia.core.community.Community community: The community target
-        :return: The list of the certified persons of this community in BMA json format
-        :rtype: list
-        """
-        certified_list = list()
-        try:
-            data = await community.bma_access.future_request(bma.wot.CertifiedBy, {'search': self.pubkey})
-            for certified_data in data['certifications']:
-                certified = {}
-                certified['identity'] = identities_registry.from_handled_data(certified_data['uid'],
-                                                                              certified_data['pubkey'],
-                                                                              None,
-                                                                              BlockchainState.VALIDATED,
-                                                                              community)
-                certified['cert_time'] = certified_data['cert_time']['medianTime']
-                if certified_data['written']:
-                    certified['block_number'] = certified_data['written']['number']
-                else:
-                    certified['block_number'] = None
-                certified_list.append(certified)
-        except errors.DuniterError as e:
-            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
-                logging.debug("Certified by error : {0}".format(str(e)))
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-
-        try:
-            data = await community.bma_access.future_request(bma.wot.Lookup, {'search': self.pubkey})
-            for result in data['results']:
-                if result["pubkey"] == self.pubkey:
-                    self._refresh_uid(result['uids'])
-                    for certified_data in result['signed']:
-                        certified = {}
-                        certified['identity'] = identities_registry.from_handled_data(certified_data['uid'],
-                                                                          certified_data['pubkey'],
-                                                                          None,
-                                                                          BlockchainState.BUFFERED,
-                                                                          community)
-                        timestamp = BlockUID.from_str(certified_data['meta']['timestamp'])
-                        certified['cert_time'] = await community.time(timestamp.number)
-                        certified['block_number'] = None
-                        certified_list.append(certified)
-        except errors.DuniterError as e:
-            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
-                logging.debug("Lookup error : {0}".format(str(e)))
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        return certified_list
-
-    async def _unique_valid(self, cert_list, community):
-        """
-        Get the certifications in the blockchain and in the pools
-        Get only unique and last certification for each pubkey
-        :param list cert_list: The certifications list to filter
-        :param sakia.core.community.Community community: The community target
-        :return: The list of the certifiers of this community
-        :rtype: list
-        """
-        unique_valid = []
-        #  add certifiers of uid
-        for certifier in tuple(cert_list):
-            # add only valid certification...
-            try:
-                cert_expired = await community.certification_expired(certifier['cert_time'])
-            except NoPeerAvailable:
-                logging.debug("No peer available")
-                cert_expired = True
-
-            if not certifier['block_number']:
-                # add only valid certification...
-                try:
-                    cert_writable = await community.certification_writable(certifier['cert_time'])
-                except NoPeerAvailable:
-                    logging.debug("No peer available")
-                    cert_writable = False
-            else:
-                cert_writable = True
-
-            if not cert_expired and cert_writable:
-                # keep only the latest certification
-                already_found = [c['identity'].pubkey for c in unique_valid]
-                if certifier['identity'].pubkey in already_found:
-                    index = already_found.index(certifier['identity'].pubkey)
-                    if certifier['cert_time'] > unique_valid[index]['cert_time']:
-                        unique_valid[index] = certifier
-                else:
-                    unique_valid.append(certifier)
-        return unique_valid
-
-    async def unique_valid_certifiers_of(self, identities_registry, community):
-        """
-        Get the certifications in the blockchain and in the pools
-        Get only unique and last certification for each pubkey
-        :param sakia.core.registry.identities.IdentitiesRegistry identities_registry: The identities registry
-        :param sakia.core.community.Community community: The community target
-        :return: The list of the certifiers of this community
-        :rtype: list
-        """
-        certifier_list = await self.certifiers_of(identities_registry, community)
-        return await self._unique_valid(certifier_list, community)
-
-    async def unique_valid_certified_by(self, identities_registry, community):
-        """
-        Get the list of persons certified by this person, filtered to get only unique
-        and valid certifications.
-        :param sakia.core.registry.IdentitiesRegistry identities_registry: The registry
-        :param sakia.core.community.Community community: The community target
-        :return: The list of the certified persons of this community in BMA json format
-        :rtype: list
-        """
-        certified_list = await self.certified_by(identities_registry, community)
-        return await self._unique_valid(certified_list, community)
-
-    async def identity_revocation_time(self, community):
-        """
-        Get the remaining time before identity implicit revocation
-        :param sakia.core.Community community: the community
-        :return: the remaining time
-        :rtype: int
-        """
-        membership = await self.membership(community)
-        join_block = membership['blockNumber']
-        block = await community.get_block(join_block)
-        join_date = block['medianTime']
-        parameters = await community.parameters()
-        # revocation date is join_date + 1 sigvalidity (expiration date)  + 2*sigvalidity
-        revocation_date = join_date + 3*parameters['sigValidity']
-        current_time = time.time()
-        return revocation_date - current_time
-
-    async def membership_expiration_time(self, community):
-        """
-        Get the remaining time before membership expiration
-        :param sakia.core.Community community: the community
-        :return: the remaining time
-        :rtype: int
-        """
-        membership = await self.membership(community)
-        join_block = membership['blockNumber']
-        block = await community.get_block(join_block)
-        join_date = block['medianTime']
-        parameters = await community.parameters()
-        expiration_date = join_date + parameters['sigValidity']
-        current_time = time.time()
-        return expiration_date - current_time
-
-    async def cert_issuance_delay(self, identities_registry, community):
-        """
-        Get the remaining time before being able to issue new certification.
-        :param sakia.core.Community community: the community
-        :return: the remaining time
-        :rtype: int
-        """
-        certified = await self.certified_by(identities_registry, community)
-        if len(certified) > 0:
-            latest_time = max([c['cert_time'] for c in certified if c['cert_time']])
-            parameters = await community.parameters()
-            if parameters and latest_time:
-                current_time = await community.time()
-                if current_time - latest_time < parameters['sigPeriod']:
-                    return parameters['sigPeriod'] - (current_time - latest_time)
-        return 0
-
-    async def requirements(self, community):
-        """
-        Get the current requirements data.
-        :param sakia.core.Community community: the community
-        :return: the requirements
-        :rtype: dict
-        """
-        try:
-            requirements = await community.bma_access.future_request(bma.wot.Requirements,
-                                                                     {'search': self.pubkey})
-            for req in requirements['identities']:
-                if req['pubkey'] == self.pubkey and req['uid'] == self.uid and \
-                        self._sigdate and \
-                                BlockUID.from_str(req['meta']['timestamp']) == self._sigdate:
-                    return req
-        except errors.DuniterError as e:
-            logging.debug(str(e))
-        return None
-
-    def _refresh_uid(self, uids):
-        """
-        Refresh UID from uids list, got from a successful lookup request
-        :param list uids: UIDs got from a lookup request
-        """
-        timestamp = BlockUID.empty()
-        if self.local_state == LocalState.NOT_FOUND:
-            for uid_data in uids:
-                if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
-                    timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"])
-                    identity_uid = uid_data["uid"]
-                    self.uid = identity_uid
-                    self.blockchain_state = BlockchainState.BUFFERED
-                    self.local_state = LocalState.PARTIAL
-
-    def jsonify(self):
-        """
-        Get the community as dict in json format.
-        :return: The community as a dict in json format
-        """
-        data = {'uid': self.uid,
-                'pubkey': self.pubkey,
-                'sigdate': str(self._sigdate) if self._sigdate else None,
-                'local_state': self.local_state.name,
-                'blockchain_state': self.blockchain_state.name}
-        return data
-
-    def __str__(self):
-        return "{uid} - {pubkey} - {sigdate} - {local} - {blockchain}".format(uid=self.uid,
-                                                                            pubkey=self.pubkey,
-                                                                            sigdate=self._sigdate,
-                                                                            local=self.local_state,
-                                                                            blockchain=self.blockchain_state)
diff --git a/src/sakia/core/transfer.py b/src/sakia/core/transfer.py
deleted file mode 100644
index b1cd3048ac8d02f013480656ed5c82a2e9285729..0000000000000000000000000000000000000000
--- a/src/sakia/core/transfer.py
+++ /dev/null
@@ -1,374 +0,0 @@
-"""
-Created on 31 janv. 2015
-
-@author: inso
-"""
-import logging
-import time
-from duniterpy.api import bma
-from duniterpy.documents import Block, BlockUID
-from PyQt5.QtCore import pyqtSignal, QObject
-from enum import Enum
-
-
-class TransferState(Enum):
-    """
-    TO_SEND means the transaction wasn't sent yet
-    AWAITING means the transaction is waiting to reach K blockchain confrmation
-    VALIDATED means the transaction was validated locally and is considered present in the blockchain
-    REFUSED means the transaction took too long to be registered in the blockchain,
-    therefore it is considered as refused
-    DROPPED means the transaction was canceled locally. It can still be validated
-    in the blockchain if it was sent, if the guy is unlucky ;)
-    """
-    TO_SEND = 0
-    AWAITING = 1
-    VALIDATING = 4
-    VALIDATED = 2
-    REFUSED = 3
-    DROPPED = 5
-
-
-class Transfer(QObject):
-    """
-    A transfer is the lifecycle of a transaction.
-    """
-    transfer_broadcasted = pyqtSignal(str)
-    broadcast_error = pyqtSignal(int, str)
-
-    def __init__(self, sha_hash, state, blockUID, metadata, locally_created):
-        """
-        The constructor of a transfer.
-        Check for metadata keys which must be present :
-        - receiver
-        - block
-        - time
-        - issuer
-        - amount
-        - comment
-
-        :param str sha_hash: The hash of the transaction
-        :param TransferState state: The state of the Transfer
-        :param duniterpy.documents.BlockUID blockUID: The blockUID of the transaction in the blockchain
-        :param dict metadata: The transfer metadata
-        """
-        assert('receiver' in metadata)
-        assert('time' in metadata)
-        assert('issuer' in metadata)
-        assert('amount' in metadata)
-        assert('comment' in metadata)
-        assert('issuer_uid' in metadata)
-        assert('receiver_uid' in metadata)
-        assert('txid' in metadata)
-        super().__init__()
-
-        self.sha_hash = sha_hash
-        self.state = state
-        self.blockUID = blockUID
-        self._locally_created = locally_created
-        self._metadata = metadata
-
-        # Dict containing states of a transfer :
-        # keys are a tuple containg (current_state, transition_parameters)
-        # values are tuples containing (transition_test, transition_success, new_state)
-        self._table_states = {
-            (TransferState.TO_SEND, (list, Block)):
-                (
-                    (self._broadcast_success, lambda l, b: self._wait(b), TransferState.AWAITING),
-                    (lambda l,b: self._broadcast_failure(l), None, TransferState.REFUSED),
-                ),
-            (TransferState.TO_SEND, ()):
-                ((self._is_locally_created, self._drop, TransferState.DROPPED),),
-
-            (TransferState.AWAITING, (bool, Block)):
-                ((self._found_in_block, lambda r, b: self._be_validating(b), TransferState.VALIDATING),),
-            (TransferState.AWAITING, (bool, Block, int, int)):
-                ((self._not_found_in_blockchain, None, TransferState.REFUSED),),
-
-            (TransferState.VALIDATING, (bool, Block, int)):
-                ((self._reached_enough_confrmation, None, TransferState.VALIDATED),),
-            (TransferState.VALIDATING, (bool, Block)):
-                ((self._rollback_and_removed, lambda r, b: self._drop(), TransferState.DROPPED),),
-
-            (TransferState.VALIDATED, (bool, Block, int)):
-                ((self._rollback_in_fork_window, lambda r, b, i: self._be_validating(b), TransferState.VALIDATING),),
-
-            (TransferState.VALIDATED, (bool, Block)):
-                (
-                    (self._rollback_and_removed, lambda r, b: self._drop(), TransferState.DROPPED),
-                    (self._rollback_and_local, lambda r, b: self._wait(b), TransferState.AWAITING),
-                ),
-
-            (TransferState.REFUSED, ()):
-                ((self._is_locally_created, self._drop, TransferState.DROPPED),)
-        }
-
-    @classmethod
-    def initiate(cls, metadata):
-        """
-        Create a new transfer in a "TO_SEND" state.
-        :param dict metadata: The computed metadata of the transfer
-        :return: A new transfer
-        :rtype: Transfer
-        """
-        return cls(None, TransferState.TO_SEND, None, metadata, True)
-
-    @classmethod
-    def create_from_blockchain(cls, hash, blockUID, metadata):
-        """
-        Create a new transfer sent from another sakia instance
-        :param str hash: The transaction hash
-        :param duniterpy.documents.BlockUID blockUID: The block id were we found the tx
-        :param dict metadata: The computed metadata of the transaction
-        :return: A new transfer
-        :rtype: Transfer
-        """
-        return cls(hash, TransferState.VALIDATING, blockUID, metadata, False)
-
-    @classmethod
-    def load(cls, data):
-        """
-        Create a new transfer from a dict in json format.
-        :param dict data: The loaded data
-        :return: A new transfer
-        :rtype: Transfer
-        """
-        return cls(data['hash'],
-                   TransferState[data['state']],
-                   BlockUID.from_str(data['blockUID']) if data['blockUID'] else None,
-                   data['metadata'], data['local'])
-
-    def jsonify(self):
-        """
-        :return: The transfer as a dict in json format
-        """
-        return {'hash': self.sha_hash,
-                'state': self.state.name,
-                'blockUID': str(self.blockUID) if self.blockUID else None,
-                'metadata': self._metadata,
-                'local': self._locally_created}
-
-    @property
-    def metadata(self):
-        """
-        :return: this transfer metadata
-        """
-        return self._metadata
-
-    def _not_found_in_blockchain(self, rollback, block, mediantime_target, mediantime_blocks):
-        """
-        Check if the transaction could not be found in the blockchain
-        :param bool rollback: True if we are in a rollback procedure
-        :param duniterpy.documents.Block block: The block to look for the tx
-        :param int mediantime_target: The mediantime to mine a block in the community parameters
-        :param int mediantime_blocks: The number of block used to derive the mediantime
-        :return: True if the transaction could not be found in a given time
-        :rtype: bool
-        """
-        if not rollback:
-            for tx in block.transactions:
-                if tx.sha_hash == self.sha_hash:
-                    return False
-            if block.time > self.metadata['time'] + mediantime_target*mediantime_blocks:
-                return True
-        return False
-
-    def _found_in_block(self, rollback, block):
-        """
-        Check if the transaction can be found in the blockchain
-        :param bool rollback: True if we are in a rollback procedure
-        :param duniterpy.documents.Block block: The block to check for the transaction
-        :return: True if the transaction was found
-        :rtype: bool
-        """
-        if not rollback:
-            for tx in block.transactions:
-                if tx.sha_hash == self.sha_hash:
-                    return True
-        return False
-
-    def _broadcast_success(self, ret_codes, block):
-        """
-        Check if the retcode is 200 after a POST
-        :param list ret_codes: The POST return codes of the broadcast
-        :param duniterpy.documents.Block block: The current block used for transition.
-        :return: True if the post was successful
-        :rtype: bool
-        """
-        return 200 in ret_codes
-
-    def _broadcast_failure(self, ret_codes):
-        """
-        Check if no retcode is 200 after a POST
-        :param list ret_codes: The POST return codes of the broadcast
-        :return: True if the post was failed
-        :rtype: bool
-        """
-        return 200 not in ret_codes
-
-    def _reached_enough_confrmation(self, rollback, current_block, fork_window):
-        """
-        Check if the transfer reached enough confrmation in the blockchain
-        :param bool rollback: True if we are in a rollback procedure
-        :param duniterpy.documents.Block current_block: The current block of the main blockchain
-        :param int fork_window: The number of confrmations needed on the network
-        :return: True if the transfer reached enough confrmations
-        :rtype: bool
-        """
-        return not rollback and self.blockUID.number + fork_window <= current_block.number
-
-    def _rollback_and_removed(self, rollback, block):
-        """
-        Check if the transfer is not in the block anymore
-        :param bool rollback: True if we are in a rollback procedure
-        :param duniterpy.documents.Block block: The block to check for the transaction
-        :return: True if the transfer is not found in the block
-        """
-        if rollback:
-            if not block or block.blockUID != self.blockUID:
-                return True
-            else:
-                return self.sha_hash not in [t.sha_hash for t in block.transactions]
-        return False
-
-    def _rollback_in_fork_window(self, rollback, current_block, fork_window):
-        """
-        Check if the transfer is not in the block anymore
-        :param bool rollback: True if we are in a rollback procedure
-        :param duniterpy.documents.Block current_block: The block to check for the transaction
-        :return: True if the transfer is found in the block
-        """
-        if rollback:
-            return self.blockUID.number + fork_window > current_block.number
-        return False
-
-    def _rollback_and_local(self, rollback, block):
-        """
-        Check if the transfer is not in the block anymore
-        :param bool rollback: True if we are in a rollback procedure
-        :param duniterpy.documents.Block block: The block to check for the transaction
-        :return: True if the transfer is found in the block
-        """
-        if rollback and self._locally_created and block.blockUID == self.blockUID:
-            return self.sha_hash not in [t.sha_hash for t in block.transactions]
-        return False
-
-    def _is_locally_created(self):
-        """
-        Check if we can send back the transaction if it was locally created
-        :return: True if the transaction was locally created
-        """
-        return self._locally_created
-
-    def _wait(self, current_block):
-        """
-        Set the transfer as AWAITING confrmation.
-        :param duniterpy.documents.Block current_block: Current block of the main blockchain
-        """
-        self.blockUID = current_block.blockUID
-        self._metadata['time'] = int(time.time())
-
-    def _be_validating(self, block):
-        """
-        Action when the transfer ins found in a block
-
-        :param bool rollback: True if we are in a rollback procedure
-        :param duniterpy.documents.Block block: The block checked
-        """
-        self.blockUID = block.blockUID
-        self._metadata['time'] = block.mediantime
-
-    def _drop(self):
-        """
-        Cancel the transfer locally.
-        The transfer state becomes TransferState.DROPPED.
-        """
-        self.blockUID = None
-
-    def _try_transition(self, transition_key, inputs):
-        """
-        Try the transition defined by the given transition_key
-        with inputs
-        :param tuple transition_key: The transition key in the table states
-        :param tuple inputs: The inputs
-        :return: True if the transition was applied
-        :rtype: bool
-        """
-        if len(inputs) == len(transition_key[1]):
-            for i, input in enumerate(inputs):
-                if type(input) is not transition_key[1][i]:
-                    return False
-            for transition in self._table_states[transition_key]:
-                if transition[0](*inputs):
-                    if self.sha_hash:
-                        logging.debug("{0} : {1} --> {2}".format(self.sha_hash[:5], self.state.name,
-                                                                 transition[2].name))
-                    else:
-                        logging.debug("Unsent transfer : {0} --> {1}".format(self.state.name,
-                                                                 transition[2].name))
-
-                    # If the transition changes data, apply changes
-                    if transition[1]:
-                        transition[1](*inputs)
-                    self.state = transition[2]
-                    return True
-        return False
-
-    def run_state_transitions(self, inputs):
-        """
-        Try all current state transitions with inputs
-        :param tuple inputs: The inputs passed to the transitions
-        :return: True if the transaction changed state
-        :rtype: bool
-        """
-        transition_keys = [k for k in self._table_states.keys() if k[0] == self.state]
-        for key in transition_keys:
-            if self._try_transition(key, inputs):
-                return True
-        return False
-
-    def cancel(self):
-        """
-        Cancel a local transaction
-        """
-        self.run_state_transitions(())
-
-    async def send(self, txdoc, community):
-        """
-        Send a transaction and update the transfer state to AWAITING if accepted.
-        If the transaction was refused (return code != 200), state becomes REFUSED
-        The txdoc is saved as the transfer txdoc.
-
-        :param txdoc: A transaction duniterpy object
-        :param community: The community target of the transaction
-        """
-        self.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)
-        result = (False, "")
-        for r in responses:
-            if r.status == 200:
-                result = (True, (await r.json()))
-            elif not result[0]:
-                result = (False, (await r.text()))
-            else:
-                await r.text()
-        self.run_state_transitions(([r.status for r in responses], block_doc))
-        self.run_state_transitions(([r.status for r in responses], ))
-        return result
-
-    async def get_raw_document(self, community):
-        """
-        Get the raw documents of this transfer
-        """
-        block = await community.get_block(self.blockUID.number)
-        if block:
-            block_doc = Block.from_signed_raw("{0}{1}\n".format(block['raw'], block['signature']))
-            for tx in block_doc.transactions:
-                if tx.sha_hash == self.sha_hash:
-                    return tx
diff --git a/src/sakia/core/txhistory.py b/src/sakia/core/txhistory.py
deleted file mode 100644
index 4fd7b9a6c008f7f446026f670fa802602f6ab4bb..0000000000000000000000000000000000000000
--- a/src/sakia/core/txhistory.py
+++ /dev/null
@@ -1,426 +0,0 @@
-import asyncio
-import logging
-import hashlib
-import math
-from duniterpy.documents import SimpleTransaction, Block, MalformedDocumentError
-from duniterpy.api import  bma, errors
-from .transfer import Transfer, TransferState
-from .net.network import MAX_CONFIRMATIONS
-from ..tools.exceptions import LookupFailureError, NoPeerAvailable
-
-
-class TxHistory:
-    def __init__(self, app, wallet):
-        self._latest_block = 0
-        self.wallet = wallet
-        self.app = app
-        self._stop_coroutines = False
-        self._running_refresh = []
-        self._transfers = []
-        self.available_sources = []
-        self._dividends = []
-
-    @property
-    def latest_block(self):
-        return self._latest_block
-
-    @latest_block.setter
-    def latest_block(self, value):
-        self._latest_block = value
-
-    def load_from_json(self, data, version):
-        """
-        Load the tx history cache from json data
-
-        :param dict data: the data
-        :param version: the version parsed with pkg_resources.parse_version
-        :return:
-        """
-        self._transfers = []
-
-        data_sent = data['transfers']
-        for s in data_sent:
-            self._transfers.append(Transfer.load(s))
-
-        for s in data['sources']:
-            self.available_sources.append(s.copy())
-
-        for d in data['dividends']:
-            d['state'] = TransferState[d['state']]
-            self._dividends.append(d)
-
-        self.latest_block = data['latest_block']
-
-    def jsonify(self):
-        data_transfer = []
-        for s in self.transfers:
-            data_transfer.append(s.jsonify())
-
-        data_sources = []
-        for s in self.available_sources:
-            data_sources.append(s)
-
-        data_dividends = []
-        for d in self._dividends:
-            dividend = {
-                'block_number': d['block_number'],
-                "consumed": d['consumed'],
-                'time': d['time'],
-                'id': d['id'],
-                'amount': d['amount'],
-                'base': d['base'],
-                'state': d['state'].name
-            }
-            data_dividends.append(dividend)
-
-        return {'latest_block': self.latest_block,
-                'transfers': data_transfer,
-                'sources': data_sources,
-                'dividends': data_dividends}
-
-    @property
-    def transfers(self):
-        return [t for t in self._transfers if t.state != TransferState.DROPPED]
-
-    @property
-    def dividends(self):
-        return self._dividends.copy()
-
-    def stop_coroutines(self, closing=False):
-        self._stop_coroutines = True
-
-    async def _get_block_doc(self, community, number):
-        """
-        Retrieve the current block document
-        :param sakia.core.Community community: The community we look for a block
-        :param int number: The block number to retrieve
-        :return: the block doc or None if no block was found
-        """
-        tries = 0
-        block_doc = None
-        block = None
-        while block is None and tries < 3:
-            try:
-                block = await community.bma_access.future_request(bma.blockchain.Block,
-                                      req_args={'number': number})
-                signed_raw = "{0}{1}\n".format(block['raw'],
-                                           block['signature'])
-                try:
-                    block_doc = Block.from_signed_raw(signed_raw)
-                except TypeError:
-                    logging.debug("Error in {0}".format(number))
-                    block = None
-                    tries += 1
-            except errors.DuniterError as e:
-                if e.ucode == errors.BLOCK_NOT_FOUND:
-                    block = None
-                    tries += 1
-        return block_doc
-
-    async def _parse_transaction(self, community, tx, blockUID,
-                           mediantime, received_list, txid):
-        """
-        Parse a transaction
-        :param sakia.core.Community community: The community
-        :param duniterpy.documents.Transaction tx: The tx json data
-        :param duniterpy.documents.BlockUID blockUID: The block id where we found the tx
-        :param int mediantime: Median time on the network
-        :param list received_list: The list of received transactions
-        :param int txid: The latest txid
-        :return: the found transaction
-        """
-        receivers = [o.conditions.left.pubkey for o in tx.outputs
-                     if o.conditions.left.pubkey != tx.issuers[0]]
-
-        if len(receivers) == 0:
-            receivers = [tx.issuers[0]]
-
-        try:
-            issuer = await self.wallet._identities_registry.future_find(tx.issuers[0], community)
-            issuer_uid = issuer.uid
-        except LookupFailureError:
-            issuer_uid = ""
-
-        try:
-            receiver = await self.wallet._identities_registry.future_find(receivers[0], community)
-            receiver_uid = receiver.uid
-        except LookupFailureError:
-            receiver_uid = ""
-
-        metadata = {
-                    'time': mediantime,
-                    'comment': tx.comment,
-                    'issuer': tx.issuers[0],
-                    'issuer_uid': issuer_uid,
-                    'receiver': receivers[0],
-                    'receiver_uid': receiver_uid,
-                    'txid': txid
-                    }
-
-        in_issuers = len([i for i in tx.issuers
-                     if i == self.wallet.pubkey]) > 0
-        in_outputs = len([o for o in tx.outputs
-                       if o.conditions.left.pubkey == self.wallet.pubkey]) > 0
-
-        tx_hash = hashlib.sha256(tx.signed_raw().encode("ascii")).hexdigest().upper()
-        # If the wallet pubkey is in the issuers we sent this transaction
-        if in_issuers:
-            outputs = [o for o in tx.outputs
-                       if o.conditions.left.pubkey != self.wallet.pubkey]
-            amount = 0
-            for o in outputs:
-                amount += o.amount * math.pow(10, o.base)
-            metadata['amount'] = amount
-            transfer = Transfer.create_from_blockchain(tx_hash,
-                                                       blockUID,
-                                                 metadata.copy())
-            return transfer
-        # If we are not in the issuers,
-        # maybe we are in the recipients of this transaction
-        elif in_outputs:
-            outputs = [o for o in tx.outputs
-                       if o.conditions.left.pubkey == self.wallet.pubkey]
-            amount = 0
-            for o in outputs:
-                amount += o.amount * math.pow(10, o.base)
-            metadata['amount'] = amount
-
-            transfer = Transfer.create_from_blockchain(tx_hash,
-                                                       blockUID,
-                                                 metadata.copy())
-            received_list.append(transfer)
-            return transfer
-        return None
-
-    async def _parse_block(self, community, block_number, received_list, txmax):
-        """
-        Parse a block
-        :param sakia.core.Community community: The community
-        :param int block_number: The block to request
-        :param list received_list: The list where we are appending transactions
-        :param int txmax: Latest tx id
-        :return: The list of transfers sent
-        """
-        block_doc = await self._get_block_doc(community, block_number)
-        transfers = []
-        if block_doc:
-            for transfer in [t for t in self._transfers if t.state == TransferState.AWAITING]:
-                transfer.run_state_transitions((False, block_doc))
-
-            new_tx = [t for t in block_doc.transactions
-                      if t.sha_hash not in [trans.sha_hash for trans in self._transfers]
-                       and SimpleTransaction.is_simple(t)]
-
-            for (txid, tx) in enumerate(new_tx):
-                transfer = await self._parse_transaction(community, tx, block_doc.blockUID,
-                                        block_doc.mediantime, received_list, txid+txmax)
-                if transfer:
-                    #logging.debug("Transfer amount : {0}".format(transfer.metadata['amount']))
-                    transfers.append(transfer)
-                else:
-                    pass
-                    #logging.debug("None transfer")
-        else:
-            logging.debug("Could not find or parse block {0}".format(block_number))
-        return transfers
-
-    async def request_dividends(self, community, parsed_block):
-        for i in range(0, 6):
-            try:
-                dividends_data = await community.bma_access.future_request(bma.ud.History,
-                                                req_args={'pubkey': self.wallet.pubkey})
-
-                dividends = dividends_data['history']['history'].copy()
-
-                for d in dividends:
-                    if d['block_number'] < parsed_block:
-                        dividends.remove(d)
-                return dividends
-            except errors.DuniterError as e:
-                if e.ucode == errors.BLOCK_NOT_FOUND:
-                    pass
-        return {}
-
-    async def _refresh(self, community, block_number_from, block_to, received_list):
-        """
-        Refresh last transactions
-
-        :param sakia.core.Community community: The community
-        :param list received_list: List of transactions received
-        """
-        new_transfers = []
-        new_dividends = []
-        try:
-            logging.debug("Refresh from : {0} to {1}".format(block_number_from, block_to['number']))
-            dividends = await self.request_dividends(community, block_number_from)
-            with_tx_data = await community.bma_access.future_request(bma.blockchain.TX)
-            blocks_with_tx = with_tx_data['result']['blocks']
-            while block_number_from <= block_to['number']:
-                udid = 0
-                for d in [ud for ud in dividends if ud['block_number'] == block_number_from]:
-                    state = TransferState.VALIDATED if block_number_from + MAX_CONFIRMATIONS <= block_to['number'] \
-                        else TransferState.VALIDATING
-
-                    if d['block_number'] not in [ud['block_number'] for ud in self._dividends]:
-                        d['id'] = udid
-                        d['state'] = state
-                        new_dividends.append(d)
-
-                        udid += 1
-                    else:
-                        known_dividend = [ud for ud in self._dividends
-                                          if ud['block_number'] == d['block_number']][0]
-                        known_dividend['state'] = state
-
-                # We parse only blocks with transactions
-                if block_number_from in blocks_with_tx:
-                    transfers = await self._parse_block(community, block_number_from,
-                                                             received_list,
-                                                             udid + len(new_transfers))
-                    new_transfers += transfers
-
-                self.wallet.refresh_progressed.emit(block_number_from, block_to['number'], self.wallet.pubkey)
-                block_number_from += 1
-
-            signed_raw = "{0}{1}\n".format(block_to['raw'],
-                                       block_to['signature'])
-            block_to = Block.from_signed_raw(signed_raw)
-            for transfer in [t for t in self._transfers + new_transfers if t.state == TransferState.VALIDATING]:
-                transfer.run_state_transitions((False, block_to, MAX_CONFIRMATIONS))
-
-            # We check if latest parsed block_number is a new high number
-            if block_number_from > self.latest_block:
-                self.available_sources = await self.wallet.sources(community)
-                if self._stop_coroutines:
-                    return
-                self.latest_block = block_number_from
-
-            parameters = await community.parameters()
-            for transfer in [t for t in self._transfers if t.state == TransferState.AWAITING]:
-                transfer.run_state_transitions((False, block_to,
-                                                parameters['avgGenTime'], parameters['medianTimeBlocks']))
-        except (MalformedDocumentError, NoPeerAvailable) as e:
-            logging.debug(str(e))
-            self.wallet.refresh_finished.emit([])
-            return
-
-        self._transfers = self._transfers + new_transfers
-        self._dividends = self._dividends + new_dividends
-
-        self.wallet.refresh_finished.emit(received_list)
-
-    async def _check_block(self, community, block_number):
-        """
-        Parse a block
-        :param sakia.core.Community community: The community
-        :param int block_number: The block to check for transfers
-        """
-        block_doc = await self._get_block_doc(community, block_number)
-        if block_doc:
-            # We check the block dividend state
-            match = [d for d in self._dividends if d['block_number'] == block_number]
-            if len(match) > 0:
-                if block_doc.ud:
-                    match[0]['amount'] = block_doc.ud
-                    match[0]['base'] = block_doc.unit_base
-                else:
-                    self._dividends.remove(match[0])
-
-            # We check if transactions are still present
-            for transfer in [t for t in self._transfers
-                             if t.state in (TransferState.VALIDATING, TransferState.VALIDATED) and
-                             t.blockUID.number == block_number]:
-                if transfer.blockUID.sha_hash == block_doc.blockUID.sha_hash:
-                    return True
-                transfer.run_state_transitions((True, block_doc))
-        else:
-            logging.debug("Could not get block document")
-        return False
-
-    async def _rollback(self, community):
-        """
-        Rollback last transactions until we find one still present
-        in the main blockchain
-
-        :param sakia.core.Community community: The community
-        """
-        try:
-            logging.debug("Rollback from : {0}".format(self.latest_block))
-            # We look for the block goal to check for rollback,
-            #  depending on validating and validated transfers...
-            tx_blocks = [tx.blockUID.number for tx in self._transfers
-                          if tx.state in (TransferState.VALIDATED, TransferState.VALIDATING) and
-                          tx.blockUID is not None]
-            ud_blocks = [ud['block_number'] for ud in self._dividends
-                          if ud['state'] in (TransferState.AWAITING, TransferState.VALIDATING)]
-            blocks = tx_blocks + ud_blocks
-            blocks.reverse()
-            for i, block_number in enumerate(blocks):
-                self.wallet.refresh_progressed.emit(i, len(blocks), self.wallet.pubkey)
-                if await self._check_block(community, block_number):
-                    break
-
-            current_block = await self._get_block_doc(community, community.network.current_blockUID.number)
-            if current_block:
-                members_pubkeys = await community.members_pubkeys()
-                for transfer in [t for t in self._transfers
-                                 if t.state == TransferState.VALIDATED]:
-                    transfer.run_state_transitions((True, current_block, MAX_CONFIRMATIONS))
-        except NoPeerAvailable:
-            logging.debug("No peer available")
-
-    async def refresh(self, community, received_list):
-        # We update the block goal
-        try:
-            current_block_number = community.network.current_blockUID.number
-            if current_block_number:
-                current_block = await community.bma_access.future_request(bma.blockchain.Block,
-                                        req_args={'number': current_block_number})
-                members_pubkeys = await community.members_pubkeys()
-                # We look for the first block to parse, depending on awaiting and validating transfers and ud...
-                tx_blocks = [tx.blockUID.number for tx in self._transfers
-                          if tx.state in (TransferState.AWAITING, TransferState.VALIDATING) \
-                         and tx.blockUID is not None]
-                ud_blocks = [ud['block_number'] for ud in self._dividends
-                          if ud['state'] in (TransferState.AWAITING, TransferState.VALIDATING)]
-                blocks = tx_blocks + ud_blocks + \
-                         [max(0, self.latest_block - MAX_CONFIRMATIONS)]
-                block_from = min(set(blocks))
-
-                await self._wait_for_previous_refresh()
-                if block_from < current_block["number"]:
-                    # Then we start a new one
-                    logging.debug("Starts a new refresh")
-                    task = asyncio.ensure_future(self._refresh(community, block_from, current_block, received_list))
-                    self._running_refresh.append(task)
-        except errors.DuniterError as e:
-            if e.ucode == errors.BLOCK_NOT_FOUND:
-                logging.debug("Block not found")
-        except NoPeerAvailable:
-            logging.debug("No peer available")
-
-    async def rollback(self, community, received_list):
-        await self._wait_for_previous_refresh()
-        # Then we start a new one
-        logging.debug("Starts a new rollback")
-        task = asyncio.ensure_future(self._rollback(community))
-        self._running_refresh.append(task)
-
-        # Then we start a refresh to check for new transactions
-        await self.refresh(community, received_list)
-
-    async def _wait_for_previous_refresh(self):
-        # We wait for current refresh coroutines
-        if len(self._running_refresh) > 0:
-            logging.debug("Wait for the end of previous refresh")
-            done, pending = await asyncio.wait(self._running_refresh)
-            for cor in done:
-                try:
-                    self._running_refresh.remove(cor)
-                except ValueError:
-                    logging.debug("Task already removed.")
-            for p in pending:
-                logging.debug("Still waiting for : {0}".format(p))
-            logging.debug("Previous refresh finished")
-        else:
-            logging.debug("No previous refresh")
diff --git a/src/sakia/core/wallet.py b/src/sakia/core/wallet.py
deleted file mode 100644
index cc4a1f0f9e2fa2a9fe504a2c93db2f0dec8bb201..0000000000000000000000000000000000000000
--- a/src/sakia/core/wallet.py
+++ /dev/null
@@ -1,412 +0,0 @@
-"""
-Created on 1 févr. 2014
-
-@author: inso
-"""
-
-from duniterpy.documents.transaction import InputSource, OutputSource, Unlock, SIGParameter, Transaction, reduce_base
-from duniterpy.grammars import output
-from duniterpy.key import SigningKey
-
-from duniterpy.api import bma
-from duniterpy.api.bma import PROTOCOL_VERSION
-from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable, LookupFailureError
-from .transfer import Transfer
-from .txhistory import TxHistory
-from .. import __version__
-
-from pkg_resources import parse_version
-from PyQt5.QtCore import QObject, pyqtSignal
-
-import logging
-import asyncio
-
-
-class Wallet(QObject):
-    """
-    A wallet is used to manage money with a unique key.
-    """
-    refresh_progressed = pyqtSignal(int, int, str)
-    refresh_finished = pyqtSignal(list)
-
-    def __init__(self, walletid, pubkey, name, identities_registry):
-        """
-        Constructor of a wallet object
-
-        :param int walletid: The wallet number, unique between all wallets
-        :param str pubkey: The wallet pubkey
-        :param str name: The wallet name
-        """
-        super().__init__()
-        self.coins = []
-        self.walletid = walletid
-        self.pubkey = pubkey
-        self.name = name
-        self._identities_registry = identities_registry
-        self.caches = {}
-
-    @classmethod
-    def create(cls, walletid, salt, password, scrypt_params, name, identities_registry):
-        """
-        Factory method to create a new wallet
-
-        :param int walletid: The wallet number, unique between all wallets
-        :param str salt: The account salt
-        :param str password: The account password
-        :param duniterpy.key.ScryptParams scrypt_params: the scrypt params
-        :param str name: The account name
-        """
-        if walletid == 0:
-            key = SigningKey(salt, password, scrypt_params)
-        else:
-            key = SigningKey(b"{0}{1}".format(salt, walletid), password, scrypt_params)
-        return cls(walletid, key.pubkey, name, identities_registry)
-
-    @classmethod
-    def load(cls, json_data, identities_registry):
-        """
-        Factory method to load a saved wallet.
-
-        :param dict json_data: The wallet as a dict in json format
-        """
-        walletid = json_data['walletid']
-        pubkey = json_data['pubkey']
-        name = json_data['name']
-        return cls(walletid, pubkey, name, identities_registry)
-
-    def load_caches(self, app, json_data):
-        """
-        Load this wallet caches.
-        Each cache correspond to one different community.
-
-        :param dict json_data: The caches as a dict in json format
-        """
-        version = parse_version(json_data['version'])
-        for currency in json_data:
-            if currency != 'version':
-                self.caches[currency] = TxHistory(app, self)
-                if version >= parse_version("0.20.dev0"):
-                    self.caches[currency].load_from_json(json_data[currency], version)
-
-    def jsonify_caches(self):
-        """
-        Get this wallet caches as json.
-
-        :return: The wallet caches as a dict in json format
-        """
-        data = {}
-        for currency in self.caches:
-            data[currency] = self.caches[currency].jsonify()
-        return data
-
-    def init_cache(self, app, community):
-        """
-        Init the cache of this wallet for the specified community.
-
-        :param community: The community to refresh its cache
-        """
-        if community.currency not in self.caches:
-            self.caches[community.currency] = TxHistory(app, self)
-
-    def refresh_transactions(self, community, received_list):
-        """
-        Refresh the cache of this wallet for the specified community.
-
-        :param community: The community to refresh its cache
-        """
-        logging.debug("Refresh transactions for {0}".format(self.pubkey))
-        asyncio.ensure_future(self.caches[community.currency].refresh(community, received_list))
-
-    def rollback_transactions(self, community, received_list):
-        """
-        Rollback the transactions of this wallet for the specified community.
-
-        :param community: The community to refresh its cache
-        """
-        logging.debug("Refresh transactions for {0}".format(self.pubkey))
-        asyncio.ensure_future(self.caches[community.currency].rollback(community, received_list))
-
-    async def relative_value(self, community):
-        """
-        Get wallet value relative to last generated UD
-
-        :param community: The community to get value
-        :return: The wallet relative value
-        """
-        value = await self.value(community)
-        ud = community.dividend
-        relative_value = value / float(ud)
-        return relative_value
-
-    async def value(self, community):
-        """
-        Get wallet absolute value
-
-        :param community: The community to get value
-        :return: The wallet absolute value
-        """
-        value = 0
-        sources = await self.sources(community)
-        for s in sources:
-            value += s['amount'] * pow(10, s['base'])
-        return value
-
-    def tx_sources(self, amount, community):
-        """
-        Get inputs to generate a transaction with a given amount of money
-
-        :param int amount: The amount target value
-        :param community: 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, 0)
-        cache = self.caches[community.currency]
-        if cache.available_sources:
-            current_base = max([src['base'] for src in cache.available_sources])
-            value = 0
-            sources = []
-            outputs = []
-            overheads = []
-            buf_sources = list(cache.available_sources)
-            while current_base >= 0:
-                for s in [src for src in cache.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, buf_sources
-
-                current_base -= 1
-
-        raise NotEnoughMoneyError(value, community.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 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, pubkey, outputs, overheads):
-        """
-        Get outputs to generate a transaction with a given amount of money
-
-        :param str pubkey: The target pubkey 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(pubkey))))
-
-        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(self.pubkey))))
-
-        return total
-
-    def prepare_tx(self, pubkey, blockstamp, amount, message, community):
-        """
-        Prepare a simple Transaction document
-        :param str pubkey: the target of the transaction
-        :param duniterpy.documents.BlockUID blockstamp: the blockstamp
-        :param int amount: the amount sent to the receiver
-        :param Community community: the target community
-        :return: the transaction document
-        :rtype: duniterpy.documents.Transaction
-        """
-        result = self.tx_sources(int(amount), community)
-        sources = result[0]
-        computed_outputs = result[1]
-        overheads = result[2]
-        self.caches[community.currency].available_sources = result[3][1:]
-        logging.debug("Inputs : {0}".format(sources))
-
-        inputs = self.tx_inputs(sources)
-        unlocks = self.tx_unlocks(sources)
-        outputs = self.tx_outputs(pubkey, computed_outputs, overheads)
-        logging.debug("Outputs : {0}".format(outputs))
-        tx = Transaction(3, community.currency, blockstamp, 0,
-                         [self.pubkey], inputs, unlocks,
-                         outputs, message, None)
-        return tx
-
-    async def send_money(self, salt, password, scrypt_params, community,
-                   recipient, amount, message):
-        """
-        Send money to a given recipient in a specified community
-
-        :param str salt: The account salt
-        :param str password: The account password
-        :param community: The community target of the transfer
-        :param str recipient: The pubkey of the recipient
-        :param int amount: The amount of money to transfer
-        :param str message: The message to send with the transfer
-        """
-        try:
-            blockUID = community.network.current_blockUID
-            block = await community.bma_access.future_request(bma.blockchain.Block,
-                                      req_args={'number': blockUID.number})
-        except ValueError as e:
-            if '404' in str(e):
-                return False, "Could not send transfer with null blockchain"
-
-        time = block['medianTime']
-        txid = len(block['transactions'])
-        if self.walletid == 0:
-            key = SigningKey(salt, password, scrypt_params)
-        else:
-            key = SigningKey("{0}{1}".format(salt, self.walletid), password, scrypt_params)
-        logging.debug("Sender pubkey:{0}".format(key.pubkey))
-
-        try:
-            issuer = await self._identities_registry.future_find(key.pubkey, community)
-            issuer_uid = issuer.uid
-        except LookupFailureError as e:
-            issuer_uid = ""
-
-        try:
-            receiver = await self._identities_registry.future_find(recipient, community)
-            receiver_uid = receiver.uid
-        except LookupFailureError as e:
-            receiver_uid = ""
-
-        metadata = {'block': None,
-                    'time': time,
-                    'amount': amount,
-                    'issuer': key.pubkey,
-                    'issuer_uid': issuer_uid,
-                    'receiver': recipient,
-                    'receiver_uid': receiver_uid,
-                    'comment': message,
-                    'txid': txid
-                    }
-        transfer = Transfer.initiate(metadata)
-        self.caches[community.currency]._transfers.append(transfer)
-        try:
-            tx = self.prepare_tx(recipient, blockUID, amount, message, community)
-            logging.debug("TX : {0}".format(tx.raw()))
-
-            tx.sign([key])
-            logging.debug("Transaction : [{0}]".format(tx.signed_raw()))
-            return await transfer.send(tx, community)
-        except NotEnoughMoneyError as e:
-            return (False, str(e))
-
-    async def sources(self, community):
-        """
-        Get available sources in a given community
-
-        :param sakia.core.community.Community community: The community where we want available sources
-        :return: List of bma sources
-        """
-        sources = []
-        try:
-            data = await community.bma_access.future_request(bma.tx.Sources,
-                                     req_args={'pubkey': self.pubkey})
-            return data['sources'].copy()
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        return sources
-
-    def transfers(self, community):
-        """
-        Get all transfers objects of this wallet
-
-        :param community: The community we want to get the executed transfers
-        :return: A list of Transfer objects
-        """
-        if community.currency in self.caches:
-            return self.caches[community.currency].transfers
-        else:
-            return []
-
-    def dividends(self, community):
-        """
-        Get all the dividends received by this wallet
-
-        :param community:  The community we want to get received dividends
-        :return: Result of udhistory request
-        """
-        if community.currency in self.caches:
-            return self.caches[community.currency].dividends
-        else:
-            return []
-
-    def stop_coroutines(self, closing=False):
-        for c in self.caches.values():
-            c.stop_coroutines(closing)
-
-    def jsonify(self):
-        """
-        Get the wallet as json format.
-
-        :return: The wallet as a dict in json format.
-        """
-        return {'walletid': self.walletid,
-                'pubkey': self.pubkey,
-                'name': self.name,
-                'version': __version__}
diff --git a/src/sakia/core/net/api/__init__.py b/src/sakia/data/__init__.py
similarity index 100%
rename from src/sakia/core/net/api/__init__.py
rename to src/sakia/data/__init__.py
diff --git a/src/sakia/data/connectors/__init__.py b/src/sakia/data/connectors/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..50431678702ffa85b431cf7b1cab28a519252f7d
--- /dev/null
+++ b/src/sakia/data/connectors/__init__.py
@@ -0,0 +1,2 @@
+from .node import NodeConnector
+from .bma import BmaConnector
diff --git a/src/sakia/data/connectors/bma.py b/src/sakia/data/connectors/bma.py
new file mode 100644
index 0000000000000000000000000000000000000000..241b4e742628a654163641491002b6ad57538895
--- /dev/null
+++ b/src/sakia/data/connectors/bma.py
@@ -0,0 +1,230 @@
+import logging
+import aiohttp
+from aiohttp.errors import ClientError, ServerDisconnectedError
+from duniterpy.api import bma, errors
+from duniterpy.documents import BMAEndpoint, SecuredBMAEndpoint
+from sakia.errors import NoPeerAvailable
+from pkg_resources import parse_version
+from socket import gaierror
+import asyncio
+import random
+import jsonschema
+import attr
+import copy
+
+
+def filter_endpoints(request, nodes):
+    def compare_versions(node, version):
+        if node.version and node.version != '':
+            try:
+                return parse_version(node.version) >= parse_version(version)
+            except TypeError:
+                return False
+        else:
+            return True
+    filters = {
+        bma.ud.history: lambda n: compare_versions(n, "0.11.0"),
+        bma.tx.history: lambda n: compare_versions(n, "0.11.0"),
+        bma.blockchain.membership: lambda n: compare_versions(n, "0.14")
+    }
+    if request in filters:
+        nodes = [n for n in nodes if filters[request](n)]
+    endpoints = []
+    for n in nodes:
+        endpoints += [e for e in n.endpoints if type(e) in (BMAEndpoint, SecuredBMAEndpoint)]
+    return endpoints
+
+
+def make_hash(o):
+    """
+    Makes a hash from a dictionary, list, tuple or set to any level, that contains
+    only other hashable types (including any lists, tuples, sets, and
+    dictionaries).
+    """
+
+    if isinstance(o, (set, tuple, list)):
+        return tuple([make_hash(e) for e in o])
+    elif not isinstance(o, dict):
+        return hash(o)
+
+    new_o = copy.deepcopy(o)
+    for k, v in new_o.items():
+        new_o[k] = make_hash(v)
+
+    return hash(tuple(frozenset(sorted(new_o.items()))))
+
+
+def _compare_json(first, second):
+    """
+    Compare two json dicts
+    :param first: the first dictionnary
+    :param second: the second dictionnary
+    :return: True if the json dicts are the same
+    :rtype: bool
+    """
+
+    def ordered(obj):
+        if isinstance(obj, dict):
+            try:
+                return sorted((k, ordered(v)) for k, v in obj.items())
+            except TypeError:
+                return obj
+        if isinstance(obj, list):
+            try:
+                return sorted(ordered(x) for x in obj)
+            except TypeError:
+                return obj
+        else:
+            return obj
+
+    return ordered(first) == ordered(second)
+
+
+def _filter_data(request, data):
+    filtered = data
+    if request is bma.wot.lookup:
+        filtered = copy.deepcopy(data)
+        filtered.pop("results")
+    elif request is bma.tx.history:
+        filtered = copy.deepcopy(data)
+        filtered["history"].pop("sending")
+        filtered["history"].pop("receiving")
+        filtered["history"].pop("pending")
+    elif request is bma.wot.requirements:
+        filtered = copy.deepcopy(data)
+        for idty in filtered["identities"]:
+            for c in idty["certifications"]:
+                c.pop("expiresIn")
+
+    return filtered
+
+
+@attr.s()
+class BmaConnector:
+    """
+    This class is used to access BMA API.
+    """
+    _nodes_processor = attr.ib()
+    _user_parameters = attr.ib()
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    async def verified_get(self, currency, request, req_args):
+        synced_nodes = self._nodes_processor.synced_members_nodes(currency)
+        if not synced_nodes:
+            # If no node is known as a member, lookup synced nodes as a fallback
+            synced_nodes = self._nodes_processor.synced_nodes(currency)
+        nodes_generator = (n for n in synced_nodes)
+        answers = {}
+        answers_data = {}
+        nb_verification = min(max(1, 0.66 * len(synced_nodes)), 10)
+        # We try to find agreeing nodes from one 1 to 66% of nodes, max 10
+        session = aiohttp.ClientSession()
+        try:
+            while max([len(nodes) for nodes in answers.values()] + [0]) <= nb_verification:
+                futures = []
+
+                try:
+                    for i in range(0, int(nb_verification)+1):
+                        node = next(nodes_generator)
+                        endpoints = filter_endpoints(request, [node])
+                        endpoint = random.choice(endpoints)
+                        self._logger.debug(
+                            "Requesting {0} on endpoint {1}".format(str(request.__name__), str(endpoint)))
+                        futures.append(request(
+                            endpoint.conn_handler(session, proxy=self._user_parameters.proxy()),
+                            **req_args))
+                except StopIteration:
+                    # When no more node is available, we go out of the while loop
+                    break
+                finally:
+                    # Everytime we go out of the while loop, we gather the futures
+                    if futures:
+                        responses = await asyncio.gather(*futures, return_exceptions=True)
+                        for r in responses:
+                            if isinstance(r, errors.DuniterError):
+                                data_hash = hash(r.ucode)
+                            elif isinstance(r, BaseException):
+                                self._logger.debug("Exception in responses : " + str(r))
+                                continue
+                            else:
+                                filtered_data = _filter_data(request, r)
+                                data_hash = make_hash(filtered_data)
+                            answers_data[data_hash] = r
+                            if data_hash not in answers:
+                                answers[data_hash] = [node]
+                            else:
+                                answers[data_hash].append(node)
+        finally:
+            session.close()
+
+        for dict_hash in answers:
+            if len(answers[dict_hash]) >= nb_verification:
+                if isinstance(answers_data[dict_hash], errors.DuniterError):
+                    raise answers_data[dict_hash]
+                else:
+                    return answers_data[dict_hash]
+
+        raise NoPeerAvailable("", len(synced_nodes))
+
+    async def simple_get(self, currency, request, req_args):
+        endpoints = filter_endpoints(request, self._nodes_processor.synced_nodes(currency))
+        tries = 0
+        while tries < 3 and endpoints:
+            endpoint = random.choice(endpoints)
+            endpoints.remove(endpoint)
+            try:
+                self._logger.debug("Requesting {0} on endpoint {1}".format(str(request.__name__), str(endpoint)))
+                async with aiohttp.ClientSession() as session:
+                    json_data = await request(endpoint.conn_handler(session), **req_args)
+                    return json_data
+            except (ClientError, ServerDisconnectedError, gaierror,
+                    asyncio.TimeoutError, ValueError, jsonschema.ValidationError) as e:
+                self._logger.debug(str(e))
+                tries += 1
+        raise NoPeerAvailable("", len(endpoints))
+
+    async def get(self, currency, request, req_args={}, verify=True):
+        """
+        :param str currency: the currency requested
+        :param class request: A bma request class calling for data
+        :param dict req_args: Arguments to pass to the request constructor
+        :param bool verify: Verify returned value against multiple nodes
+        :return: The returned data
+        """
+        if verify:
+            return await self.verified_get(currency, request, req_args)
+        else:
+            return await self.simple_get(currency, request, req_args)
+
+    async def broadcast(self, currency, request, req_args={}):
+        """
+        Broadcast data to a network.
+        Sends the data to all knew nodes.
+
+        :param str currency: the currency target
+        :param request: A duniterpy bma request class
+        :param req_args: Arguments to pass to the request constructor
+        :return: All nodes replies
+        :rtype: tuple of aiohttp replies
+
+        .. note:: If one node accept the requests (returns 200),
+        the broadcast should be considered accepted by the network.
+        """
+        filtered_endpoints = filter_endpoints(request, self._nodes_processor.synced_nodes(currency))
+        endpoints = random.sample(filtered_endpoints, 6) if len(filtered_endpoints) > 6 else filtered_endpoints
+        replies = []
+
+        if len(endpoints) > 0:
+            with aiohttp.ClientSession() as session:
+                for endpoint in endpoints:
+                    self._logger.debug("Trying to connect to : " + str(endpoint))
+                    reply = asyncio.ensure_future(request(endpoint.conn_handler(session,
+                                                                                proxy=self._user_parameters.proxy()),
+                                                          **req_args))
+                    replies.append(reply)
+
+                result = await asyncio.gather(*replies, return_exceptions=True)
+                return tuple(result)
+            return ()
+        else:
+            raise NoPeerAvailable("", len(endpoints))
diff --git a/src/sakia/data/connectors/node.py b/src/sakia/data/connectors/node.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e59bc5b9f80a535a2ee7c07dabebc82ba2a5014
--- /dev/null
+++ b/src/sakia/data/connectors/node.py
@@ -0,0 +1,379 @@
+import asyncio
+import logging
+from asyncio import TimeoutError
+from socket import gaierror
+
+import aiohttp
+import jsonschema
+from PyQt5.QtCore import QObject, pyqtSignal
+from aiohttp.errors import ClientError, DisconnectedError
+from aiohttp.errors import WSServerHandshakeError, ClientResponseError
+
+from duniterpy.api import bma, errors
+from duniterpy.documents import BlockUID, MalformedDocumentError, BMAEndpoint
+from duniterpy.documents.peer import Peer, ConnectionHandler
+from sakia.decorators import asyncify
+from sakia.errors import InvalidNodeCurrency
+from ..entities.node import Node
+
+
+class NodeConnector(QObject):
+    """
+    A node is a peer send from the client point of view.
+    """
+    changed = pyqtSignal()
+    error = pyqtSignal()
+    identity_changed = pyqtSignal()
+    neighbour_found = pyqtSignal(Peer)
+
+    def __init__(self, node, user_parameters, session=None):
+        """
+        Constructor
+        """
+        super().__init__()
+        self.node = node
+        self._ws_tasks = {'block': None,
+                    'peer': None}
+        self._connected = {'block': False,
+                    'peer': False}
+        self._user_parameters = user_parameters
+        self.session = session
+        self._refresh_counter = 1
+        self._logger = logging.getLogger('sakia')
+
+    def __del__(self):
+        for ws in self._ws_tasks.values():
+            if ws:
+                ws.cancel()
+
+    @classmethod
+    async def from_address(cls, currency, secured, address, port, user_parameters):
+        """
+        Factory method to get a node from a given address
+        :param str currency: The node currency. None if we don't know\
+         the currency it should have, for example if its the first one we add
+        :param bool secured: True if the node uses https
+        :param str address: The node address
+        :param int port: The node port
+        :return: A new node
+        :rtype: sakia.core.net.Node
+        """
+        http_scheme = "https" if secured else "http"
+        ws_scheme = "ws" if secured else "wss"
+        session = aiohttp.ClientSession()
+        peer_data = await bma.network.peering(ConnectionHandler(http_scheme, ws_scheme, address, port,
+                                                                proxy=user_parameters.proxy(), session=session))
+
+        peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'],
+                                                      peer_data['signature']))
+
+        if currency and peer.currency != currency:
+            raise InvalidNodeCurrency(peer.currency, currency)
+
+        node = Node(peer.currency, peer.pubkey, peer.endpoints, peer.blockUID)
+        logging.getLogger('sakia').debug("Node from address : {:}".format(str(node)))
+
+        return cls(node, user_parameters, session=session)
+
+    @classmethod
+    def from_peer(cls, currency, peer, user_parameters):
+        """
+        Factory method to get a node from a peer document.
+        :param str currency: The node currency. None if we don't know\
+         the currency it should have, for example if its the first one we add
+        :param peer: The peer document
+        :return: A new node
+        :rtype: sakia.core.net.Node
+        """
+        if currency and peer.currency != currency:
+            raise InvalidNodeCurrency(peer.currency, currency)
+
+        node = Node(peer.currency, peer.pubkey, peer.endpoints, peer.blockUID)
+        logging.getLogger('sakia').debug("Node from peer : {:}".format(str(node)))
+
+        return cls(node, user_parameters, session=None)
+
+    async def safe_request(self, endpoint, request, proxy, req_args={}):
+        try:
+            conn_handler = endpoint.conn_handler(self.session, proxy=proxy)
+            data = await request(conn_handler, **req_args)
+            return data
+        except (ClientError, gaierror, TimeoutError, ConnectionRefusedError, DisconnectedError, ValueError) as e:
+            self._logger.debug("{0} : {1}".format(str(e), self.node.pubkey[:5]))
+            self.node.state = Node.OFFLINE
+        except jsonschema.ValidationError as e:
+            self._logger.debug(str(e))
+            self._logger.debug("Validation error : {0}".format(self.node.pubkey[:5]))
+            self.node.state = Node.CORRUPTED
+
+    async def init_session(self):
+        if not self.session:
+            self.session = aiohttp.ClientSession()
+
+    async def close_ws(self):
+        for ws in self._ws_tasks.values():
+            if ws:
+                ws.cancel()
+                await asyncio.sleep(0)
+        closed = False
+        while not closed:
+            for ws in self._ws_tasks.values():
+                if ws:
+                    closed = False
+                    break
+            else:
+                closed = True
+            await asyncio.sleep(0)
+        await self.session.close()
+        await asyncio.sleep(0)
+
+    def refresh(self, manual=False):
+        """
+        Refresh all data of this node
+        :param bool manual: True if the refresh was manually initiated
+        """
+        if not self._ws_tasks['block']:
+            self._ws_tasks['block'] = asyncio.ensure_future(self.connect_current_block())
+
+        if not self._ws_tasks['peer']:
+            self._ws_tasks['peer'] = asyncio.ensure_future(self.connect_peers())
+
+        if manual:
+            asyncio.ensure_future(self.request_peers())
+
+        if self._refresh_counter % 20 == 0 or manual:
+            self.refresh_summary()
+            self._refresh_counter = self._refresh_counter if manual else 1
+        else:
+            self._refresh_counter += 1
+
+    async def connect_current_block(self):
+        """
+        Connects to the websocket entry point of the node
+        If the connection fails, it tries the fallback mode on HTTP GET
+        """
+        for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
+            if not self._connected['block']:
+                try:
+                    conn_handler = endpoint.conn_handler(self.session, proxy=self._user_parameters.proxy())
+                    ws_connection = bma.ws.block(conn_handler)
+                    async with ws_connection as ws:
+                        self._connected['block'] = True
+                        self._logger.debug("Connected successfully to block ws : {0}"
+                                      .format(self.node.pubkey[:5]))
+                        async for msg in ws:
+                            if msg.tp == aiohttp.MsgType.text:
+                                self._logger.debug("Received a block : {0}".format(self.node.pubkey[:5]))
+                                block_data = bma.parse_text(msg.data, bma.ws.WS_BLOCk_SCHEMA)
+                                await self.refresh_block(block_data)
+                            elif msg.tp == aiohttp.MsgType.closed:
+                                break
+                            elif msg.tp == aiohttp.MsgType.error:
+                                break
+                except (WSServerHandshakeError,
+                        ClientResponseError, ValueError) as e:
+                    self._logger.debug("Websocket block {0} : {1} - {2}"
+                                  .format(type(e).__name__, str(e), self.node.pubkey[:5]))
+                    await self.request_current_block()
+                except (ClientError, gaierror, TimeoutError, DisconnectedError) as e:
+                    self._logger.debug("{0} : {1}".format(str(e), self.node.pubkey[:5]))
+                    self.node.state = Node.OFFLINE
+                    self.changed.emit()
+                except jsonschema.ValidationError as e:
+                    self._logger.debug(str(e))
+                    self._logger.debug("Validation error : {0}".format(self.node.pubkey[:5]))
+                    self.node.state = Node.CORRUPTED
+                    self.changed.emit()
+                finally:
+                    self._connected['block'] = False
+                    self._ws_tasks['block'] = None
+
+    async def request_current_block(self):
+        """
+        Request a node on the HTTP GET interface
+        If an error occurs, the node is considered offline
+        """
+        for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
+            try:
+                block_data = await self.safe_request(endpoint, bma.blockchain.current,
+                                                     proxy=self._user_parameters.proxy())
+                if not block_data:
+                    continue
+                await self.refresh_block(block_data)
+                return  # Do not try any more endpoint
+            except errors.DuniterError as e:
+                if e.ucode == errors.BLOCK_NOT_FOUND:
+                    self.node.previous_buid = BlockUID.empty()
+                    self.changed.emit()
+                else:
+                    self.node.state = Node.OFFLINE
+                    self.changed.emit()
+                self._logger.debug("Error in block reply of {0} : {1}}".format(self.node.pubkey[:5], str(e)))
+        else:
+            self._logger.debug("Could not connect to any BMA endpoint : {0}".format(self.node.pubkey[:5]))
+            self.node.state = Node.OFFLINE
+            self.changed.emit()
+
+    async def refresh_block(self, block_data):
+        """
+        Refresh the blocks of this node
+        :param dict block_data: The block data in json format
+        """
+        self.node.state = Node.ONLINE
+        if not self.node.current_buid or self.node.current_buid.sha_hash != block_data['hash']:
+            for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
+                conn_handler = endpoint.conn_handler(self.session,
+                                                     proxy=self._user_parameters.proxy())
+                self._logger.debug("Requesting {0}".format(conn_handler))
+                try:
+                    previous_block = await self.safe_request(endpoint, bma.blockchain.block,
+                                                             proxy=self._user_parameters.proxy(),
+                                                             req_args={'number': self.node.current_buid.number})
+                    if not previous_block:
+                        continue
+                    self.node.previous_buid = BlockUID(previous_block['number'], previous_block['hash'])
+                    return  # Do not try any more endpoint
+                except errors.DuniterError as e:
+                    if e.ucode == errors.BLOCK_NOT_FOUND:
+                        self.node.previous_buid = BlockUID.empty()
+                        self.node.current_buid = BlockUID.empty()
+                    else:
+                        self.node.state = Node.OFFLINE
+                    self._logger.debug("Error in previous block reply of {0} : {1}".format(self.node.pubkey[:5], str(e)))
+                finally:
+                    if self.node.current_buid != BlockUID(block_data['number'], block_data['hash']):
+                        self.node.current_buid = BlockUID(block_data['number'], block_data['hash'])
+                        self.node.current_ts = block_data['medianTime']
+                        self._logger.debug("Changed block {0} -> {1}".format(self.node.current_buid.number,
+                                                                        block_data['number']))
+                        self.changed.emit()
+            else:
+                self._logger.debug("Could not connect to any BMA endpoint : {0}".format(self.node.pubkey[:5]))
+                self.node.state = Node.OFFLINE
+                self.changed.emit()
+
+    @asyncify
+    async def refresh_summary(self):
+        """
+        Refresh the summary of this node
+        """
+        for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
+            try:
+                summary_data = await self.safe_request(endpoint, bma.node.summary,
+                                                       proxy=self._user_parameters.proxy())
+                if not summary_data:
+                    continue
+                self.node.software = summary_data["duniter"]["software"]
+                self.node.version = summary_data["duniter"]["version"]
+                self.node.state = Node.ONLINE
+                self.changed.emit()
+                return  # Break endpoints loop
+            except errors.DuniterError as e:
+                self.node.state = Node.OFFLINE
+                self._logger.debug("Error in summary of {0} : {1}".format(self.node.pubkey[:5], str(e)))
+                self.changed.emit()
+        else:
+            self._logger.debug("Could not connect to any BMA endpoint : {0}".format(self.node.pubkey[:5]))
+            self.node.state = Node.OFFLINE
+            self.changed.emit()
+
+    async def connect_peers(self):
+        """
+        Connects to the peer websocket entry point
+        If the connection fails, it tries the fallback mode on HTTP GET
+        """
+        for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
+            if not self._connected['peer']:
+                try:
+                    conn_handler = endpoint.conn_handler(self.session,
+                                                         proxy=self._user_parameters.proxy())
+                    ws_connection = bma.ws.peer(conn_handler)
+                    async with ws_connection as ws:
+                        self._connected['peer'] = True
+                        self._logger.debug("Connected successfully to peer ws : {0}".format(self.node.pubkey[:5]))
+                        async for msg in ws:
+                            if msg.tp == aiohttp.MsgType.text:
+                                self._logger.debug("Received a peer : {0}".format(self.node.pubkey[:5]))
+                                peer_data = bma.parse_text(msg.data, bma.ws.WS_PEER_SCHEMA)
+                                self.refresh_peer_data(peer_data)
+                            elif msg.tp == aiohttp.MsgType.closed:
+                                break
+                            elif msg.tp == aiohttp.MsgType.error:
+                                break
+                except (WSServerHandshakeError,
+                        ClientResponseError, ValueError) as e:
+                    self._logger.debug("Websocket peer {0} : {1} - {2}"
+                                       .format(type(e).__name__, str(e), self.node.pubkey[:5]))
+                    await self.request_peers()
+                except (ClientError, gaierror, TimeoutError, DisconnectedError) as e:
+                    self._logger.debug("{0} : {1}".format(str(e), self.node.pubkey[:5]))
+                    self.node.state = Node.OFFLINE
+                    self.changed.emit()
+                except jsonschema.ValidationError as e:
+                    self._logger.debug(str(e))
+                    self._logger.debug("Validation error : {0}".format(self.node.pubkey[:5]))
+                    self.node.state = Node.CORRUPTED
+                    self.changed.emit()
+                finally:
+                    self._connected['peer'] = False
+                    self._ws_tasks['peer'] = None
+        else:
+            self._logger.debug("Could not connect to any BMA endpoint : {0}".format(self.node.pubkey[:5]))
+            self.node.state = Node.OFFLINE
+            self.changed.emit()
+
+    async def request_peers(self):
+        """
+        Refresh the list of peers knew by this node
+        """
+        for endpoint in [e for e in self.node.endpoints if isinstance(e, BMAEndpoint)]:
+            try:
+                peers_data = await self.safe_request(endpoint, bma.network.peers,
+                                                     req_args={'leaves': 'true'},
+                                                     proxy=self._user_parameters.proxy())
+                if not peers_data:
+                    continue
+                self.node.state = Node.ONLINE
+                if peers_data['root'] != self.node.merkle_peers_root:
+                    leaves = [leaf for leaf in peers_data['leaves']
+                              if leaf not in self.node.merkle_peers_leaves]
+                    for leaf_hash in leaves:
+                        try:
+                            leaf_data = await self.safe_request(endpoint,
+                                                                bma.network.peers,
+                                                                proxy=self._user_parameters.proxy(),
+                                                                req_args={'leaf': leaf_hash})
+                            if not leaf_data:
+                                break
+                            self.refresh_peer_data(leaf_data['leaf']['value'])
+                        except (AttributeError, ValueError, errors.DuniterError) as e:
+                            self._logger.debug("{pubkey} : Incorrect peer data in {leaf}"
+                                          .format(pubkey=self.node.pubkey[:5],
+                                                  leaf=leaf_hash))
+                            self.node.state = Node.OFFLINE
+                        finally:
+                            self.changed.emit()
+                    else:
+                        self.node.merkle_peers_root = peers_data['root']
+                        self.node.merkle_peers_leaves = tuple(peers_data['leaves'])
+                return  # Break endpoints loop
+            except errors.DuniterError as e:
+                self._logger.debug("Error in peers reply : {0}".format(str(e)))
+                self.node.state = Node.OFFLINE
+                self.changed.emit()
+        else:
+            self._logger.debug("Could not connect to any BMA endpoint : {0}".format(self.node.pubkey[:5]))
+            self.node.state = Node.OFFLINE
+            self.changed.emit()
+
+    def refresh_peer_data(self, peer_data):
+        if "raw" in peer_data:
+            try:
+                str_doc = "{0}{1}\n".format(peer_data['raw'],
+                                            peer_data['signature'])
+                peer_doc = Peer.from_signed_raw(str_doc)
+                self.neighbour_found.emit(peer_doc)
+            except MalformedDocumentError as e:
+                self._logger.debug(str(e))
+        else:
+            self._logger.debug("Incorrect leaf reply")
diff --git a/src/sakia/data/entities/__init__.py b/src/sakia/data/entities/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c3eb79e32d6b493c85e1e229ecceefd3a6f013e
--- /dev/null
+++ b/src/sakia/data/entities/__init__.py
@@ -0,0 +1,10 @@
+from .identity import Identity
+from .blockchain import Blockchain, BlockchainParameters
+from .certification import Certification
+from .transaction import Transaction
+from .node import Node
+from .connection import Connection
+from .user_parameters import UserParameters
+from .app_data import AppData
+from .source import Source
+from .dividend import Dividend
\ No newline at end of file
diff --git a/src/sakia/data/entities/app_data.py b/src/sakia/data/entities/app_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3ed41ad1bac72fabd2dc19b3813addaac5b6127
--- /dev/null
+++ b/src/sakia/data/entities/app_data.py
@@ -0,0 +1,7 @@
+import attr
+
+
+@attr.s()
+class AppData:
+    profiles = attr.ib(default=attr.Factory(list))
+    default = attr.ib(convert=str, default="Default Profile")
diff --git a/src/sakia/data/entities/blockchain.py b/src/sakia/data/entities/blockchain.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ff30d4fefb3b5761b582e11cc4ac6f5b0ca033c
--- /dev/null
+++ b/src/sakia/data/entities/blockchain.py
@@ -0,0 +1,74 @@
+import attr
+from duniterpy.documents import block_uid, BlockUID
+
+
+@attr.s()
+class BlockchainParameters:
+    # The decimal percent growth of the UD every [dt] period
+    c = attr.ib(convert=float, default=0, cmp=False, hash=False)
+    # Time period between two UD in seconds
+    dt = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # UD(0), i.e. initial Universal Dividend
+    ud0 = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Minimum delay between 2 certifications of a same issuer, in seconds. Must be positive or zero
+    sig_period = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Maximum quantity of active certifications made by member
+    sig_stock = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Maximum age of a active signature (in seconds)
+    sig_validity = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Minimum quantity of signatures to be part of the WoT
+    sig_qty = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Maximum delay in seconds a certification can wait before being expired for non-writing
+    sig_window = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Maximum delay in seconds an identity can wait before being expired for non-writing
+    idty_window = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Maximum delay in seconds a membership can wait before being expired for non-writing
+    ms_window = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Minimum decimal percent of sentries to reach to match the distance rule
+    xpercent = attr.ib(convert=float, default=0, cmp=False, hash=False)
+    # Maximum age of an active membership( in seconds)
+    ms_validity = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Maximum distance between each WoT member and a newcomer
+    step_max = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Number of blocks used for calculating median time
+    median_time_blocks = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # The average time for writing 1 block (wished time) in seconds
+    avg_gen_time = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # The number of blocks required to evaluate again PoWMin value
+    dt_diff_eval = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # The decimal percent of previous issuers to reach for personalized difficulty
+    percent_rot = attr.ib(convert=float, default=0, cmp=False, hash=False)
+
+
+@attr.s()
+class Blockchain:
+    # Parameters in block 0
+    parameters = attr.ib(default=BlockchainParameters())
+    # block number and hash
+    current_buid = attr.ib(convert=block_uid, default=BlockUID.empty())
+    # Number of members
+    current_members_count = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Current monetary mass in units
+    current_mass = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Median time in seconds
+    median_time = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # 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=1, cmp=False, hash=False)
+    # Last UD base
+    last_ud_base = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Last UD base
+    last_ud_time = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Previous monetary mass in units
+    previous_mass = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Previous members count
+    previous_members_count = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Previous UD amount in units (multiply by 10^base)
+    previous_ud = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Previous UD base
+    previous_ud_base = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Previous UD base
+    previous_ud_time = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    # Currency name
+    currency = attr.ib(convert=str, default="", cmp=False, hash=False)
diff --git a/src/sakia/data/entities/certification.py b/src/sakia/data/entities/certification.py
new file mode 100644
index 0000000000000000000000000000000000000000..d59b7ff789e31ccdc2ab773bb8d4237173e613f3
--- /dev/null
+++ b/src/sakia/data/entities/certification.py
@@ -0,0 +1,13 @@
+import attr
+from duniterpy.documents import block_uid, BlockUID
+
+
+@attr.s()
+class Certification:
+    currency = attr.ib(convert=str)
+    certifier = attr.ib(convert=str)
+    certified = attr.ib(convert=str)
+    block = attr.ib(convert=int)
+    timestamp = attr.ib(convert=int)
+    signature = attr.ib(convert=str, cmp=False, hash=False)
+    written_on = attr.ib(convert=int, default=0, cmp=False, hash=False)
diff --git a/src/sakia/data/entities/connection.py b/src/sakia/data/entities/connection.py
new file mode 100644
index 0000000000000000000000000000000000000000..09b453740b106506ddad0520368884072731aad8
--- /dev/null
+++ b/src/sakia/data/entities/connection.py
@@ -0,0 +1,28 @@
+import attr
+from duniterpy.documents import block_uid, BlockUID
+from duniterpy.key import ScryptParams
+
+
+@attr.s()
+class Connection:
+    """
+    A connection represents a connection to a currency's network
+    It is defined by the currency name, and the key informations
+    used to connect to it. If the user is using an identity, it is defined here too.
+    """
+    currency = attr.ib(convert=str)
+    pubkey = attr.ib(convert=str)
+    salt = attr.ib(convert=str)
+    uid = attr.ib(convert=str, default="", cmp=False, hash=False)
+    scrypt_N = attr.ib(convert=int, default=4096)
+    scrypt_r = attr.ib(convert=int, default=16)
+    scrypt_p = attr.ib(convert=int, default=1)
+    blockstamp = attr.ib(convert=block_uid, default=BlockUID.empty(), cmp=False, hash=False)
+    password = attr.ib(init=False, convert=str, default="", cmp=False, hash=False)
+
+    def title(self):
+        return "@".join([self.uid, self.pubkey[:11]])
+
+    @property
+    def scrypt_params(self):
+        return ScryptParams(self.scrypt_N, self.scrypt_r, self.scrypt_p)
diff --git a/src/sakia/data/entities/dividend.py b/src/sakia/data/entities/dividend.py
new file mode 100644
index 0000000000000000000000000000000000000000..75c70b4e8721643b7924c05a3298b5c5f04164a8
--- /dev/null
+++ b/src/sakia/data/entities/dividend.py
@@ -0,0 +1,11 @@
+import attr
+
+
+@attr.s()
+class Dividend:
+    currency = attr.ib(convert=str)
+    pubkey = attr.ib(convert=str)
+    block_number = attr.ib(convert=int)
+    timestamp = attr.ib(convert=int)
+    amount = attr.ib(convert=int, cmp=False, hash=False)
+    base = attr.ib(convert=int, cmp=False, hash=False)
diff --git a/src/sakia/data/entities/identity.py b/src/sakia/data/entities/identity.py
new file mode 100644
index 0000000000000000000000000000000000000000..91a8e06d74ea04737128a038906e6577e8c7cef4
--- /dev/null
+++ b/src/sakia/data/entities/identity.py
@@ -0,0 +1,31 @@
+import attr
+from duniterpy.documents import block_uid, BlockUID
+from duniterpy.documents import Identity as IdentityDoc
+
+
+@attr.s()
+class Identity:
+    currency = attr.ib(convert=str)
+    pubkey = attr.ib(convert=str)
+    uid = attr.ib(convert=str, default="")
+    blockstamp = attr.ib(convert=block_uid, default=BlockUID.empty())
+    signature = attr.ib(convert=str, default="", cmp=False, hash=False)
+    # Mediantime of the block referenced by blockstamp
+    timestamp = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    written_on = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    revoked_on = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    outdistanced = attr.ib(convert=bool, default=True, cmp=False, hash=False)
+    member = attr.ib(validator=attr.validators.instance_of(bool), default=False, cmp=False, hash=False)
+    membership_buid = attr.ib(convert=block_uid, default=BlockUID.empty(), cmp=False, hash=False)
+    membership_timestamp = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    membership_type = attr.ib(convert=str, default='', validator=lambda s, a, t: t in ('', 'IN', 'OUT'), cmp=False, hash=False)
+    membership_written_on = attr.ib(convert=int, default=0, cmp=False, hash=False)
+
+    def document(self):
+        """
+        Creates a self cert document for a given identity
+        :param sakia.data.entities.Identity identity:
+        :return: the document
+        :rtype: duniterpy.documents.Identity
+        """
+        return IdentityDoc(10, self.currency, self.pubkey, self.uid, self.blockstamp, self.signature)
diff --git a/src/sakia/data/entities/node.py b/src/sakia/data/entities/node.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3f9cac2887ecb82fc14b1fe27edf93e499b5061
--- /dev/null
+++ b/src/sakia/data/entities/node.py
@@ -0,0 +1,81 @@
+import attr
+from duniterpy.documents import block_uid, endpoint
+
+
+def _tuple_of_endpoints(value):
+    if isinstance(value, tuple):
+        return value
+    elif isinstance(value, list):
+        l = [endpoint(e) for e in value]
+        return tuple(l)
+    elif isinstance(value, str):
+        list_of_str = value.split('\n')
+        conv = []
+        for s in list_of_str:
+            conv.append(endpoint(s))
+        return conv
+    else:
+        raise TypeError("Can't convert {0} to list of endpoints".format(value))
+
+
+def _tuple_of_hashes(ls):
+    if isinstance(ls, tuple):
+        return ls
+    elif isinstance(ls, list):
+        return tuple([str(a) for a in ls])
+    elif isinstance(ls, str):
+        if ls:  # if string is not empty
+            return tuple([str(a) for a in ls.split('\n')])
+        else:
+            return tuple()
+
+
+@attr.s()
+class Node:
+    """
+
+    A node can have multiple states :
+    - ONLINE : The node is available for requests
+    - OFFLINE: The node is disconnected
+    - DESYNCED : The node is online but is desynced from the network
+    - CORRUPTED : The node is corrupted, some weird behaviour is going on
+    """
+    MERKLE_EMPTY_ROOT = "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
+
+    ONLINE = 1
+    OFFLINE = 2
+    DESYNCED = 3
+    CORRUPTED = 4
+
+    # The currency handled by this node
+    currency = attr.ib(convert=str)
+    # The pubkey of the node
+    pubkey = attr.ib(convert=str)
+    # The endpoints of the node, in a list of Endpoint objects format
+    endpoints = attr.ib(convert=_tuple_of_endpoints, cmp=False)
+    # The previous block uid in /blockchain/current
+    peer_blockstamp = attr.ib(convert=block_uid, cmp=False)
+    # The uid of the owner of node
+    uid = attr.ib(convert=str, cmp=False, default="")
+    # The current block uid in /blockchain/current
+    current_buid = attr.ib(convert=block_uid, cmp=False, default=None)
+    # The current block time in /blockchain/current
+    current_ts = attr.ib(convert=int, cmp=False, default=0)
+    # The previous block uid in /blockchain/current
+    previous_buid = attr.ib(convert=block_uid, cmp=False, default=None)
+    # The state of the node in Sakia
+    state = attr.ib(convert=int, cmp=False, default=OFFLINE)
+    # The version of the software in /node/summary
+    software = attr.ib(convert=str, cmp=False, default="")
+    # The version of the software in /node/summary
+    version = attr.ib(convert=str, cmp=False, default="")
+    # Root of the merkle peers tree, default = sha256 of empty string
+    merkle_peers_root = attr.ib(convert=str, cmp=False,
+                                default=MERKLE_EMPTY_ROOT)
+    # Leaves of the merkle peers tree
+    merkle_peers_leaves = attr.ib(convert=_tuple_of_hashes, cmp=False, default=tuple())
+    # Define if this node is a root node in Sakia
+    root = attr.ib(convert=bool, cmp=False, default=False)
+    # If this node is a member or not
+    member = attr.ib(convert=bool, cmp=False, default=False)
+
diff --git a/src/sakia/data/entities/source.py b/src/sakia/data/entities/source.py
new file mode 100644
index 0000000000000000000000000000000000000000..96beeb2cf4a37e695d24f4269675ceefc6f030a4
--- /dev/null
+++ b/src/sakia/data/entities/source.py
@@ -0,0 +1,12 @@
+import attr
+
+
+@attr.s()
+class Source:
+    currency = attr.ib(convert=str)
+    pubkey = attr.ib(convert=str)
+    identifier = attr.ib(convert=str)
+    noffset = attr.ib(convert=int)
+    type = attr.ib(convert=str, validator=lambda i, a, s: s == 'T' or s == 'D')
+    amount = attr.ib(convert=int, cmp=False, hash=False)
+    base = attr.ib(convert=int, cmp=False, hash=False)
diff --git a/src/sakia/data/entities/transaction.py b/src/sakia/data/entities/transaction.py
new file mode 100644
index 0000000000000000000000000000000000000000..66b5a30b9af5c811e417c00ce6760e333bae02a8
--- /dev/null
+++ b/src/sakia/data/entities/transaction.py
@@ -0,0 +1,103 @@
+import attr
+from duniterpy.documents import block_uid
+from duniterpy.documents.transaction import reduce_base
+import math
+
+
+def parse_transaction_doc(tx_doc, pubkey, block_number, mediantime, txid):
+    """
+    Parse a transaction
+    :param duniterpy.documents.Transaction tx_doc: The tx json data
+    :param str pubkey: The pubkey of the transaction to parse, to know if its a receiver or issuer
+    :param int block_number: The block number where we found the tx
+    :param int mediantime: Median time on the network
+    :param int txid: The latest txid
+    :return: the found transaction
+    """
+    receivers = [o.conditions.left.pubkey for o in tx_doc.outputs
+                 if o.conditions.left.pubkey != tx_doc.issuers[0]]
+
+    if len(receivers) == 0:
+        receivers = [tx_doc.issuers[0]]
+
+    in_issuers = len([i for i in tx_doc.issuers
+                      if i == pubkey]) > 0
+    in_outputs = len([o for o in tx_doc.outputs
+                      if o.conditions.left.pubkey == pubkey]) > 0
+
+    if in_issuers or in_outputs:
+        # If the wallet pubkey is in the issuers we sent this transaction
+        if in_issuers:
+            outputs = [o for o in tx_doc.outputs
+                       if o.conditions.left.pubkey != pubkey]
+            amount = 0
+            for o in outputs:
+                amount += o.amount * math.pow(10, o.base)
+        # If we are not in the issuers,
+        # maybe we are in the recipients of this transaction
+        else:
+            outputs = [o for o in tx_doc.outputs
+                       if o.conditions.left.pubkey == pubkey]
+        amount = 0
+        for o in outputs:
+            amount += o.amount * math.pow(10, o.base)
+        amount, amount_base = reduce_base(amount, 0)
+
+        transaction = Transaction(currency=tx_doc.currency,
+                                  sha_hash=tx_doc.sha_hash,
+                                  written_block=block_number,
+                                  blockstamp=tx_doc.blockstamp,
+                                  timestamp=mediantime,
+                                  signature=tx_doc.signatures[0],
+                                  issuer=tx_doc.issuers[0],
+                                  receiver=receivers[0],
+                                  amount=amount,
+                                  amount_base=amount_base,
+                                  comment=tx_doc.comment,
+                                  txid=txid,
+                                  state=Transaction.VALIDATED,
+                                  raw=tx_doc.signed_raw())
+        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 int amount_base: the amount base
+    :param str comment: a comment
+    :param str txid: the transaction id to sort transctions
+    :param int state: the state of the transaction
+    """
+    TO_SEND = 0
+    AWAITING = 1
+    VALIDATED = 4
+    REFUSED = 8
+    DROPPED = 16
+
+    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)
+    local         = attr.ib(convert=bool, cmp=False, default=False)
+    raw           = attr.ib(convert=str, cmp=False, default="")
diff --git a/src/sakia/data/entities/user_parameters.py b/src/sakia/data/entities/user_parameters.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6e79736a2bd7d2d3b307877fc5adbfc70801f78
--- /dev/null
+++ b/src/sakia/data/entities/user_parameters.py
@@ -0,0 +1,23 @@
+import attr
+
+
+@attr.s()
+class UserParameters:
+    """
+    The user parameters entity
+    """
+    profile_name = attr.ib(convert=str, default="Default Profile")
+    lang = attr.ib(convert=str, default="en_US")
+    referential = attr.ib(convert=int, default=0)
+    expert_mode = attr.ib(convert=bool, default=False)
+    digits_after_comma = attr.ib(convert=int, default=2)
+    maximized = attr.ib(convert=bool, default=False)
+    notifications = attr.ib(convert=bool, default=True)
+    enable_proxy = attr.ib(convert=bool, default=False)
+    proxy_type = attr.ib(convert=int, default=0)
+    proxy_address = attr.ib(convert=str, default="")
+    proxy_port = attr.ib(convert=int, default=8080)
+
+    def proxy(self):
+        if self.enable_proxy is True:
+            return "http://{0}:{1}".format(self.proxy_address, self.proxy_port)
diff --git a/src/sakia/data/files/__init__.py b/src/sakia/data/files/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc136098a6d0981bebaaf8a3b1fca6fc76655beb
--- /dev/null
+++ b/src/sakia/data/files/__init__.py
@@ -0,0 +1,2 @@
+from .user_parameters import UserParametersFile
+from .app_data import AppDataFile
diff --git a/src/sakia/data/files/app_data.py b/src/sakia/data/files/app_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..921c8f24787488618833750a93e6972c5747a655
--- /dev/null
+++ b/src/sakia/data/files/app_data.py
@@ -0,0 +1,39 @@
+import attr
+import json
+import os
+import logging
+from ..entities import AppData
+
+
+@attr.s(frozen=True)
+class AppDataFile:
+    """
+    The repository for AppData
+    """
+    _file = attr.ib()
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+    filename = "appdata.json"
+
+    @classmethod
+    def in_config_path(cls, config_path):
+        return cls(os.path.join(config_path, AppDataFile.filename))
+
+    def save(self, app_data):
+        """
+        Commit a app_data to the database
+        :param sakia.data.entities.AppData app_data: the app_data to commit
+        """
+        with open(self._file, 'w') as outfile:
+            json.dump(attr.asdict(app_data), outfile, indent=4)
+
+    def load_or_init(self):
+        """
+        Update an existing app_data in the database
+        :param sakia.data.entities.AppData app_data: the app_data to update
+        """
+        try:
+            with open(self._file, 'r') as json_data:
+                app_data = AppData(**json.load(json_data))
+        except OSError:
+            app_data = AppData()
+        return app_data
diff --git a/src/sakia/data/files/user_parameters.py b/src/sakia/data/files/user_parameters.py
new file mode 100644
index 0000000000000000000000000000000000000000..f00116060ce20954552163d7682e7d06c8283cc8
--- /dev/null
+++ b/src/sakia/data/files/user_parameters.py
@@ -0,0 +1,44 @@
+import attr
+import json
+import os
+import logging
+from ..entities import UserParameters
+
+
+@attr.s(frozen=True)
+class UserParametersFile:
+    """
+    The repository for UserParameters
+    """
+    _file = attr.ib()
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+    filename = "parameters.json"
+
+    @classmethod
+    def in_config_path(cls, config_path, profile_name):
+        if not os.path.exists(os.path.join(config_path, profile_name)):
+            os.makedirs(os.path.join(config_path, profile_name))
+        return cls(os.path.join(config_path, profile_name, UserParametersFile.filename))
+
+    def save(self, user_parameters):
+        """
+        Commit a user_parameters to the database
+        :param sakia.data.entities.UserParameters user_parameters: the user_parameters to commit
+        """
+        if not os.path.exists(os.path.abspath(os.path.join(self._file, os.pardir))):
+            os.makedirs(os.path.abspath(os.path.join(self._file, os.pardir)))
+        with open(self._file, 'w') as outfile:
+            json.dump(attr.asdict(user_parameters), outfile, indent=4)
+        return user_parameters
+
+    def load_or_init(self):
+        """
+        Update an existing user_parameters in the database
+        :param sakia.data.entities.UserParameters user_parameters: the user_parameters to update
+        """
+        try:
+            with open(self._file, 'r') as json_data:
+                user_parameters = UserParameters(**json.load(json_data))
+        except OSError:
+            user_parameters = UserParameters()
+        return user_parameters
diff --git a/src/sakia/data/graphs/__init__.py b/src/sakia/data/graphs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed8f7ac56cb500a3e4a7cc3dd2ecfdd1d2388629
--- /dev/null
+++ b/src/sakia/data/graphs/__init__.py
@@ -0,0 +1,2 @@
+from .base_graph import BaseGraph
+from .wot_graph import WoTGraph
\ No newline at end of file
diff --git a/src/sakia/data/graphs/base_graph.py b/src/sakia/data/graphs/base_graph.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c4436d355687d20fa4c25ad4fd04d9c43214f14
--- /dev/null
+++ b/src/sakia/data/graphs/base_graph.py
@@ -0,0 +1,242 @@
+import logging
+import time
+import networkx
+from PyQt5.QtCore import QLocale, QDateTime, QObject
+from sakia.errors import NoPeerAvailable
+from .constants import EdgeStatus, NodeStatus
+from sakia.constants import MAX_CONFIRMATIONS
+
+
+class BaseGraph(QObject):
+    def __init__(self, app, blockchain_service, identities_service, nx_graph):
+        """
+        Init Graph instance
+        :param sakia.app.Application app: Application instance
+        :param sakia.services.BlockchainService blockchain_service: Blockchain service instance
+        :param sakia.services.IdentitiesService identities_service: Identities service instance
+        :param networkx.Graph nx_graph: The networkx graph
+        :return:
+        """
+        super().__init__()
+        self.app = app
+        self.identities_service = identities_service
+        self.blockchain_service = blockchain_service
+        # graph empty if None parameter
+        self.nx_graph = nx_graph if nx_graph else networkx.DiGraph()
+
+    def arc_status(self, cert_time):
+        """
+        Get arc status of a certification
+        :param int cert_time: the timestamp of the certification
+        :return: the certification time
+        """
+        parameters = self.blockchain_service.parameters()
+        #  arc considered strong during 75% of signature validity time
+        arc_strong = int(parameters.sig_validity * 0.75)
+        # display validity status
+        if (time.time() - cert_time) > arc_strong:
+            return EdgeStatus.WEAK
+        else:
+            return EdgeStatus.STRONG
+
+    async def node_status(self, node_identity, account_identity):
+        """
+        Return the status of the node depending
+        :param sakia.core.registry.Identity node_identity: The identity of the node
+        :param sakia.core.registry.Identity account_identity: The identity of the account displayed
+        :return: HIGHLIGHTED if node_identity is account_identity and OUT if the node_identity is not a member
+        :rtype: sakia.core.graph.constants.NodeStatus
+        """
+        # new node
+        node_status = NodeStatus.NEUTRAL
+        node_identity = await self.identities_service.load_requirements(node_identity)
+        if node_identity.pubkey == account_identity.pubkey:
+            node_status += NodeStatus.HIGHLIGHTED
+        if node_identity.member is False:
+            node_status += NodeStatus.OUT
+        return node_status
+
+    def offline_node_status(self, node_identity, account_identity):
+        """
+        Return the status of the node depending on its requirements. No network request.
+        :param sakia.core.registry.Identity node_identity: The identity of the node
+        :param sakia.core.registry.Identity account_identity: The identity of the account displayed
+        :return: HIGHLIGHTED if node_identity is account_identity and OUT if the node_identity is not a member
+        :rtype: sakia.core.graph.constants.NodeStatus
+        """
+        # new node
+        node_status = NodeStatus.NEUTRAL
+        if node_identity.pubkey == account_identity.pubkey:
+            node_status += NodeStatus.HIGHLIGHTED
+        if node_identity.member is False:
+            node_status += NodeStatus.OUT
+        return node_status
+
+    def confirmation_text(self, block_number):
+        """
+        Build confirmation text of an arc
+        :param int block_number: the block number of the certification
+        :return: the confirmation text
+        :rtype: str
+        """
+        try:
+            current_confirmations = min(max(self.blockchain_service.current_buid().number - block_number, 0), 6)
+
+            if MAX_CONFIRMATIONS > current_confirmations:
+                if self.app.parameters.expert_mode:
+                    return "{0}/{1}".format(current_confirmations, MAX_CONFIRMATIONS)
+                else:
+                    confirmation = current_confirmations / MAX_CONFIRMATIONS * 100
+                    return "{0} %".format(QLocale().toString(float(confirmation), 'f', 0))
+        except ValueError:
+            pass
+        return None
+
+    def is_sentry(self, nb_certs, nb_members):
+        """
+        Check if it is a sentry or not
+        :param int nb_certs: the number of certs
+        :param int nb_members: the number of members
+        :return: True if a sentry
+        """
+        Y = {
+            10: 2,
+            100: 4,
+            1000: 6,
+            10000: 12,
+            100000: 20
+        }
+        for k in reversed(sorted(Y.keys())):
+            if nb_members >= k:
+                return nb_certs >= Y[k]
+        return False
+
+    def add_certifier_node(self, certifier, identity, certification, node_status):
+        metadata = {
+            'text': certifier.uid,
+            'tooltip': certifier.pubkey,
+            'identity': certifier,
+            'status': node_status
+        }
+        self.nx_graph.add_node(certifier.pubkey, attr_dict=metadata)
+
+        arc_status = self.arc_status(certification.timestamp)
+        sig_validity = self.blockchain_service.parameters().sig_validity
+        arc = {
+            'status': arc_status,
+            'tooltip': QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(certification.timestamp + sig_validity).date(),
+                QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
+            ),
+            'cert_time': certification.timestamp,
+            'confirmation_text': self.confirmation_text(certification.block)
+        }
+        self.nx_graph.add_edge(certifier.pubkey, identity.pubkey, attr_dict=arc)
+
+    def add_certified_node(self, identity, certified, certification, node_status):
+        metadata = {
+            'text': certified.uid,
+            'tooltip': certified.pubkey,
+            'identity': certified,
+            'status': node_status
+        }
+        self.nx_graph.add_node(certified.pubkey, attr_dict=metadata)
+
+        arc_status = self.arc_status(certification.timestamp)
+        sig_validity = self.blockchain_service.parameters().sig_validity
+        arc = {
+            'status': arc_status,
+            'tooltip': QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(certification.timestamp + sig_validity).date(),
+                QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
+            ),
+            'cert_time': certification.timestamp,
+            'confirmation_text': self.confirmation_text(certification.block)
+        }
+
+        self.nx_graph.add_edge(identity.pubkey, certified.pubkey, attr_dict=arc)
+
+    def add_offline_certifier_list(self, certifier_list, identity, account_identity):
+        """
+        Add list of certifiers to graph
+        :param List[sakia.data.entities.Certification] certifier_list: List of certified from api
+        :param sakia.data.entities.Identity identity:   identity instance which is certified
+        :param sakia.data.entities.Identity account_identity:   Account identity instance
+        :return:
+        """
+        #  add certifiers of uid
+        for certification in tuple(certifier_list):
+            certifier = self.identities_service.get_identity(certification.certifier)
+            node_status = self.offline_node_status(certifier, account_identity)
+            self.add_certifier_node(certifier, identity, certification, node_status)
+
+    def add_offline_certified_list(self, certified_list, identity, account_identity):
+        """
+        Add list of certified from api to graph
+        :param List[sakia.data.entities.Certification] certified_list: List of certified from api
+        :param identity identity:   identity instance which is certifier
+        :param identity account_identity:   Account identity instance
+        :return:
+        """
+        # add certified by uid
+        for certification in tuple(certified_list):
+            certified = self.identities_service.get_identity(certification.certified)
+            node_status = self.offline_node_status(certified, account_identity)
+            self.add_certified_node(identity, certified, certification, node_status)
+
+    async def add_certifier_list(self, certifier_list, identity, account_identity):
+        """
+        Add list of certifiers to graph
+        :param List[sakia.data.entities.Certification] certifier_list: List of certified from api
+        :param sakia.data.entities.Identity identity:   identity instance which is certified
+        :param sakia.data.entities.Identity account_identity:   Account identity instance
+        :return:
+        """
+        try:
+            #  add certifiers of uid
+            for certification in tuple(certifier_list):
+                certifier = self.identities_service.get_identity(certification.certifier)
+                if not certifier:
+                    certifier = await self.identities_service.find_from_pubkey(certification.certifier)
+                node_status = await self.node_status(certifier, account_identity)
+                self.add_certifier_node(certifier, identity, certification, node_status)
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+
+    async def add_certified_list(self, certified_list, identity, account_identity):
+        """
+        Add list of certified from api to graph
+        :param List[sakia.data.entities.Certification] certified_list: List of certified from api
+        :param identity identity:   identity instance which is certifier
+        :param identity account_identity:   Account identity instance
+        :return:
+        """
+        try:
+            # add certified by uid
+            for certification in tuple(certified_list):
+                certified = self.identities_service.get_identity(certification.certified)
+                if not certified:
+                    certified = await self.identities_service.find_from_pubkey(certification.certified)
+                node_status = await self.node_status(certified, account_identity)
+                self.add_certified_node(identity, certified, certification, node_status)
+
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+
+    def add_identity(self, identity, status):
+        """
+        Add identity as a new node in graph
+        :param identity identity: identity instance
+        :param int status:  Optional, default=None, Node status (see sakia.gui.views.wot)
+        :param list edges:  Optional, default=None, List of edges (certified by identity)
+        :param list connected:  Optional, default=None, Public key list of the connected nodes around the identity
+        """
+        metadata = {
+            'text': identity.uid,
+            'tooltip': identity.pubkey,
+            'status': status,
+            'identity': identity
+        }
+        self.nx_graph.add_node(identity.pubkey, attr_dict=metadata)
diff --git a/src/sakia/core/graph/constants.py b/src/sakia/data/graphs/constants.py
similarity index 100%
rename from src/sakia/core/graph/constants.py
rename to src/sakia/data/graphs/constants.py
diff --git a/src/sakia/data/graphs/wot_graph.py b/src/sakia/data/graphs/wot_graph.py
new file mode 100644
index 0000000000000000000000000000000000000000..d93d0d57fda62de4375aa20e37622c6a6e073f07
--- /dev/null
+++ b/src/sakia/data/graphs/wot_graph.py
@@ -0,0 +1,51 @@
+import asyncio
+import networkx
+from .base_graph import BaseGraph
+
+
+class WoTGraph(BaseGraph):
+    def __init__(self, app, blockchain_service, identities_service, nx_graph=None):
+        """
+        Init WoTGraph instance
+        :param sakia.app.Application app: the app
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        :param sakia.services.IdentitiesService identities_service: the identities service
+        :param networkx.Graph nx_graph: The networkx graph
+        :return:
+        """
+        super().__init__(app, blockchain_service, identities_service, nx_graph)
+
+    async def initialize(self, center_identity, connection_identity):
+        self.nx_graph.clear()
+        node_status = await self.node_status(center_identity, connection_identity)
+
+        self.add_identity(center_identity, node_status)
+
+        # create Identity from node metadata
+        certifier_coro = asyncio.ensure_future(self.identities_service.load_certifiers_of(center_identity))
+        certified_coro = asyncio.ensure_future(self.identities_service.load_certified_by(center_identity))
+
+        certifier_list, certified_list = await asyncio.gather(*[certifier_coro, certified_coro])
+
+        # populate graph with certifiers-of
+        certifier_coro = asyncio.ensure_future(self.add_certifier_list(certifier_list,
+                                                                       center_identity, connection_identity))
+        # populate graph with certified-by
+        certified_coro = asyncio.ensure_future(self.add_certified_list(certified_list,
+                                                                       center_identity, connection_identity))
+
+        await asyncio.gather(*[certifier_coro, certified_coro], return_exceptions=True)
+        await asyncio.sleep(0)
+
+    def offline_init(self, center_identity, connection_identity):
+        node_status = self.offline_node_status(center_identity, connection_identity)
+
+        self.add_identity(center_identity, node_status)
+
+        # populate graph with certifiers-of
+        certifier_list = self.identities_service.certifications_received(center_identity.pubkey)
+        self.add_offline_certifier_list(certifier_list, center_identity, connection_identity)
+        # populate graph with certified-by
+        certified_list = self.identities_service.certifications_sent(center_identity.pubkey)
+        self.add_offline_certified_list(certified_list, center_identity, connection_identity)
diff --git a/src/sakia/data/processors/__init__.py b/src/sakia/data/processors/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..764f624b174de3e07a52f55d3b6db2fd81594156
--- /dev/null
+++ b/src/sakia/data/processors/__init__.py
@@ -0,0 +1,9 @@
+from .nodes import NodesProcessor
+from .identities import IdentitiesProcessor
+from .certifications import CertificationsProcessor
+from .blockchain import BlockchainProcessor
+from .connections import ConnectionsProcessor
+from .sources import SourcesProcessor
+from .transactions import TransactionsProcessor
+from .dividends import DividendsProcessor
+
diff --git a/src/sakia/data/processors/blockchain.py b/src/sakia/data/processors/blockchain.py
new file mode 100644
index 0000000000000000000000000000000000000000..037a1255235ed2bdf4d996b429ca77a6e7411cef
--- /dev/null
+++ b/src/sakia/data/processors/blockchain.py
@@ -0,0 +1,308 @@
+import attr
+import logging
+from sakia.errors import NoPeerAvailable
+from ..entities import Blockchain, BlockchainParameters
+from .nodes import NodesProcessor
+from ..connectors import BmaConnector
+from duniterpy.api import bma, errors
+from duniterpy.documents import Block, BMAEndpoint
+import asyncio
+
+
+@attr.s
+class BlockchainProcessor:
+    _repo = attr.ib()  # :type sakia.data.repositories.CertificationsRepo
+    _bma_connector = attr.ib()  # :type sakia.data.connectors.bma.BmaConnector
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def instanciate(cls, app):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        :rtype: sakia.data.processors.BlockchainProcessor
+        """
+        return cls(app.db.blockchains_repo,
+                   BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters))
+
+    async def timestamp(self, currency, blockstamp):
+        try:
+            block = await self._bma_connector.get(currency, bma.blockchain.block, {'number': blockstamp.number})
+            if block:
+                return block['medianTime']
+        except NoPeerAvailable as e:
+            self._logger.debug(str(e))
+        return 0
+
+    def current_buid(self, currency):
+        """
+        Get the local current blockuid
+        :rtype: duniterpy.documents.BlockUID
+        """
+        blockchain = self._repo.get_one(currency=currency)
+        return blockchain.current_buid
+
+    def time(self, currency):
+        """
+        Get the local current median time
+        :rtype: int
+        """
+        return self._repo.get_one(currency=currency).median_time
+
+    def parameters(self, currency):
+        """
+        Get the parameters of the blockchain
+        :rtype: sakia.data.entities.BlockchainParameters
+        """
+        return self._repo.get_one(currency=currency).parameters
+
+    def current_mass(self, currency):
+        """
+        Get the local current monetary mass
+        :rtype: int
+        """
+        return self._repo.get_one(currency=currency).current_mass
+
+    def current_members_count(self, currency):
+        """
+        Get the number of members in the blockchain
+        :rtype: int
+        """
+        return self._repo.get_one(currency=currency).current_members_count
+
+    def last_members_count(self, currency):
+        """
+        Get the last ud value and base
+        :rtype: int, int
+        """
+        return self._repo.get_one(currency=currency).last_members_count
+
+    def last_ud(self, currency):
+        """
+        Get the last ud value and base
+        :rtype: int, int
+        """
+        blockchain = self._repo.get_one(currency=currency)
+        try:
+            return blockchain.last_ud, blockchain.last_ud_base
+        except AttributeError:
+            pass
+
+    def last_ud_time(self, currency):
+        """
+        Get the last ud time
+        :rtype: int
+        """
+        blockchain = self._repo.get_one(currency=currency)
+        return blockchain.last_ud_time
+
+    def previous_monetary_mass(self, currency):
+        """
+        Get the local current monetary mass
+        :rtype: int
+        """
+        return self._repo.get_one(currency=currency).previous_mass
+
+    def previous_members_count(self, currency):
+        """
+        Get the local current monetary mass
+        :rtype: int
+        """
+        return self._repo.get_one(currency=currency).previous_members_count
+
+    def previous_ud(self, currency):
+        """
+        Get the previous ud value and base
+        :rtype: int, int
+        """
+        blockchain = self._repo.get_one(currency=currency)
+        return blockchain.previous_ud, blockchain.previous_ud_base
+
+    def previous_ud_time(self, currency):
+        """
+        Get the previous ud time
+        :rtype: int
+        """
+        blockchain = self._repo.get_one(currency=currency)
+        return blockchain.previous_ud_time
+
+    async def get_block(self, currency, number):
+        """
+        Get block documen at a given number
+        :param str currency:
+        :param int number:
+        :rtype: duniterpy.documents.Block
+        """
+        block = await self._bma_connector.get(currency, bma.blockchain.block, req_args={'number': number})
+        if block:
+            block_doc = Block.from_signed_raw("{0}{1}\n".format(block['raw'], block['signature']))
+            return block_doc
+
+
+    async def new_blocks_with_identities(self, currency):
+        """
+        Get blocks more recent than local blockuid
+        with identities
+        """
+        with_identities = []
+        future_requests = []
+        for req in (bma.blockchain.joiners,
+                    bma.blockchain.leavers,
+                    bma.blockchain.actives,
+                    bma.blockchain.excluded,
+                    bma.blockchain.newcomers):
+            future_requests.append(self._bma_connector.get(currency, req))
+        results = await asyncio.gather(*future_requests)
+
+        for res in results:
+            with_identities += res["result"]["blocks"]
+
+        local_current_buid = self.current_buid(currency)
+        return sorted([b for b in with_identities if b > local_current_buid.number])
+
+    async def new_blocks_with_money(self, currency):
+        """
+        Get blocks more recent than local block uid
+        with money data (tx and uds)
+        """
+        with_money = []
+        future_requests = []
+        for req in (bma.blockchain.ud, bma.blockchain.tx):
+            future_requests.append(self._bma_connector.get(currency, req))
+        results = await asyncio.gather(*future_requests)
+
+        for res in results:
+            with_money += res["result"]["blocks"]
+
+        local_current_buid = self.current_buid(currency)
+        return sorted([b for b in with_money if b > local_current_buid.number])
+
+    async def blocks(self, numbers, currency):
+        """
+        Get blocks from the network
+        :param List[int] numbers: list of blocks numbers to get
+        :return: the list of block documents
+        :rtype: List[duniterpy.documents.Block]
+        """
+        if numbers:
+            from_block = min(numbers)
+            to_block = max(numbers)
+            count = to_block - from_block
+
+            blocks_data = await self._bma_connector.get(currency, bma.blockchain.blocks, req_args={'count': count,
+                                                                                         'start': from_block})
+            blocks = []
+            for data in blocks_data:
+                if data['number'] in numbers:
+                    blocks.append(Block.from_signed_raw(data["raw"] + data["signature"] + "\n"))
+
+            return blocks
+        return []
+
+    async def initialize_blockchain(self, currency, log_stream):
+        """
+        Initialize blockchain for a given currency if no source exists locally
+        """
+        blockchain = self._repo.get_one(currency=currency)
+        if not blockchain:
+            blockchain = Blockchain(currency=currency)
+            log_stream("Requesting blockchain parameters")
+            try:
+                parameters = await self._bma_connector.get(currency, bma.blockchain.parameters, verify=False)
+                blockchain.parameters.ms_validity = parameters['msValidity']
+                blockchain.parameters.avg_gen_time = parameters['avgGenTime']
+                blockchain.parameters.c = parameters['c']
+                blockchain.parameters.dt = parameters['dt']
+                blockchain.parameters.dt_diff_eval = parameters['dtDiffEval']
+                blockchain.parameters.median_time_blocks = parameters['medianTimeBlocks']
+                blockchain.parameters.percent_rot = parameters['percentRot']
+                blockchain.parameters.idty_window = parameters['idtyWindow']
+                blockchain.parameters.ms_window = parameters['msWindow']
+                blockchain.parameters.sig_window = parameters['sigWindow']
+                blockchain.parameters.sig_period = parameters['sigPeriod']
+                blockchain.parameters.sig_qty = parameters['sigQty']
+                blockchain.parameters.sig_stock = parameters['sigStock']
+                blockchain.parameters.sig_validity = parameters['sigValidity']
+                blockchain.parameters.sig_qty = parameters['sigQty']
+                blockchain.parameters.sig_period = parameters['sigPeriod']
+                blockchain.parameters.ud0 = parameters['ud0']
+                blockchain.parameters.xpercent = parameters['xpercent']
+            except errors.DuniterError as e:
+                raise
+
+            log_stream("Requesting current block")
+            try:
+                current_block = await self._bma_connector.get(currency, bma.blockchain.current, verify=False)
+                signed_raw = "{0}{1}\n".format(current_block['raw'], current_block['signature'])
+                block = Block.from_signed_raw(signed_raw)
+                blockchain.current_buid = block.blockUID
+                blockchain.median_time = block.mediantime
+                blockchain.current_members_count = block.members_count
+            except errors.DuniterError as e:
+                if e.ucode != errors.NO_CURRENT_BLOCK:
+                    raise
+
+            log_stream("Requesting blocks with dividend")
+            with_ud = await self._bma_connector.get(currency, bma.blockchain.ud, verify=False)
+            blocks_with_ud = with_ud['result']['blocks']
+
+            if len(blocks_with_ud) > 0:
+                log_stream("Requesting last block with dividend")
+                try:
+                    index = max(len(blocks_with_ud) - 1, 0)
+                    block_number = blocks_with_ud[index]
+                    block_with_ud = await self._bma_connector.get(currency, bma.blockchain.block,
+                                                                  req_args={'number': block_number}, verify=False)
+                    if block_with_ud:
+                        blockchain.last_members_count = block_with_ud['membersCount']
+                        blockchain.last_ud = block_with_ud['dividend']
+                        blockchain.last_ud_base = block_with_ud['unitbase']
+                        blockchain.last_ud_time = block_with_ud['medianTime']
+                        blockchain.current_mass = block_with_ud['monetaryMass']
+                except errors.DuniterError as e:
+                    if e.ucode != errors.NO_CURRENT_BLOCK:
+                        raise
+
+                log_stream("Requesting previous block with dividend")
+                try:
+                    index = max(len(blocks_with_ud) - 2, 0)
+                    block_number = blocks_with_ud[index]
+                    block_with_ud = await self._bma_connector.get(currency, bma.blockchain.block,
+                                                                  req_args={'number': block_number}, verify=False)
+                    blockchain.previous_mass = block_with_ud['monetaryMass']
+                    blockchain.previous_members_count = block_with_ud['membersCount']
+                    blockchain.previous_ud = block_with_ud['dividend']
+                    blockchain.previous_ud_base = block_with_ud['unitbase']
+                    blockchain.previous_ud_time = block_with_ud['medianTime']
+                except errors.DuniterError as e:
+                    if e.ucode != errors.NO_CURRENT_BLOCK:
+                        raise
+
+            self._repo.insert(blockchain)
+
+    def handle_new_blocks(self, currency, blocks):
+        """
+        Initialize blockchain for a given currency if no source exists locally
+        :param List[duniterpy.documents.Block] blocks
+        """
+        blockchain = self._repo.get_one(currency=currency)
+        for block in sorted(blocks):
+            blockchain.current_buid = block.blockUID
+            blockchain.median_time = block.mediantime
+            blockchain.current_members_count = block.members_count
+            if block.ud:
+                blockchain.previous_mass = blockchain.current_mass
+                blockchain.previous_members_count = blockchain.last_members_count
+                blockchain.previous_ud = blockchain.last_ud
+                blockchain.previous_ud_base = blockchain.last_ud_base
+                blockchain.previous_ud_time = blockchain.last_ud_time
+                blockchain.current_mass = blockchain.current_mass + block.ud * block.members_count
+                blockchain.last_members_count = block.members_count
+                blockchain.last_ud = block.ud
+                blockchain.last_ud_base = block.unit_base
+                blockchain.last_ud_time = block.mediantime
+        self._repo.update(blockchain)
+
+    def remove_blockchain(self, currency):
+        self._repo.drop(self._repo.get_one(currency=currency))
+
diff --git a/src/sakia/data/processors/certifications.py b/src/sakia/data/processors/certifications.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9fc98773973e6d41115e6bfe884fc8764ff95ca
--- /dev/null
+++ b/src/sakia/data/processors/certifications.py
@@ -0,0 +1,186 @@
+import attr
+import asyncio
+from duniterpy.api import bma, errors
+from duniterpy.documents import block_uid
+from ..connectors import BmaConnector
+from ..processors import NodesProcessor
+from ..entities import Certification, Identity
+import sqlite3
+import logging
+from sakia.errors import NoPeerAvailable
+
+
+@attr.s
+class CertificationsProcessor:
+    _certifications_repo = attr.ib()  # :type sakia.data.repositories.CertificationsRepo
+    _identities_repo = attr.ib()  # :type sakia.data.repositories.IdentitiesRepo
+    _bma_connector = attr.ib()  # :type sakia.data.connectors.bma.BmaConnector
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def instanciate(cls, app):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        """
+        return cls(app.db.certifications_repo, app.db.identities_repo,
+                   BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters))
+
+    def certifications_sent(self, currency, pubkey):
+        """
+        Get the list of certifications sent for a given pubkey
+        :param str currency:
+        :param str pubkey:
+        :rtype: List[sakia.data.entities.Certification]
+        """
+        return self._certifications_repo.get_all(currency=currency, certifier=pubkey)
+
+    def certifications_received(self, currency, pubkey):
+        """
+        Get the list of certifications sent for a given pubkey
+        :param str currency:
+        :param str pubkey:
+        :rtype: List[sakia.data.entities.Certification]
+        """
+        return self._certifications_repo.get_all(currency=currency, certified=pubkey)
+
+    def cert_issuance_delay(self, currency, pubkey, parameters, blockchain_time):
+        """
+        Get the remaining time before being able to issue new certification.
+        :param str currency: the currency of the certifications
+        :param str pubkey: the pubkey of the certifications
+        :param sakia.data.entities.BlockchainParameters parameters: the parameters of the blockchain
+        :param int blockchain_time: the current time of the blockchain
+        :return: the remaining time
+        :rtype: int
+        """
+        certified = self._certifications_repo.get_latest_sent(currency=currency, pubkey=pubkey)
+        if certified and blockchain_time - certified.timestamp < parameters.sig_period:
+            return parameters.sig_period - (blockchain_time - certified.timestamp)
+        return 0
+
+    def create_certification(self, currency, cert, blockstamp):
+        """
+        Creates a certification and insert it in the db
+        :param duniterpy.documents.Certification cert:
+        :param duniterpy.documents.BlockUID blockstamp:
+        :return: the instanciated certification
+        :rtype: sakia.data.entities.Certification
+        """
+        cert = Certification(currency, cert.pubkey_from, cert.pubkey_to, cert.timestamp.number,
+                             0, cert.signatures[0], blockstamp)
+        self._certifications_repo.insert(cert)
+        return cert
+
+    def insert_or_update_certification(self, cert):
+        """
+        Commits a certification to the DB
+        :param sakia.data.entities.Certification cert:
+        :return:
+        """
+        try:
+            self._certifications_repo.insert(cert)
+        except sqlite3.IntegrityError:
+            self._certifications_repo.update(cert)
+
+    async def initialize_certifications(self, identity, log_stream):
+        """
+        Initialize certifications to and from a given identity
+        :param sakia.data.entities.Identity identity:
+        :param function log_stream:
+        """
+        log_stream("Requesting certifiers of data")
+        identities = list()
+        certifiers = list()
+        try:
+            data = await self._bma_connector.get(identity.currency, bma.wot.certifiers_of,
+                                                 req_args={'search': identity.pubkey}, verify=False)
+
+            for certifier_data in data['certifications']:
+                certification = Certification(currency=identity.currency,
+                                              certified=identity.pubkey,
+                                              certifier=certifier_data['pubkey'],
+                                              block=certifier_data['cert_time']['block'],
+                                              timestamp=certifier_data['cert_time']['medianTime'],
+                                              signature=certifier_data['signature'])
+                other_identity = Identity(currency=identity.currency,
+                                          pubkey=certifier_data['pubkey'],
+                                          uid=certifier_data['uid'],
+                                          blockstamp=certifier_data['sigDate'],
+                                          member=certifier_data['isMember'])
+                if certifier_data['written']:
+                    certification.written_on = certifier_data['written']['number']
+
+                certifiers.append(certification)
+                identities.append(other_identity)
+        except errors.DuniterError as e:
+            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
+                logging.debug("Certifiers of error : {0}".format(str(e)))
+            else:
+                raise
+
+        log_stream("Requesting certified by data")
+        certified = list()
+        try:
+            data = await self._bma_connector.get(identity.currency, bma.wot.certified_by,
+                                                 req_args={'search': identity.pubkey}, verify=False)
+            for certified_data in data['certifications']:
+                certification = Certification(currency=identity.currency,
+                                              certifier=identity.pubkey,
+                                              certified=certified_data['pubkey'],
+                                              block=certified_data['cert_time']['block'],
+                                              timestamp=certified_data['cert_time']['medianTime'],
+                                              signature=certified_data['signature'])
+                other_identity = Identity(currency=identity.currency,
+                                          pubkey=certified_data['pubkey'],
+                                          uid=certified_data['uid'],
+                                          blockstamp=certified_data['sigDate'],
+                                          member=certified_data['isMember'])
+                if certified_data['written']:
+                    certification.written_on = certified_data['written']['number']
+
+                certified.append(certification)
+                identities.append(other_identity)
+        except errors.DuniterError as e:
+            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
+                logging.debug("Certified by error : {0}".format(str(e)))
+            else:
+                raise
+
+        log_stream('Commiting certifications...')
+        for i, cert in enumerate(certifiers + certified):
+            log_stream('Certification {0}/{1}'.format(i, len(certifiers + certified)))
+            self.insert_or_update_certification(cert)
+            await asyncio.sleep(0)
+
+        log_stream('Commiting identities...')
+        for i, idty in enumerate(identities):
+            log_stream('Identity {0}/{1}'.format(i, len(identities)))
+            try:
+                self._identities_repo.insert(idty)
+            except sqlite3.IntegrityError:
+                self._identities_repo.update(idty)
+            await asyncio.sleep(0)
+
+    def cleanup_connection(self, connection, connections_pubkeys):
+        """
+        Cleanup connections data after removal
+        :param sakia.data.entities.Connection connection: removed connection
+        :param List[str] connections_pubkeys: pubkeys of existing connections
+        :return:
+        """
+        certifiers = self._certifications_repo.get_all(currency=connection.currency, certifier=connection.pubkey)
+        for c in certifiers:
+            self._certifications_repo.drop(c)
+            if c.certified not in connections_pubkeys:
+                idty = self._identities_repo.get_one(currency=connection.currency, pubkey=c.certified)
+                if idty:
+                    self._identities_repo.drop(idty)
+
+        certified = self._certifications_repo.get_all(currency=connection.currency, certified=connection.pubkey)
+        for c in certified:
+            self._certifications_repo.drop(c)
+            if c.certifier not in connections_pubkeys:
+                idty = self._identities_repo.get_one(currency=connection.currency, pubkey=c.certifier)
+                if idty:
+                    self._identities_repo.drop(idty)
\ No newline at end of file
diff --git a/src/sakia/data/processors/connections.py b/src/sakia/data/processors/connections.py
new file mode 100644
index 0000000000000000000000000000000000000000..879cc69c6d966f311a0ef1608fd33385841dcce4
--- /dev/null
+++ b/src/sakia/data/processors/connections.py
@@ -0,0 +1,42 @@
+import attr
+import sqlite3
+import logging
+
+
+@attr.s
+class ConnectionsProcessor:
+    _connections_repo = attr.ib()  # :type sakia.data.repositories.ConnectionsRepo
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def instanciate(cls, app):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        """
+        return cls(app.db.connections_repo)
+
+    def commit_connection(self, connection):
+        """
+        Saves a connection state in the db
+        :param sakia.data.entities.Connection connection: the connection updated
+        """
+        try:
+            self._connections_repo.insert(connection)
+        except sqlite3.IntegrityError:
+            self._connections_repo.update(connection)
+
+    def remove_connections(self, connection):
+        self._connections_repo.drop(connection)
+
+    def pubkeys(self):
+        return self._connections_repo.get_pubkeys()
+
+    def connections(self):
+        return self._connections_repo.get_all()
+
+    def connections_to(self, currency):
+        return self._connections_repo.get_all(currency=currency)
+
+    def currencies(self):
+        return self._connections_repo.get_currencies()
diff --git a/src/sakia/data/processors/dividends.py b/src/sakia/data/processors/dividends.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c10168face779efd62ff8a91e905a3a0a092616
--- /dev/null
+++ b/src/sakia/data/processors/dividends.py
@@ -0,0 +1,96 @@
+import attr
+import logging
+from ..entities import Dividend
+from .nodes import NodesProcessor
+from ..connectors import BmaConnector
+from duniterpy.api import bma
+from duniterpy.documents import Transaction
+import sqlite3
+import asyncio
+
+
+@attr.s
+class DividendsProcessor:
+    """
+    :param sakia.data.repositories.DividendsRepo _repo: the repository of the sources
+    :param sakia.data.connectors.bma.BmaConnector _bma_connector: the bma connector
+    """
+    _repo = attr.ib()
+    _bma_connector = attr.ib()
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def instanciate(cls, app):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        """
+        return cls(app.db.dividends_repo,
+                   BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters))
+
+    def commit(self, dividend):
+        try:
+            self._repo.insert(dividend)
+            return True
+        except sqlite3.IntegrityError:
+            self._logger.debug("Dividend already in db")
+        return False
+
+    async def initialize_dividends(self, connection, transactions, log_stream):
+        """
+        Request transactions from the network to initialize data for a given pubkey
+        :param sakia.data.entities.Connection connection:
+        :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx processor
+        :param function log_stream:
+        """
+        history_data = await self._bma_connector.get(connection.currency, bma.ud.history,
+                                                     req_args={'pubkey': connection.pubkey}, verify=False)
+        log_stream("Found {0} available dividends".format(len(history_data["history"]["history"])))
+        block_numbers = []
+        for ud_data in history_data["history"]["history"]:
+            dividend = Dividend(currency=connection.currency,
+                                pubkey=connection.pubkey,
+                                block_number=ud_data["block_number"],
+                                timestamp=ud_data["time"],
+                                amount=ud_data["amount"],
+                                base=ud_data["base"])
+            log_stream("Dividend of block {0}".format(dividend.block_number))
+            block_numbers.append(dividend.block_number)
+            try:
+                self._repo.insert(dividend)
+            except sqlite3.IntegrityError:
+                log_stream("Dividend already registered in database")
+
+        for tx in transactions:
+            txdoc = Transaction.from_signed_raw(tx.raw)
+            for input in txdoc.inputs:
+                if input.source == "D" and input.origin_id == connection.pubkey and input.index not in block_numbers:
+                    block = await self._bma_connector.get(connection.currency,
+                                                          bma.blockchain.block, req_args={'number': input.index},
+                                                          verify=False)
+                    await asyncio.sleep(0.5)
+
+                    dividend = Dividend(currency=connection.currency,
+                                        pubkey=connection.pubkey,
+                                        block_number=input.index,
+                                        timestamp=block["medianTime"],
+                                        amount=block["dividend"],
+                                        base=block["unitbase"])
+                    log_stream("Dividend of block {0}".format(dividend.block_number))
+                    try:
+                        self._repo.insert(dividend)
+                    except sqlite3.IntegrityError:
+                        log_stream("Dividend already registered in database")
+
+    def dividends(self, currency, pubkey):
+        return self._repo.get_all(currency=currency, pubkey=pubkey)
+
+    def cleanup_connection(self, connection):
+        """
+        Cleanup connection after removal
+        :param sakia.data.entities.Connection connection:
+        :return:
+        """
+        dividends = self._repo.get_all(currency=connection.currency, pubkey=connection.pubkey)
+        for d in dividends:
+            self._repo.drop(d)
diff --git a/src/sakia/data/processors/identities.py b/src/sakia/data/processors/identities.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9099ddf507da65bc29756148b5142ee1f5e437e
--- /dev/null
+++ b/src/sakia/data/processors/identities.py
@@ -0,0 +1,177 @@
+import attr
+import sqlite3
+import logging
+import asyncio
+from ..entities import Identity
+from ..connectors import BmaConnector
+from ..processors import NodesProcessor
+from duniterpy.api import bma, errors
+from duniterpy.key import SigningKey
+from duniterpy.documents import BlockUID, block_uid
+from duniterpy.documents import Identity as IdentityDoc
+from aiohttp.errors import ClientError
+from sakia.errors import NoPeerAvailable
+
+
+@attr.s
+class IdentitiesProcessor:
+    _identities_repo = attr.ib()  # :type sakia.data.repositories.IdentitiesRepo
+    _blockchain_repo = attr.ib()  # :type sakia.data.repositories.BlockchainRepo
+    _bma_connector = attr.ib()  # :type sakia.data.connectors.bma.BmaConnector
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def instanciate(cls, app):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        """
+        return cls(app.db.identities_repo, app.db.blockchains_repo,
+                   BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters))
+
+    async def find_from_pubkey(self, currency, pubkey):
+        """
+        Get the most recent identity corresponding to a pubkey
+        from the network and the local db
+        :param currency:
+        :param pubkey:
+        :rtype: sakia.data.entities.Identity
+        """
+        found_identity = Identity(currency=currency, pubkey=pubkey)
+        identities = self._identities_repo.get_all(currency=currency, pubkey=pubkey)
+        for idty in identities:
+            if idty.blockstamp > found_identity.blockstamp:
+                found_identity = idty
+        if not found_identity:
+            tries = 0
+            while tries < 3:
+                try:
+                    data = await self._bma_connector.get(currency, bma.wot.lookup, req_args={'search': pubkey})
+                    found_identity = None
+                    for result in data['results']:
+                        if result["pubkey"] == pubkey:
+                            uids = result['uids']
+                            for uid_data in uids:
+                                identity = Identity(currency, pubkey)
+                                identity.uid = uid_data['uid']
+                                identity.blockstamp = block_uid(uid_data['meta']['timestamp'])
+                                identity.signature = uid_data['self']
+                                if identity.blockstamp > found_identity.blockstamp:
+                                    found_identity = identity
+                except (errors.DuniterError, asyncio.TimeoutError, ClientError) as e:
+                    tries += 1
+                    self._logger.debug(str(e))
+                except NoPeerAvailable:
+                    self._logger.debug(str(e))
+        return found_identity
+
+    async def lookup(self, currency, text):
+        """
+        Get the list of identities corresponding to a pubkey
+        from the network and the local db
+        :param str currency:
+        :param str text: the text to lookup
+        :rtype: list[sakia.data.entities.Identity]
+        """
+        identities = self._identities_repo.find_all(currency=currency, text=text)
+        tries = 0
+        while tries < 3:
+            try:
+                data = await self._bma_connector.get(currency, bma.wot.lookup, req_args={'search': text})
+                for result in data['results']:
+                    pubkey = result['pubkey']
+                    for uid_data in result['uids']:
+                        if not uid_data['revoked']:
+                            identity = Identity(currency=currency,
+                                                pubkey=pubkey,
+                                                uid=uid_data['uid'],
+                                                blockstamp=uid_data['meta']['timestamp'],
+                                                signature=uid_data['self'])
+                            if identity not in identities:
+                                identities.append(identity)
+                break
+            except (errors.DuniterError, asyncio.TimeoutError, ClientError) as e:
+                tries += 1
+                self._logger.debug(str(e))
+        return identities
+
+    def get_identity(self, currency, pubkey, uid=""):
+        """
+        Return the identity corresponding to a given pubkey, uid and currency
+        If no UID is given, o
+        :param str currency:
+        :param str pubkey:
+        :param str uid: optionally, specify an uid to lookup
+
+        :rtype: sakia.data.entities.Identity
+        """
+        identities = self._identities_repo.get_all(currency=currency, pubkey=pubkey)
+        if identities:
+            recent = identities[0]
+            for i in identities:
+                if i.blockstamp > recent.blockstamp:
+                    if uid and i.uid == uid:
+                        recent = i
+                    elif not uid:
+                        recent = i
+            return recent
+
+    def insert_or_update_identity(self, identity):
+        """
+        Saves an identity state in the db
+        :param sakia.data.entities.Identity identity: the identity updated
+        """
+        try:
+            self._identities_repo.insert(identity)
+        except sqlite3.IntegrityError:
+            self._identities_repo.update(identity)
+
+    async def initialize_identity(self, identity, log_stream):
+        """
+        Initialize memberships and other data for given identity
+        :param sakia.data.entities.Identity identity:
+        :param function log_stream:
+        """
+        log_stream("Requesting membership data")
+        try:
+            memberships_data = await self._bma_connector.get(identity.currency, bma.blockchain.memberships,
+                                                             req_args={'search': identity.pubkey}, verify=False)
+            if block_uid(memberships_data['sigDate']) == identity.blockstamp \
+               and memberships_data['uid'] == identity.uid:
+                for ms in memberships_data['memberships']:
+                    if ms['written'] > identity.membership_written_on:
+                        identity.membership_buid = BlockUID(ms['blockNumber'], ms['blockHash'])
+                        identity.membership_type = ms['membership']
+
+                if identity.membership_buid:
+                    log_stream("Requesting membership timestamp")
+                    ms_block_data = await self._bma_connector.get(identity.currency, bma.blockchain.block,
+                                                                  req_args={'number': identity.membership_buid.number},
+                                                                  verify=False)
+                    if ms_block_data:
+                        identity.membership_timestamp = ms_block_data['medianTime']
+
+                log_stream("Requesting identity requirements status")
+
+                requirements_data = await self._bma_connector.get(identity.currency, bma.wot.requirements,
+                                                                  req_args={'search': identity.pubkey}, verify=False)
+                identity_data = next((data for data in requirements_data["identities"]
+                                      if data["pubkey"] == identity.pubkey))
+                identity.member = identity_data['membershipExpiresIn'] > 0 and not identity_data['outdistanced']
+                identity.outdistanced = identity_data['outdistanced']
+                self.insert_or_update_identity(identity)
+        except errors.DuniterError as e:
+            if e.ucode == errors.NO_MEMBER_MATCHING_PUB_OR_UID:
+                pass
+            else:
+                raise
+
+    def cleanup_connection(self, connection):
+        """
+        Cleanup after connection removal
+        :param sakia.data.entities.Connectionb connection:
+        :return:
+        """
+        identities = self._identities_repo.get_all(currency=connection.currency, pubkey=connection.pubkey)
+        for idty in identities:
+            self._identities_repo.drop(idty)
diff --git a/src/sakia/data/processors/nodes.py b/src/sakia/data/processors/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5d92bf0e321b66a6326f1530bed03a2c1cba92a
--- /dev/null
+++ b/src/sakia/data/processors/nodes.py
@@ -0,0 +1,136 @@
+import attr
+import sqlite3
+from ..entities import Node
+from duniterpy.documents import BlockUID, endpoint
+import logging
+
+
+@attr.s
+class NodesProcessor:
+    _repo = attr.ib()  # :type sakia.data.repositories.NodesRepo
+
+    @classmethod
+    def instanciate(cls, app):
+        return cls(app.db.nodes_repo)
+
+    def current_buid(self, currency):
+        """
+        Get current buid
+        :param str currency:
+        """
+        synced_node = self._repo.get_one(currency=currency, state=Node.ONLINE)
+        return synced_node.current_buid
+
+    def synced_nodes(self, currency):
+        """
+        Get nodes which are in the ONLINE state.
+        """
+        return self._repo.get_all(currency=currency, state=Node.ONLINE)
+
+    def synced_members_nodes(self, currency):
+        """
+        Get nodes which are in the ONLINE state.
+        """
+        return self._repo.get_all(currency=currency, state=Node.ONLINE, member=True)
+
+    def online_nodes(self, currency):
+        """
+        Get nodes which are in the ONLINE state.
+        """
+        return self._repo.get_all(currency=currency, state=Node.ONLINE) + \
+               self._repo.get_all(currency=currency, state=Node.DESYNCED)
+
+    def update_node(self, node):
+        """
+        Update node in the repository.
+        First involves basic checks about pubkey and primary key constraints.
+
+        :param sakia.data.entities.Node node: the node to update
+        """
+        other_node = self._repo.get_one(currency=node.currency, pubkey=node.pubkey)
+        if other_node:
+            self._repo.update(node)
+        else:
+            self._repo.insert(node)
+
+    def insert_node(self, node):
+        """
+        Update node in the repository.
+        First involves basic checks about pubkey and primary key constraints.
+
+        :param sakia.data.entities.Node node: the node to update
+        """
+        self._repo.insert(node)
+
+    def commit_node(self, node):
+        """
+        Saves a node state in the db
+        :param sakia.data.entities.Node node: the node updated
+        """
+        try:
+            self._repo.insert(node)
+        except sqlite3.IntegrityError:
+            self._repo.update(node)
+
+    def unknown_node(self, currency, pubkey):
+        """
+        Search for pubkey in the repository.
+        :param str pubkey: the pubkey to lookup
+        """
+        other_node = self._repo.get_one(currency=currency, pubkey=pubkey)
+        return other_node is None
+
+    def nodes(self, currency):
+        """
+        Get all knew nodes.
+        """
+        return self._repo.get_all(currency=currency)
+
+    def root_nodes(self, currency):
+        """
+        Get root nodes.
+        """
+        return self._repo.get_all(currency=currency, root=True)
+
+    def current_buid(self, currency):
+        """
+        Get the latest block considered valid
+        It is the most frequent last block of every known nodes
+        """
+        blocks_uids = [n.current_buid for n in self.synced_nodes(currency)]
+        if len(blocks_uids) > 0:
+            return blocks_uids[0]
+        else:
+            return BlockUID.empty()
+
+    def quality(self, currency):
+        """
+        Get a ratio of the synced nodes vs the rest
+        """
+        synced = len(self.synced_nodes(currency))
+        total = len(self.nodes(currency))
+        if total == 0:
+            ratio_synced = 0
+        else:
+            ratio_synced = synced / total
+        return ratio_synced
+
+    def update_peer(self, currency, peer):
+        """
+        Update the peer of a node
+        :param str currency: the currency of the peer
+        :param peer:
+        :return:
+        """
+        node = self._repo.get_one(pubkey=peer.pubkey, currency=currency)
+        if node and node.peer_blockstamp < peer.blockUID:
+            logging.debug("Update node : {0}".format(peer.pubkey[:5]))
+            node.endpoints = tuple(peer.endpoints)
+            node.peer_blockstamp = peer.blockUID
+            self._repo.update(node)
+        return node
+
+    def drop_all(self):
+        nodes = self._repo.get_all()
+        for n in nodes:
+            self._repo.drop(n)
\ No newline at end of file
diff --git a/src/sakia/data/processors/sources.py b/src/sakia/data/processors/sources.py
new file mode 100644
index 0000000000000000000000000000000000000000..36055a5ac27e305f80d96316f790a6c3e3da7e2f
--- /dev/null
+++ b/src/sakia/data/processors/sources.py
@@ -0,0 +1,83 @@
+import attr
+import sqlite3
+from ..entities import Source
+from .nodes import NodesProcessor
+from ..connectors import BmaConnector
+from duniterpy.api import bma, errors
+
+
+@attr.s
+class SourcesProcessor:
+    """
+    :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):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        """
+        return cls(app.db.sources_repo,
+                   BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters))
+
+    def commit(self, source):
+        try:
+            self._repo.insert(source)
+        except sqlite3.IntegrityError:
+            self._repo.update(source)
+
+    async def initialize_sources(self, currency, pubkey, log_stream):
+        """
+        Initialize sources for a given pubkey if no source exists locally
+        """
+        log_stream("Requesting sources")
+        try:
+            sources_data = await self._bma_connector.get(currency, bma.tx.sources,
+                                                         req_args={'pubkey': pubkey}, verify=False)
+
+            log_stream("Found {0} sources".format(len(sources_data['sources'])))
+            for i, s in enumerate(sources_data['sources']):
+                source = Source(currency=currency, pubkey=pubkey,
+                                identifier=s['identifier'],
+                                type=s['type'],
+                                noffset=s['noffset'],
+                                amount=s['amount'],
+                                base=s['base'])
+                self.commit(source)
+                log_stream("{0}/{1} sources".format(i, len(sources_data['sources'])))
+        except errors.DuniterError as e:
+            raise
+
+    def amount(self, currency, pubkey):
+        """
+        Get the amount value of the sources for a given pubkey
+        :param str currency: the currency of the sources
+        :param str pubkey: the pubkey owning the sources
+        :return:
+        """
+        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)
+
+    def drop_all_of(self, currency, pubkey):
+        self._repo.drop_all(currency=currency, pubkey=pubkey)
diff --git a/src/sakia/data/processors/transactions.py b/src/sakia/data/processors/transactions.py
new file mode 100644
index 0000000000000000000000000000000000000000..35435d2f2b0996969c03e5991e81002fd8a8a2c4
--- /dev/null
+++ b/src/sakia/data/processors/transactions.py
@@ -0,0 +1,179 @@
+import logging
+import attr
+import asyncio
+import sqlite3
+from ..entities import Transaction
+from ..entities.transaction import parse_transaction_doc
+from .nodes import NodesProcessor
+from . import tx_lifecycle
+from ..connectors import BmaConnector
+from duniterpy.api import bma
+from duniterpy.documents import Transaction as TransactionDoc
+
+
+@attr.s
+class TransactionsProcessor:
+    _repo = attr.ib()  # :type sakia.data.repositories.SourcesRepo
+    _bma_connector = attr.ib()  # :type sakia.data.connectors.bma.BmaConnector
+    _table_states = attr.ib(default=attr.Factory(dict))
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def instanciate(cls, app):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        """
+        return cls(app.db.transactions_repo,
+                   BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters))
+
+    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
+        :param str currency:
+        :param str pubkey:
+        :return: the list of Transaction entities
+        :rtype: List[sakia.data.entities.Transaction]
+        """
+        return self._repo.get_transfers(currency, pubkey)
+
+    def _try_transition(self, tx, transition_key, *inputs):
+        """
+        Try the transition defined by the given transition_key
+        with inputs
+        :param sakia.data.entities.Transaction tx: the transaction
+        :param tuple transition_key: The transition key in the table states
+        :param tuple inputs: The inputs
+        :return: True if the transition was applied
+        :rtype: bool
+        """
+        if len(inputs) == len(transition_key[1]):
+            for i, input in enumerate(inputs):
+                if not isinstance(input, transition_key[1][i]):
+                    return False
+            for transition in tx_lifecycle.states[transition_key]:
+                if transition[0](tx, *inputs):
+                    if tx.sha_hash:
+                        self._logger.debug("{0} : {1} --> {2}".format(tx.sha_hash[:5], tx.state,
+                                                                      transition[2]))
+                    else:
+                        self._logger.debug("Unsent transfer : {0} --> {1}".format(tx.state,
+                                                                                  transition[2]))
+
+                    # If the transition changes data, apply changes
+                    if transition[1]:
+                        transition[1](tx, *inputs)
+                    tx.state = transition[2]
+                    return True
+        return False
+
+    def commit(self, tx):
+        try:
+            self._repo.insert(tx)
+        except sqlite3.IntegrityError:
+            self._repo.update(tx)
+
+    def find_by_hash(self, sha_hash):
+        return self._repo.get_one(sha_hash=sha_hash)
+
+    def awaiting(self, currency):
+        return self._repo.get_all(currency=currency, state=Transaction.AWAITING)
+
+    def run_state_transitions(self, tx, *inputs):
+        """
+        Try all current state transitions with inputs
+        :param sakia.data.entities.Transaction tx: the transaction
+        :param tuple inputs: The inputs passed to the transitions
+        :return: True if the transaction changed state
+        :rtype: bool
+        """
+        transition_keys = [k for k in tx_lifecycle.states.keys() if k[0] == tx.state]
+        for key in transition_keys:
+            if self._try_transition(tx, key, *inputs):
+                self._repo.update(tx)
+                return True
+        return False
+
+    def cancel(self, tx):
+        """
+        Cancel a local transaction
+        :param sakia.data.entities.Transaction tx: the transaction
+        """
+        self.run_state_transitions(tx, ())
+
+    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
+        The txdoc is saved as the transfer txdoc.
+
+        :param sakia.data.entities.Transaction tx: the transaction
+        :param txdoc: A transaction duniterpy object
+        :param currency: The community target of the transaction
+        """
+        self._logger.debug(txdoc.signed_raw())
+        self._repo.insert(tx)
+        responses = await self._bma_connector.broadcast(currency, bma.tx.process, req_args={'transaction': txdoc.signed_raw()})
+        result = (False, "")
+        for r in responses:
+            if isinstance(r, BaseException):
+                result = (False, str(r))
+            elif r.status == 200:
+                result = (True, (await r.json()))
+            elif not result[0]:
+                result = (False, (await r.text()))
+            else:
+                await r.text()
+        self.run_state_transitions(tx, [r.status for r in responses if not isinstance(r, BaseException)])
+        return result, tx
+
+    async def initialize_transactions(self, connection, log_stream):
+        """
+        Request transactions from the network to initialize data for a given pubkey
+        :param sakia.data.entities.Connection connection:
+        :param function log_stream:
+        """
+        history_data = await self._bma_connector.get(connection.currency, bma.tx.history,
+                                                     req_args={'pubkey': connection.pubkey}, verify=False)
+        txid = 0
+        nb_tx = len(history_data["history"]["sent"]) + len(history_data["history"]["received"])
+        log_stream("Found {0} transactions".format(nb_tx))
+        transactions = []
+        for sent_data in history_data["history"]["sent"] + history_data["history"]["received"]:
+            sent = TransactionDoc.from_bma_history(history_data["currency"], sent_data)
+            log_stream("{0}/{1} transactions".format(txid, nb_tx))
+            try:
+                tx = parse_transaction_doc(sent, connection.pubkey, sent_data["block_number"],
+                                           sent_data["time"], txid)
+                self._repo.insert(tx)
+                transactions.append(tx)
+            except sqlite3.IntegrityError:
+                log_stream("Transaction already registered in database")
+            await asyncio.sleep(0)
+            txid += 1
+        return transactions
+
+    def cleanup_connection(self, connection, connections_pubkeys):
+        """
+        Cleanup connections data after removal
+        :param sakia.data.entities.Connection connection: removed connection
+        :param List[str] connections_pubkeys: pubkeys of existing connections
+        :return:
+        """
+        sent = self._repo.get_all(currency=connection.currency, issuer=connection.pubkey)
+        for tx in sent:
+            if tx.receiver not in connections_pubkeys:
+                self._repo.drop(tx)
+        received = self._repo.get_all(currency=connection.currency, receiver=connection.pubkey)
+        for tx in received:
+            if tx.issuer not in connections_pubkeys:
+                self._repo.drop(tx)
diff --git a/src/sakia/data/processors/tx_lifecycle.py b/src/sakia/data/processors/tx_lifecycle.py
new file mode 100644
index 0000000000000000000000000000000000000000..37b89567dcc9102a846434ce3eafc568b02ebe24
--- /dev/null
+++ b/src/sakia/data/processors/tx_lifecycle.py
@@ -0,0 +1,127 @@
+import time
+from sakia.data.entities import Transaction
+from duniterpy.documents import Block
+
+
+def _found_in_block(tx, block):
+    """
+    Check if the transaction can be found in the blockchain
+    :param sakia.data.entities.Transaction tx: the transaction
+    :param duniterpy.documents.Block block: The block to check for the transaction
+    :return: True if the transaction was found
+    :rtype: bool
+    """
+    for block_tx in block.transactions:
+        if block_tx.sha_hash == tx.sha_hash:
+            return True
+
+
+def _broadcast_success(tx, ret_codes):
+    """
+    Check if the retcode is 200 after a POST
+    :param sakia.data.entities.Transaction tx: the transaction
+    :param list ret_codes: The POST return codes of the broadcast
+    :param duniterpy.documents.Block block: The current block used for transition.
+    :return: True if the post was successful
+    :rtype: bool
+    """
+    return 200 in ret_codes
+
+
+def _broadcast_failure(tx, ret_codes):
+    """
+    Check if no retcode is 200 after a POST
+    :param sakia.data.entities.Transaction tx: the transaction
+    :param list ret_codes: The POST return codes of the broadcast
+    :return: True if the post was failed
+    :rtype: bool
+    """
+    return 200 not in ret_codes
+
+
+def _rollback_and_removed(tx, rollback, block):
+    """
+    Check if the transfer is not in the block anymore
+
+    :param sakia.data.entities.Transaction tx: the transaction
+    :param bool rollback: True if we are in a rollback procedure
+    :param duniterpy.documents.Block block: The block to check for the transaction
+    :return: True if the transfer is not found in the block
+    """
+    if rollback:
+        if not block or block.blockUID != tx.blockstamp:
+            return True
+        else:
+            return tx.sha_hash not in [t.sha_hash for t in block.transactions]
+    return False
+
+
+def _rollback_and_local(tx, rollback, block):
+    """
+    Check if the transfer is not in the block anymore
+
+    :param sakia.data.entities.Transaction tx: the transaction
+    :param bool rollback: True if we are in a rollback procedure
+    :param duniterpy.documents.Block block: The block to check for the transaction
+    :return: True if the transfer is found in the block
+    """
+    if rollback and tx.local and block.blockUID == tx.blockstamp:
+        return tx.sha_hash not in [t.sha_hash for t in block.transactions]
+    return False
+
+
+def _is_locally_created(tx):
+    """
+    Check if we can send back the transaction if it was locally created
+
+    :param sakia.data.entities.Transaction tx: the transaction
+    :return: True if the transaction was locally created
+    """
+    return tx.local
+
+
+def _be_validated(tx, block):
+    """
+    Action when the transfer ins found in a block
+
+    :param sakia.data.entities.Transaction tx: the transaction
+    :param bool rollback: True if we are in a rollback procedure
+    :param duniterpy.documents.Block block: The block checked
+    """
+    tx.blockstamp = block.blockUID
+    tx.timestamp = block.mediantime
+
+
+def _drop(tx):
+    """
+    Cancel the transfer locally.
+    The transfer state becomes TransferState.DROPPED.
+    :param sakia.data.entities.Transaction tx: the transaction
+    """
+    tx.blockstamp = None
+
+
+# Dict containing states of a transfer :
+# keys are a tuple containg (current_state, transition_parameters)
+# values are tuples containing (transition_test, transition_success, new_state)
+states = {
+            (Transaction.TO_SEND, (list,)):
+                (
+                    (_broadcast_success, None, Transaction.AWAITING),
+                    (lambda tx, l: _broadcast_failure(tx, l), None, Transaction.REFUSED),
+                ),
+            (Transaction.TO_SEND, ()):
+                ((_is_locally_created, _drop, Transaction.DROPPED),),
+
+            (Transaction.AWAITING, (Block,)):
+                ((_found_in_block, _be_validated, Transaction.VALIDATED),),
+
+            (Transaction.VALIDATED, (bool,)):
+                (
+                    (_rollback_and_removed, lambda tx, r: _drop(tx), Transaction.DROPPED),
+                    (_rollback_and_local, None, Transaction.AWAITING),
+                ),
+
+            (Transaction.REFUSED, ()):
+                ((_is_locally_created, _drop, Transaction.DROPPED),)
+        }
diff --git a/src/sakia/data/repositories/__init__.py b/src/sakia/data/repositories/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a2eca6386d2ef45810e49dc76ff53328f5c1191
--- /dev/null
+++ b/src/sakia/data/repositories/__init__.py
@@ -0,0 +1,9 @@
+from .identities import IdentitiesRepo
+from .blockchains import BlockchainsRepo
+from .meta import SakiaDatabase
+from .certifications import CertificationsRepo
+from .transactions import TransactionsRepo
+from .nodes import NodesRepo
+from .connections import ConnectionsRepo
+from .sources import SourcesRepo
+from .dividends import DividendsRepo
diff --git a/src/sakia/data/repositories/blockchains.py b/src/sakia/data/repositories/blockchains.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6dd9fb4edc88a7cce688536fd1e31747843723d
--- /dev/null
+++ b/src/sakia/data/repositories/blockchains.py
@@ -0,0 +1,121 @@
+from typing import List
+
+import attr
+
+from ..entities import Blockchain, BlockchainParameters
+
+
+@attr.s(frozen=True)
+class BlockchainsRepo:
+    """The repository for Blockchain entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Blockchain.currency,)
+
+    def insert(self, blockchain):
+        """
+        Commit a blockchain to the database
+        :param sakia.data.entities.Blockchain blockchain: the blockchain to commit
+        """
+        blockchain_tuple = attr.astuple(blockchain.parameters) \
+                           + attr.astuple(blockchain, filter=attr.filters.exclude(Blockchain.parameters))
+        values = ",".join(['?'] * len(blockchain_tuple))
+        self._conn.execute("INSERT INTO blockchains VALUES ({0})".format(values), blockchain_tuple)
+
+    def update(self, blockchain):
+        """
+        Update an existing blockchain in the database
+        :param sakia.data.entities.Blockchain blockchain: the blockchain to update
+        """
+        updated_fields = attr.astuple(blockchain, filter=attr.filters.exclude(
+            Blockchain.parameters, *BlockchainsRepo._primary_keys))
+        where_fields = attr.astuple(blockchain, filter=attr.filters.include(*BlockchainsRepo._primary_keys))
+        self._conn.execute("""UPDATE blockchains SET
+                          current_buid=?,
+                          current_members_count=?,
+                          current_mass=?,
+                          median_time=?,
+                          last_members_count=?,
+                          last_ud=?,
+                          last_ud_base=?,
+                          last_ud_time=?,
+                          previous_mass=?,
+                          previous_members_count=?,
+                          previous_ud=?,
+                          previous_ud_base=?,
+                          previous_ud_time=?
+                           WHERE
+                          currency=?""",
+                           updated_fields + where_fields)
+
+    def get_one(self, **search):
+        """
+        Get an existing blockchain in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Blockchain
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        if filters:
+            request = "SELECT * FROM blockchains WHERE {filters}".format(filters=" AND ".join(filters))
+        else:
+            request = "SELECT * FROM blockchains"
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Blockchain(BlockchainParameters(*data[:16]), *data[17:])
+
+    def get_all(self, offset=0, limit=1000, sort_by="currency", sort_order="ASC", **search) -> List[Blockchain]:
+        """
+        Get all existing blockchain in the database corresponding to the search
+        :param int offset: offset in results to paginate
+        :param int limit: limit results to paginate
+        :param str sort_by: column name to sort by
+        :param str sort_order: sort order ASC or DESC
+        :param dict search: the criterions of the lookup
+        :rtype: [sakia.data.entities.Blockchain]
+        """
+        filters = []
+        values = []
+        if search:
+            for k, v in search.items():
+                filters.append("{k}=?".format(k=k))
+                values.append(v)
+
+            request = """SELECT * FROM blockchains WHERE {filters}
+                          ORDER BY {sort_by} {sort_order}
+                          LIMIT {limit} OFFSET {offset}""".format(
+                filters=" AND ".join(filters),
+                offset=offset,
+                limit=limit,
+                sort_by=sort_by,
+                sort_order=sort_order
+            )
+            c = self._conn.execute(request, tuple(values))
+        else:
+            request = """SELECT * FROM blockchains
+                          ORDER BY {sort_by} {sort_order}
+                          LIMIT {limit} OFFSET {offset}""".format(
+                offset=offset,
+                limit=limit,
+                sort_by=sort_by,
+                sort_order=sort_order
+            )
+            c = self._conn.execute(request)
+        datas = c.fetchall()
+        if datas:
+            return [Blockchain(BlockchainParameters(*data[:16]), *data[17:]) for data in datas]
+        return []
+
+    def drop(self, blockchain):
+        """
+        Drop an existing blockchain from the database
+        :param sakia.data.entities.Blockchain blockchain: the blockchain to update
+        """
+        where_fields = attr.astuple(blockchain, filter=attr.filters.include(*BlockchainsRepo._primary_keys))
+        self._conn.execute("DELETE FROM blockchains WHERE currency=?", where_fields)
diff --git a/src/sakia/data/repositories/certifications.py b/src/sakia/data/repositories/certifications.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ef13cdde7549ec37e08eeeef6552ca70fe09c11
--- /dev/null
+++ b/src/sakia/data/repositories/certifications.py
@@ -0,0 +1,109 @@
+import attr
+
+from ..entities import Certification
+
+
+@attr.s(frozen=True)
+class CertificationsRepo:
+    """The repository for Communities entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Certification.currency, Certification.certified,
+                     Certification.certifier, Certification.block,)
+
+    def insert(self, certification):
+        """
+        Commit a certification to the database
+        :param sakia.data.entities.Certification certification: the certification to commit
+        """
+        certification_tuple = attr.astuple(certification)
+        values = ",".join(['?'] * len(certification_tuple))
+        self._conn.execute("INSERT INTO certifications VALUES ({0})".format(values), certification_tuple)
+
+    def update(self, certification):
+        """
+        Update an existing certification in the database
+        :param sakia.data.entities.Certification certification: the certification to update
+        """
+        updated_fields = attr.astuple(certification, filter=attr.filters.exclude(*CertificationsRepo._primary_keys))
+        where_fields = attr.astuple(certification, filter=attr.filters.include(*CertificationsRepo._primary_keys))
+        self._conn.execute("""UPDATE certifications SET
+                           ts=?,
+                           signature=?,
+                           written_on=?
+                           WHERE
+                           currency=? AND
+                           certifier=? AND
+                           certified=? AND
+                           block=?""",
+                           updated_fields + where_fields)
+
+    def get_one(self, **search):
+        """
+        Get an existing certification in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Certification
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM certifications WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Certification(*data)
+
+    def get_all(self, **search):
+        """
+        Get all existing certification in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Certification
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            value = v
+            filters.append("{key} = ?".format(key=k))
+            values.append(value)
+
+        request = "SELECT * FROM certifications WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        datas = c.fetchall()
+        if datas:
+            return [Certification(*data) for data in datas]
+        return []
+
+    def get_latest_sent(self, currency, pubkey):
+        """
+        Get latest sent certification
+        :param str currency:
+        :param str pubkey:
+        :return:
+        :rtype: sakia.data.entities.Certification
+        """
+        request = """SELECT * FROM certifications
+                  WHERE currency=? AND certifier=?
+                  ORDER BY ts DESC
+                  LIMIT 1"""
+        c = self._conn.execute(request, (currency, pubkey))
+        data = c.fetchone()
+        if data:
+            return Certification(*data)
+
+    def drop(self, certification):
+        """
+        Drop an existing certification from the database
+        :param sakia.data.entities.Certification certification: the certification to update
+        """
+        where_fields = attr.astuple(certification, filter=attr.filters.include(*CertificationsRepo._primary_keys))
+        self._conn.execute("""DELETE FROM certifications
+                              WHERE
+                              currency=? AND
+                              certifier=? AND
+                              certified=? AND
+                              block=?""", where_fields)
diff --git a/src/sakia/data/repositories/connections.py b/src/sakia/data/repositories/connections.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c24fad895c86d783be23e527a290f7acb638c3f
--- /dev/null
+++ b/src/sakia/data/repositories/connections.py
@@ -0,0 +1,121 @@
+import attr
+
+from ..entities import Connection
+
+
+@attr.s(frozen=True)
+class ConnectionsRepo:
+    """
+    The repository for Connections entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Connection.currency, Connection.pubkey)
+
+    def insert(self, connection):
+        """
+        Commit a connection to the database
+        :param sakia.data.entities.Connection connection: the connection to commit
+        """
+        connection_tuple = attr.astuple(connection, filter=attr.filters.exclude(Connection.password))
+        values = ",".join(['?'] * len(connection_tuple))
+        self._conn.execute("INSERT INTO connections VALUES ({0})".format(values), connection_tuple)
+
+    def update(self, connection):
+        """
+        Update an existing connection in the database
+        :param sakia.data.entities.Connection connection: the certification to update
+        """
+        updated_fields = attr.astuple(connection, filter=attr.filters.exclude(Connection.password,
+                                                                              *ConnectionsRepo._primary_keys))
+        where_fields = attr.astuple(connection, filter=attr.filters.include(*ConnectionsRepo._primary_keys))
+
+        self._conn.execute("""UPDATE connections SET
+                              salt=?,
+                              uid=?,
+                              scrypt_N=?,
+                              scrypt_p=?,
+                              scrypt_r=?,
+                              blockstamp=?
+                              WHERE
+                              currency=? AND
+                              pubkey=?
+                          """, updated_fields + where_fields)
+
+    def get_one(self, **search):
+        """
+        Get an existing connection in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Connection
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM connections WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Connection(*data)
+
+    def get_all(self, **search):
+        """
+        Get all existing connection in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Connection
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            value = v
+            filters.append("{connection} = ?".format(connection=k))
+            values.append(value)
+
+        request = "SELECT * FROM connections"
+        if filters:
+            request += " WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        datas = c.fetchall()
+        if datas:
+            return [Connection(*data) for data in datas]
+        return []
+
+    def get_currencies(self):
+        """
+        Get all existing connection in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: List[str]
+        """
+        request = "SELECT DISTINCT currency FROM connections"
+        c = self._conn.execute(request)
+        datas = c.fetchall()
+        if datas:
+            return [data[0] for data in datas]
+        return []
+
+    def get_pubkeys(self):
+        """
+        Get all existing connection in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: List[str]
+        """
+        request = "SELECT DISTINCT pubkey FROM connections"
+        c = self._conn.execute(request)
+        datas = c.fetchall()
+        if datas:
+            return [data[0] for data in datas]
+        return []
+
+    def drop(self, connection):
+        """
+        Drop an existing connection from the database
+        :param sakia.data.entities.Connection connection: the connection to update
+        """
+        where_fields = attr.astuple(connection, filter=attr.filters.include(*ConnectionsRepo._primary_keys))
+        self._conn.execute("""DELETE FROM connections
+                              WHERE
+                              currency=? AND
+                              pubkey=?""", where_fields)
diff --git a/src/sakia/data/repositories/dividends.py b/src/sakia/data/repositories/dividends.py
new file mode 100644
index 0000000000000000000000000000000000000000..41ba667f732dc38bb73745be09ed7f8ceb2fc93b
--- /dev/null
+++ b/src/sakia/data/repositories/dividends.py
@@ -0,0 +1,92 @@
+import attr
+
+from ..entities import Dividend
+
+
+@attr.s(frozen=True)
+class DividendsRepo:
+    """The repository for Communities entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Dividend.currency, Dividend.pubkey, Dividend.block_number)
+
+    def insert(self, dividend):
+        """
+        Commit a dividend to the database
+        :param sakia.data.entities.Dividend dividend: the dividend to commit
+        """
+        dividend_tuple = attr.astuple(dividend)
+        values = ",".join(['?'] * len(dividend_tuple))
+        self._conn.execute("INSERT INTO dividends VALUES ({0})".format(values), dividend_tuple)
+
+    def get_one(self, **search):
+        """
+        Get an existing dividend in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Dividend
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM dividends WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Dividend(*data)
+
+    def get_all(self, **search):
+        """
+        Get all existing dividend in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Dividend
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            value = v
+            filters.append("{key} = ?".format(key=k))
+            values.append(value)
+
+        request = "SELECT * FROM dividends WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        datas = c.fetchall()
+        if datas:
+            return [Dividend(*data) for data in datas]
+        return []
+
+    def get_dividends(self, currency, pubkey, offset=0, limit=1000, sort_by="currency", sort_order="ASC"):
+        """
+        Get all transfers in the database on a given currency from or to a pubkey
+
+        :param str pubkey: the criterions of the lookup
+        :rtype: List[sakia.data.entities.Dividend]
+        """
+        request = """SELECT * FROM dividends
+                  WHERE currency=? AND pubkey=?
+                  ORDER BY {sort_by} {sort_order}
+                  LIMIT {limit} OFFSET {offset}""" \
+                    .format(offset=offset,
+                            limit=limit,
+                            sort_by=sort_by,
+                            sort_order=sort_order
+                            )
+        c = self._conn.execute(request, (currency, pubkey, pubkey))
+        datas = c.fetchall()
+        if datas:
+            return [Dividend(*data) for data in datas]
+        return []
+
+    def drop(self, dividend):
+        """
+        Drop an existing dividend from the database
+        :param sakia.data.entities.Dividend dividend: the dividend to update
+        """
+        where_fields = attr.astuple(dividend, filter=attr.filters.include(*DividendsRepo._primary_keys))
+        self._conn.execute("""DELETE FROM dividends
+                              WHERE
+                              currency=? AND pubkey=? AND block_number=? """, where_fields)
diff --git a/src/sakia/data/repositories/identities.py b/src/sakia/data/repositories/identities.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e432205249859aaa00a02d1b08d6a63c790acbe
--- /dev/null
+++ b/src/sakia/data/repositories/identities.py
@@ -0,0 +1,114 @@
+import attr
+
+from duniterpy.documents.block import BlockUID
+
+from ..entities import Identity
+
+
+@attr.s(frozen=True)
+class IdentitiesRepo:
+    """The repository for Identities entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Identity.currency, Identity.pubkey, Identity.uid, Identity.blockstamp)
+
+    def insert(self, identity):
+        """
+        Commit an identity to the database
+        :param sakia.data.entities.Identity identity: the identity to commit
+        """
+        identity_tuple = attr.astuple(identity)
+        values = ",".join(['?'] * len(identity_tuple))
+        self._conn.execute("INSERT INTO identities VALUES ({0})".format(values), identity_tuple)
+
+    def update(self, identity):
+        """
+        Update an existing identity in the database
+        :param sakia.data.entities.Identity identity: the identity to update
+        """
+        updated_fields = attr.astuple(identity, filter=attr.filters.exclude(*IdentitiesRepo._primary_keys))
+        where_fields = attr.astuple(identity, filter=attr.filters.include(*IdentitiesRepo._primary_keys))
+        self._conn.execute("""UPDATE identities SET
+                              signature=?,
+                              timestamp=?,
+                              written_on=?,
+                              revoked_on=?,
+                              outdistanced=?,
+                              member=?,
+                              ms_buid=?,
+                              ms_timestamp=?,
+                              ms_written_on=?,
+                              ms_type=?
+                              WHERE
+                              currency=? AND
+                              pubkey=? AND
+                              uid=? AND
+                              blockstamp=?""", updated_fields + where_fields
+                           )
+
+    def get_one(self, **search):
+        """
+        Get an existing identity in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Identity
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM identities WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Identity(*data)
+
+    def get_all(self, **search):
+        """
+        Get all existing identity in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Identity
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            if isinstance(v, bool):
+                v = int(v)
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM identities WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        datas = c.fetchall()
+        if datas:
+            return [Identity(*data) for data in datas]
+        return []
+
+    def find_all(self, currency, text):
+        """
+        Get all existing identity in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Identity
+        """
+        request = "SELECT * FROM identities WHERE currency=? AND (UID LIKE ? or PUBKEY LIKE ?)"
+
+        c = self._conn.execute(request, (currency, "%{0}%".format(text), "%{0}%".format(text)))
+        datas = c.fetchall()
+        if datas:
+            return [Identity(*data) for data in datas]
+        return []
+
+    def drop(self, identity):
+        """
+        Drop an existing identity from the database
+        :param sakia.data.entities.Identity identity: the identity to update
+        """
+        where_fields = attr.astuple(identity, filter=attr.filters.include(*IdentitiesRepo._primary_keys))
+        self._conn.execute("""DELETE FROM identities WHERE
+                           currency=? AND
+                           pubkey=? AND
+                           uid=? AND
+                           blockstamp=?""", where_fields)
diff --git a/src/sakia/data/repositories/meta.py b/src/sakia/data/repositories/meta.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bae0a53f13a2ccc60d80cf6a69e855a24c0e661
--- /dev/null
+++ b/src/sakia/data/repositories/meta.py
@@ -0,0 +1,100 @@
+import attr
+import os
+import logging
+import sqlite3
+from duniterpy.documents import BlockUID
+from .connections import ConnectionsRepo
+from .identities import IdentitiesRepo
+from .blockchains import BlockchainsRepo
+from .certifications import CertificationsRepo
+from .transactions import TransactionsRepo
+from .dividends import DividendsRepo
+from .nodes import NodesRepo
+from .sources import SourcesRepo
+
+
+@attr.s(frozen=True)
+class SakiaDatabase:
+    """
+    This is Sakia unique SQLite database.
+    """
+    conn = attr.ib()  # :type sqlite3.Connection
+    connections_repo = attr.ib(default=None)
+    identities_repo = attr.ib(default=None)
+    blockchains_repo = attr.ib(default=None)
+    certifications_repo = attr.ib(default=None)
+    transactions_repo = attr.ib(default=None)
+    nodes_repo = attr.ib(default=None)
+    sources_repo = attr.ib(default=None)
+    dividends_repo = attr.ib(default=None)
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def load_or_init(cls, options, profile_name):
+        sqlite3.register_adapter(BlockUID, str)
+        sqlite3.register_adapter(bool, int)
+        sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v)))
+        con = sqlite3.connect(os.path.join(options.config_path, profile_name, options.database + ".db"),
+                              detect_types=sqlite3.PARSE_DECLTYPES)
+        meta = SakiaDatabase(con, ConnectionsRepo(con), IdentitiesRepo(con),
+                             BlockchainsRepo(con), CertificationsRepo(con), TransactionsRepo(con),
+                             NodesRepo(con), SourcesRepo(con), DividendsRepo(con))
+        meta.prepare()
+        meta.upgrade_database()
+        return meta
+
+    def prepare(self):
+        """
+        Prepares the database if the table is missing
+        """
+        with self.conn:
+            self._logger.debug("Initializing meta database")
+            self.conn.execute("""CREATE TABLE IF NOT EXISTS meta(
+                               id INTEGER NOT NULL,
+                               version INTEGER NOT NULL,
+                               PRIMARY KEY (id)
+                               )"""
+                              )
+
+    @property
+    def upgrades(self):
+        return [
+            self.create_all_tables,
+        ]
+
+    def upgrade_database(self):
+        """
+        Execute the migrations
+        """
+        self._logger.debug("Begin upgrade of database...")
+        version = self.version()
+        nb_versions = len(self.upgrades)
+        for v in range(version, nb_versions):
+            self._logger.debug("Upgrading to version {0}...".format(v))
+            self.upgrades[v]()
+            with self.conn:
+                self.conn.execute("UPDATE meta SET version=? WHERE id=1", (version + 1,))
+        self._logger.debug("End upgrade of database...")
+
+    def create_all_tables(self):
+        """
+        Init all the tables
+        :return:
+        """
+        self._logger.debug("Initialiazing all databases")
+        sql_file = open(os.path.join(os.path.dirname(__file__), 'meta.sql'), 'r')
+        with self.conn:
+            self.conn.executescript(sql_file.read())
+
+    def version(self):
+        with self.conn:
+            c = self.conn.execute("SELECT * FROM meta WHERE id=1")
+            data = c.fetchone()
+            if data:
+                return data[1]
+            else:
+                self.conn.execute("INSERT INTO meta VALUES (1, 0)")
+                return 0
+
+    def commit(self):
+        self.conn.commit()
diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d53558f529b4f09223ac3a982316e427c2bdeebe
--- /dev/null
+++ b/src/sakia/data/repositories/meta.sql
@@ -0,0 +1,143 @@
+-- IDENTITY TABLE
+CREATE TABLE IF NOT EXISTS identities(
+                               currency VARCHAR(30),
+                               pubkey VARCHAR(50),
+                               uid VARCHAR(255),
+                               blockstamp VARCHAR(100),
+                               signature VARCHAR(100),
+                               timestamp INT,
+                               written_on INT,
+                               revoked_on INT,
+                               outdistanced BOOLEAN,
+                               member BOOLEAN,
+                               ms_buid VARCHAR(100),
+                               ms_timestamp INT,
+                               ms_written_on INT,
+                               ms_type VARCHAR(5),
+                               PRIMARY KEY (currency, pubkey, uid, blockstamp)
+                               );
+
+-- BLOCKCHAIN TABLE
+CREATE TABLE IF NOT EXISTS blockchains (
+  c                             FLOAT(1, 6),
+  dt                            INT,
+  ud0                           INT,
+  sig_period                    INT,
+  sig_stock                     INT,
+  sig_window                    INT,
+  idty_window                   INT,
+  ms_window                     INT,
+  sig_validity                  INT,
+  sig_qty                       INT,
+  xpercent                      FLOAT(1, 6),
+  ms_validity                   INT,
+  step_max                      INT,
+  median_time_blocks            INT,
+  avg_gen_time                  INT,
+  dt_diff_eval                  INT,
+  percent_rot                   FLOAT(1, 6),
+  current_buid            INT,
+  current_members_count   INT,
+  current_mass            INT,
+  median_time             INT,
+  last_members_count      INT,
+  last_ud                 INT,
+  last_ud_base            INT,
+  last_ud_time            INT,
+  previous_mass           INT,
+  previous_members_count  INT,
+  previous_ud             INT,
+  previous_ud_base        INT,
+  previous_ud_time        INT,
+  currency                VARCHAR(30),
+  PRIMARY KEY (currency)
+);
+
+
+-- CERTIFICATIONS TABLE
+CREATE TABLE IF NOT EXISTS certifications(
+                               currency VARCHAR(30),
+                               certifier VARCHAR(50),
+                               certified VARCHAR(50),
+                               block INT,
+                               ts INT,
+                               signature VARCHAR(100),
+                               written_on INT,
+                               PRIMARY KEY (currency, certifier, certified, block)
+                               );
+
+-- TRANSACTIONS TABLE
+CREATE TABLE IF NOT EXISTS transactions(
+                               currency VARCHAR(30),
+                               sha_hash VARCHAR(50),
+                               written_on INT,
+                               blockstamp VARCHAR(100),
+                               ts INT,
+                               signature VARCHAR(100),
+                               issuer VARCHAR(50),
+                               receiver VARCHAR(50),
+                               amount INT,
+                               amountbase INT,
+                               comment VARCHAR(255),
+                               txid INT,
+                               state INT,
+                               local BOOLEAN,
+                               raw TEXT,
+                               PRIMARY KEY (sha_hash)
+                               );
+
+-- NODES TABLE
+CREATE TABLE IF NOT EXISTS nodes(
+                               currency             VARCHAR(30),
+                               pubkey               VARCHAR(50),
+                               endpoints            TEXT,
+                               peer_buid            VARCHAR(100),
+                               uid                  VARCHAR(50),
+                               current_buid         VARCHAR(100),
+                               current_ts           INT,
+                               previous_buid        VARCHAR(100),
+                               state                INT,
+                               software             VARCHAR(100),
+                               version              VARCHAR(50),
+                               merkle_peers_root    VARCHAR(50),
+                               merkle_peers_leaves  TEXT,
+                               root                 BOOLEAN,
+                               member               BOOLEAN,
+                               PRIMARY KEY (currency, pubkey)
+                               );
+
+-- Cnnections TABLE
+CREATE TABLE IF NOT EXISTS connections(
+                               currency           VARCHAR(30),
+                               pubkey             VARCHAR(50),
+                               salt               VARCHAR(50),
+                               uid                VARCHAR(255),
+                               scrypt_N           INT,
+                               scrypt_p           INT,
+                               scrypt_r           INT,
+                               blockstamp         VARCHAR(100),
+                               PRIMARY KEY (currency, pubkey)
+                               );
+
+-- Cnnections TABLE
+CREATE TABLE IF NOT EXISTS sources(
+                               currency           VARCHAR(30),
+                               pubkey             VARCHAR(50),
+                               identifier         VARCHAR(255),
+                               noffset             INT,
+                               type               VARCHAR(8),
+                               amount             INT,
+                               base               INT,
+                               PRIMARY KEY (currency, pubkey, identifier, noffset)
+                               );
+
+CREATE TABLE IF NOT EXISTS dividends(
+                               currency           VARCHAR(30),
+                               pubkey             VARCHAR(50),
+                               block_number       VARCHAR(255),
+                               timestamp          INT,
+                               amount             INT,
+                               base               INT,
+                               PRIMARY KEY (currency, pubkey, block_number)
+);
+
diff --git a/src/sakia/data/repositories/nodes.py b/src/sakia/data/repositories/nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..4500574e3346f3c374d3827ad2e64831209ae61d
--- /dev/null
+++ b/src/sakia/data/repositories/nodes.py
@@ -0,0 +1,110 @@
+import attr
+
+from ..entities import Node
+
+
+@attr.s(frozen=True)
+class NodesRepo:
+    """The repository for Communities entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Node.currency, Node.pubkey)
+
+    def insert(self, node):
+        """
+        Commit a node to the database
+        :param sakia.data.entities.Node node: the node to commit
+        """
+        node_tuple = attr.astuple(node, tuple_factory=list)
+        node_tuple[2] = "\n".join([str(n) for n in node_tuple[2]])
+        node_tuple[12] = "\n".join([str(n) for n in node_tuple[12]])
+        values = ",".join(['?'] * len(node_tuple))
+        self._conn.execute("INSERT INTO nodes VALUES ({0})".format(values), node_tuple)
+
+    def update(self, node):
+        """
+        Update an existing node in the database
+        :param sakia.data.entities.Node node: the node to update
+        """
+        updated_fields = attr.astuple(node, tuple_factory=list,
+                                      filter=attr.filters.exclude(*NodesRepo._primary_keys))
+        updated_fields[0] = "\n".join([str(n) for n in updated_fields[0]])
+        updated_fields[10] = "\n".join([str(n) for n in updated_fields[9]])
+        where_fields = attr.astuple(node, tuple_factory=list,
+                                    filter=attr.filters.include(*NodesRepo._primary_keys))
+        self._conn.execute("""UPDATE nodes SET
+                                    endpoints=?,
+                                    peer_buid=?,
+                                    uid=?,
+                                    current_buid=?,
+                                    current_ts=?,
+                                    previous_buid=?,
+                                    state=?,
+                                    software=?,
+                                    version=?,
+                                    merkle_peers_root=?,
+                                    merkle_peers_leaves=?,
+                                    root=?,
+                                    member=?
+                                   WHERE
+                                   currency=? AND
+                                   pubkey=?""",
+                                   updated_fields + where_fields)
+
+    def get_one(self, **search):
+        """
+        Get an existing node in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Node
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            if isinstance(v, bool):
+                v = int(v)
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM nodes WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Node(*data)
+
+    def get_all(self, **search):
+        """
+        Get all existing node in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Node
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            if isinstance(v, bool):
+                value = int(v)
+            else:
+                value = v
+            filters.append("{key} = ?".format(key=k))
+            values.append(value)
+
+        if filters:
+            request = "SELECT * FROM nodes WHERE {filters}".format(filters=" AND ".join(filters))
+        else:
+            request = "SELECT * FROM nodes"
+
+        c = self._conn.execute(request, tuple(values))
+        datas = c.fetchall()
+        if datas:
+            return [Node(*data) for data in datas]
+        return []
+
+    def drop(self, node):
+        """
+        Drop an existing node from the database
+        :param sakia.data.entities.Node node: the node to update
+        """
+        where_fields = attr.astuple(node, filter=attr.filters.include(*NodesRepo._primary_keys))
+        self._conn.execute("""DELETE FROM nodes
+                              WHERE
+                              currency=? AND pubkey=?""", where_fields)
diff --git a/src/sakia/data/repositories/sources.py b/src/sakia/data/repositories/sources.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd5266cf27688cc0810caddc8c4b8e746a0a5b9a
--- /dev/null
+++ b/src/sakia/data/repositories/sources.py
@@ -0,0 +1,81 @@
+import attr
+
+from ..entities import Source
+
+
+@attr.s(frozen=True)
+class SourcesRepo:
+    """The repository for Communities entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Source.identifier,)
+
+    def insert(self, source):
+        """
+        Commit a source to the database
+        :param sakia.data.entities.Source source: the source to commit
+        """
+        source_tuple = attr.astuple(source)
+        values = ",".join(['?'] * len(source_tuple))
+        self._conn.execute("INSERT INTO sources VALUES ({0})".format(values), source_tuple)
+
+    def get_one(self, **search):
+        """
+        Get an existing source in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Source
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM sources WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Source(*data)
+
+    def get_all(self, **search):
+        """
+        Get all existing source in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Source
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            value = v
+            filters.append("{key} = ?".format(key=k))
+            values.append(value)
+
+        request = "SELECT * FROM sources WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        datas = c.fetchall()
+        if datas:
+            return [Source(*data) for data in datas]
+        return []
+
+    def drop(self, source):
+        """
+        Drop an existing source from the database
+        :param sakia.data.entities.Source source: the source to update
+        """
+        where_fields = attr.astuple(source, filter=attr.filters.include(*SourcesRepo._primary_keys))
+        self._conn.execute("""DELETE FROM sources
+                              WHERE
+                              identifier=?""", where_fields)
+
+    def drop_all(self, **filter):
+        filters = []
+        values = []
+        for k, v in filter.items():
+            value = v
+            filters.append("{key} = ?".format(key=k))
+            values.append(value)
+
+        request = "DELETE FROM sources WHERE {filters}".format(filters=" AND ".join(filters))
+        self._conn.execute(request, tuple(values))
diff --git a/src/sakia/data/repositories/transactions.py b/src/sakia/data/repositories/transactions.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f735b6b64507f1fcbd331464fbe285c64f5874a
--- /dev/null
+++ b/src/sakia/data/repositories/transactions.py
@@ -0,0 +1,118 @@
+import attr
+
+from ..entities import Transaction
+
+
+@attr.s(frozen=True)
+class TransactionsRepo:
+    """The repository for Communities entities.
+    """
+    _conn = attr.ib()  # :type sqlite3.Connection
+    _primary_keys = (Transaction.sha_hash,)
+
+    def insert(self, transaction):
+        """
+        Commit a transaction to the database
+        :param sakia.data.entities.Transaction transaction: the transaction to commit
+        """
+        transaction_tuple = attr.astuple(transaction)
+        values = ",".join(['?'] * len(transaction_tuple))
+        self._conn.execute("INSERT INTO transactions VALUES ({0})".format(values), transaction_tuple)
+
+    def update(self, transaction):
+        """
+        Update an existing transaction in the database
+        :param sakia.data.entities.Transaction transaction: the transaction to update
+        """
+        updated_fields = attr.astuple(transaction, filter=attr.filters.exclude(*TransactionsRepo._primary_keys))
+        where_fields = attr.astuple(transaction, filter=attr.filters.include(*TransactionsRepo._primary_keys))
+        self._conn.execute("""UPDATE transactions SET
+                           currency=?,
+                           written_on=?,
+                           blockstamp=?,
+                           ts=?,
+                           signature=?,
+                           issuer = ?,
+                           receiver = ?,
+                           amount = ?,
+                           amountbase = ?,
+                           comment = ?,
+                           txid = ?,
+                           state = ?,
+                           local = ?,
+                           raw = ?
+                           WHERE
+                           sha_hash=?""",
+                           updated_fields + where_fields)
+
+    def get_one(self, **search):
+        """
+        Get an existing transaction in the database
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Transaction
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            filters.append("{k}=?".format(k=k))
+            values.append(v)
+
+        request = "SELECT * FROM transactions WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        data = c.fetchone()
+        if data:
+            return Transaction(*data)
+
+    def get_all(self, **search):
+        """
+        Get all existing transaction in the database corresponding to the search
+        :param dict search: the criterions of the lookup
+        :rtype: sakia.data.entities.Transaction
+        """
+        filters = []
+        values = []
+        for k, v in search.items():
+            value = v
+            filters.append("{key} = ?".format(key=k))
+            values.append(value)
+
+        request = "SELECT * FROM transactions WHERE {filters}".format(filters=" AND ".join(filters))
+
+        c = self._conn.execute(request, tuple(values))
+        datas = c.fetchall()
+        if datas:
+            return [Transaction(*data) for data in datas]
+        return []
+
+    def get_transfers(self, currency, pubkey, offset=0, limit=1000, sort_by="currency", sort_order="ASC"):
+        """
+        Get all transfers in the database on a given currency from or to a pubkey
+
+        :param str pubkey: the criterions of the lookup
+        :rtype: List[sakia.data.entities.Transaction]
+        """
+        request = """SELECT * FROM transactions
+                  WHERE currency=? AND (issuer=? or receiver=?)
+                  ORDER BY {sort_by} {sort_order}
+                  LIMIT {limit} OFFSET {offset}""" \
+                    .format(offset=offset,
+                            limit=limit,
+                            sort_by=sort_by,
+                            sort_order=sort_order
+                            )
+        c = self._conn.execute(request, (currency, pubkey, pubkey))
+        datas = c.fetchall()
+        if datas:
+            return [Transaction(*data) for data in datas]
+        return []
+
+    def drop(self, transaction):
+        """
+        Drop an existing transaction from the database
+        :param sakia.data.entities.Transaction transaction: the transaction to update
+        """
+        where_fields = attr.astuple(transaction, filter=attr.filters.include(*TransactionsRepo._primary_keys))
+        self._conn.execute("""DELETE FROM transactions
+                              WHERE
+                              sha_hash=?""", where_fields)
diff --git a/src/sakia/tools/decorators.py b/src/sakia/decorators.py
similarity index 75%
rename from src/sakia/tools/decorators.py
rename to src/sakia/decorators.py
index 7648502ebeb53d64180a81e1c5b04e40aa330538..f9dc5f2103c04299d0d010141f8ab759f13d8c5e 100644
--- a/src/sakia/tools/decorators.py
+++ b/src/sakia/decorators.py
@@ -19,24 +19,24 @@ def once_at_a_time(fn):
                 func_call = args[0].__tasks[fn.__name__]
                 args[0].__tasks.pop(fn.__name__)
                 if getattr(func_call, "_next_task", None):
-                    func_call._next_task._start()
+                    start_task(func_call._next_task[0],
+                               *func_call._next_task[1],
+                               **func_call._next_task[2])
             except KeyError:
                 logging.debug("Task {0} already removed".format(fn.__name__))
 
-        def start_task():
-            args[0].__tasks[fn.__name__] = fn(*args, **kwargs)
-            args[0].__tasks[fn.__name__].add_done_callback(task_done)
+        def start_task(f, *a, **k):
+            args[0].__tasks[f.__name__] = f(*a, **k)
+            args[0].__tasks[f.__name__].add_done_callback(task_done)
 
         if getattr(args[0], "__tasks", None) is None:
             setattr(args[0], "__tasks", {})
 
-        fn._start = lambda: start_task()
-
         if fn.__name__ in args[0].__tasks:
-            args[0].__tasks[fn.__name__]._next_task = fn
+            args[0].__tasks[fn.__name__]._next_task = (fn, args, kwargs)
             args[0].__tasks[fn.__name__].cancel()
         else:
-            fn._start()
+            start_task(fn, *args, **kwargs)
 
         return args[0].__tasks[fn.__name__]
     return wrapper
diff --git a/src/sakia/errors.py b/src/sakia/errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..801e47dc5764ae7167520f919eb3383debe150ba
--- /dev/null
+++ b/src/sakia/errors.py
@@ -0,0 +1,63 @@
+"""
+Created on 9 févr. 2014
+
+@author: inso
+"""
+
+
+class Error(Exception):
+
+    def __init__(self, message):
+        """
+        Constructor
+        """
+        self.message = "Error : " + message
+
+    def __str__(self):
+        return self.message
+
+
+class NotEnoughChangeError(Error):
+
+    """
+    Exception raised when trying to send money but user
+    is missing change
+    """
+
+    def __init__(self, available, currency, nb_inputs, requested):
+        """
+        Constructor
+        """
+        super() .__init__(
+            "Only {0} {1} available in {2} sources, needs {3}"
+            .format(available,
+                    currency,
+                    nb_inputs,
+                    requested))
+
+
+class NoPeerAvailable(Error):
+    """
+    Exception raised when a community doesn't have any
+    peer available.
+    """
+    def __init__(self, currency, nbpeers):
+        """
+        Constructor
+        """
+        super() .__init__(
+            "No peer answered in {0} community ({1} peers available)"
+            .format(currency, nbpeers))
+
+
+class InvalidNodeCurrency(Error):
+    """
+    Exception raised when a node doesn't use the intended currency
+    """
+    def __init__(self, currency, node_currency):
+        """
+        Constructor
+        """
+        super() .__init__(
+            "Node is working for {0} currency, but should be {1}"
+            .format(node_currency, currency))
diff --git a/src/sakia/gui/certification.py b/src/sakia/gui/certification.py
deleted file mode 100644
index f19a2840f8edd3c566ff446b065525db639b87ae..0000000000000000000000000000000000000000
--- a/src/sakia/gui/certification.py
+++ /dev/null
@@ -1,263 +0,0 @@
-"""
-Created on 24 dec. 2014
-
-@author: inso
-"""
-import asyncio
-import logging
-from duniterpy.api import errors
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QApplication, QMessageBox
-from PyQt5.QtCore import Qt, QObject, QLocale, QDateTime
-
-from .widgets import toast
-from .widgets.dialogs import QAsyncMessageBox
-from .member import MemberDialog
-from ..tools.decorators import asyncify, once_at_a_time
-from ..tools.exceptions import NoPeerAvailable
-from ..gen_resources.certification_uic import Ui_CertificationDialog
-
-class CertificationDialog(QObject):
-    """
-    A dialog to certify individuals
-    """
-
-    def __init__(self, app, account, password_asker, widget, ui):
-        """
-        Constructor if a certification dialog
-
-        :param sakia.core.Application app:
-        :param sakia.core.Account account:
-        :param sakia.gui.password_asker.PasswordAsker password_asker:
-        :param PyQt5.QtWidgets widget: the widget of the dialog
-        :param sakia.gen_resources.certification_uic.Ui_CertificationDialog view: the view of the certification dialog
-        :return:
-        """
-        super().__init__()
-        self.widget = widget
-        self.ui = ui
-        self.ui.setupUi(self.widget)
-        self.app = app
-        self.account = account
-        self.password_asker = password_asker
-        self.community = self.account.communities[0]
-
-        self.ui.radio_contact.toggled.connect(lambda c, radio="contact": self.recipient_mode_changed(radio))
-        self.ui.radio_pubkey.toggled.connect(lambda c, radio="pubkey": self.recipient_mode_changed(radio))
-        self.ui.radio_search.toggled.connect(lambda c, radio="search": self.recipient_mode_changed(radio))
-        self.ui.button_box.accepted.connect(self.accept)
-        self.ui.button_box.rejected.connect(self.widget.reject)
-
-        for community in self.account.communities:
-            self.ui.combo_community.addItem(community.currency)
-
-        for contact_name in sorted([c['name'] for c in account.contacts], key=str.lower):
-            self.ui.combo_contact.addItem(contact_name)
-
-        if len(account.contacts) == 0:
-            self.ui.radio_pubkey.setChecked(True)
-            self.ui.radio_contact.setEnabled(False)
-
-        self.ui.member_widget = MemberDialog.as_widget(self.ui.groupBox, self.app, self.account, self.community, None)
-        self.ui.horizontalLayout_5.addWidget(self.ui.member_widget.widget)
-
-        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.combo_contact.currentIndexChanged.connect(self.refresh_member)
-        self.ui.edit_pubkey.textChanged.connect(self.refresh_member)
-        self.ui.search_user.identity_selected.connect(self.refresh_member)
-        self.ui.radio_contact.toggled.connect(self.refresh_member)
-        self.ui.radio_search.toggled.connect(self.refresh_member)
-        self.ui.radio_pubkey.toggled.connect(self.refresh_member)
-        self.ui.combo_community.currentIndexChanged.connect(self.change_current_community)
-
-    @classmethod
-    def open_dialog(cls, app, account, community, password_asker):
-        """
-        Certify and identity
-        :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.gui.password_asker.PasswordAsker password_asker: the password asker
-        :return:
-        """
-        dialog = cls(app, account, password_asker, QDialog(), Ui_CertificationDialog())
-        if community:
-            dialog.ui.combo_community.setCurrentText(community.name)
-        dialog.refresh()
-        return dialog.exec()
-
-    @classmethod
-    async def certify_identity(cls, app, account, password_asker, community, identity):
-        """
-        Certify and identity
-        :param sakia.core.Application app: the application
-        :param sakia.core.Account account: the account certifying the identity
-        :param sakia.gui.password_asker.PasswordAsker password_asker: the password asker
-        :param sakia.core.Community community: the community
-        :param sakia.core.registry.Identity identity: the identity certified
-        :return:
-        """
-        dialog = cls(app, account, password_asker, QDialog(), Ui_CertificationDialog())
-        dialog.ui.combo_community.setCurrentText(community.name)
-        dialog.ui.edit_pubkey.setText(identity.pubkey)
-        dialog.ui.radio_pubkey.setChecked(True)
-        dialog.refresh()
-        return await dialog.async_exec()
-
-    @asyncify
-    async def accept(self):
-        """
-        Validate the dialog
-        """
-        pubkey = self.selected_pubkey()
-        if pubkey:
-            password = await self.password_asker.async_exec()
-            if password == "":
-                self.ui.button_box.setEnabled(True)
-                return
-            QApplication.setOverrideCursor(Qt.WaitCursor)
-            result = await self.account.certify(password, self.community, pubkey)
-            if result[0]:
-                if self.app.preferences['notifications']:
-                    toast.display(self.tr("Certification"),
-                                  self.tr("Success sending certification"))
-                else:
-                    await QAsyncMessageBox.information(self.widget, self.tr("Certification"),
-                                                 self.tr("Success sending certification"))
-                QApplication.restoreOverrideCursor()
-                self.widget.accept()
-            else:
-                if self.app.preferences['notifications']:
-                    toast.display(self.tr("Certification"), self.tr("Could not broadcast certification : {0}"
-                                                                    .format(result[1])))
-                else:
-                    await QAsyncMessageBox.critical(self.widget, self.tr("Certification"),
-                                              self.tr("Could not broadcast certification : {0}"
-                                                                    .format(result[1])))
-                QApplication.restoreOverrideCursor()
-                self.ui.button_box.setEnabled(True)
-
-    def change_current_community(self, index):
-        self.community = self.account.communities[index]
-        self.ui.search_user.change_community(self.community)
-        self.ui.member_widget.change_community(self.community)
-        if self.widget.isVisible():
-            self.refresh()
-
-    def selected_pubkey(self):
-        """
-        Get selected pubkey in the widgets of the window
-        :return: the current pubkey
-        :rtype: str
-        """
-        pubkey = None
-        if self.ui.radio_contact.isChecked():
-            for contact in self.account.contacts:
-                if contact['name'] == self.ui.combo_contact.currentText():
-                    pubkey = contact['pubkey']
-                    break
-        elif self.ui.radio_search.isChecked():
-            if self.ui.search_user.current_identity():
-                pubkey = self.ui.search_user.current_identity().pubkey
-        else:
-            pubkey = self.ui.edit_pubkey.text()
-        return pubkey
-
-    @asyncify
-    async def refresh_member(self, checked=False):
-        """
-        Refresh the member widget
-        """
-        current_pubkey = self.selected_pubkey()
-        if current_pubkey:
-            identity = await self.app.identities_registry.future_find(current_pubkey, self.community)
-        else:
-            identity = None
-        self.ui.member_widget.identity = identity
-        self.ui.member_widget.refresh()
-
-    @once_at_a_time
-    @asyncify
-    async def refresh(self):
-        account_identity = await self.account.identity(self.community)
-        is_member = await account_identity.is_member(self.community)
-        try:
-            block_0 = await self.community.get_block(0)
-        except errors.DuniterError as e:
-            if e.ucode == errors.BLOCK_NOT_FOUND:
-                block_0 = None
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-            block_0 = None
-
-        params = await self.community.parameters()
-        certifications = await account_identity.unique_valid_certified_by(self.app.identities_registry, self.community)
-        nb_certifications = len([c for c in certifications if c['block_number']])
-        nb_cert_pending = len([c for c in certifications if not c['block_number']])
-        remaining_time = await account_identity.cert_issuance_delay(self.app.identities_registry, self.community)
-        cert_text = self.tr("Certifications sent : {nb_certifications}/{stock}").format(
-            nb_certifications=nb_certifications,
-            stock=params['sigStock'])
-        if nb_cert_pending > 0:
-            cert_text += " (+{nb_cert_pending} certifications pending)".format(nb_cert_pending=nb_cert_pending)
-        if remaining_time > 0:
-            cert_text += "\n"
-            days, remainder = divmod(remaining_time, 3600*24)
-            hours, remainder = divmod(remainder, 3600)
-            minutes, seconds = divmod(remainder, 60)
-            if days > 0:
-                remaining_localized = self.tr("{days} days").format(days=days)
-            else:
-                remaining_localized = self.tr("{hours} hours and {min} min.").format(hours=hours,
-                                                                                min=minutes)
-            cert_text += self.tr("Remaining time before next certification validation : {0}".format(remaining_localized))
-        self.ui.label_cert_stock.setText(cert_text)
-
-        if is_member or not block_0:
-            if nb_certifications < params['sigStock'] or params['sigStock'] == 0:
-                self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(True)
-                if remaining_time > 0:
-                    self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("&Ok") +
-                                                                           self.tr(" (Not validated before ")
-                                                                            + remaining_localized + ")")
-                else:
-                    self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("&Ok"))
-            else:
-                self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
-                self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("No more certifications"))
-        else:
-            self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
-            self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("Not a member"))
-
-    def showEvent(self, event):
-        super().showEvent(event)
-        self.first_certification_check()
-
-    def first_certification_check(self):
-        if self.account.notifications['warning_certifying_first_time']:
-            self.account.notifications['warning_certifying_first_time'] = False
-            QMessageBox.warning(self, "Certifying individuals", """Please follow the following guidelines :
-1.) Don't certify an account if you believe the issuers identity might be faked.
-2.) Don't certify an account if you believe the issuer already has another certified account.
-3.) Don't certify an account if you believe the issuer purposely or carelessly violates rule 1 or 2 (the issuer certifies faked or double accounts
-""")
-
-    def recipient_mode_changed(self, radio):
-        """
-        :param str radio:
-        """
-        self.ui.edit_pubkey.setEnabled(radio == "pubkey")
-        self.ui.combo_contact.setEnabled(radio == "contact")
-        self.ui.search_user.setEnabled(radio == "search")
-
-    def async_exec(self):
-        future = asyncio.Future()
-        self.widget.finished.connect(lambda r: future.set_result(r))
-        self.widget.open()
-        self.refresh()
-        return future
-
-    def exec(self):
-        self.widget.exec()
diff --git a/src/sakia/gui/community_tile.py b/src/sakia/gui/community_tile.py
deleted file mode 100644
index 3e59dc2574943482645ad20661f03ba739790945..0000000000000000000000000000000000000000
--- a/src/sakia/gui/community_tile.py
+++ /dev/null
@@ -1,204 +0,0 @@
-"""
-@author: inso
-"""
-
-import enum
-
-from PyQt5.QtWidgets import QFrame, QLabel, QVBoxLayout, QLayout
-from PyQt5.QtCore import QSize, pyqtSignal, QTime
-from duniterpy.documents.block import Block
-from duniterpy.api import errors
-
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from ..tools.exceptions import NoPeerAvailable
-from .widgets.busy import Busy
-
-
-@enum.unique
-class CommunityState(enum.Enum):
-    NOT_INIT = 0
-    OFFLINE = 1
-    READY = 2
-
-
-class CommunityTile(QFrame):
-    clicked = pyqtSignal()
-    _hover_stylesheet = """QFrame#CommunityTile {
-border-radius: 5px;
-background-color: palette(midlight);
-}
-"""
-    _pressed_stylesheet = """QFrame#CommunityTile {
-border-radius: 5px;
-background-color: palette(dark);
-}
-"""
-    _standard_stylesheet = """QFrame#CommunityTile {
-border-radius: 5px;
-background-color: palette(base);
-}
-"""
-
-    def __init__(self, parent, app, community):
-        super().__init__(parent)
-        self.setObjectName("CommunityTile")
-        self.app = app
-        self.community = community
-        self.community.network.nodes_changed.connect(self.handle_nodes_change)
-        self.text_label = QLabel()
-        self.setLayout(QVBoxLayout())
-        self.layout().setSizeConstraint(QLayout.SetFixedSize)
-        self.layout().addWidget(self.text_label)
-        self.setFrameShape(QFrame.StyledPanel)
-        self.setFrameShadow(QFrame.Raised)
-        self.setStyleSheet(CommunityTile._standard_stylesheet)
-        self.busy = Busy(self)
-        self.busy.hide()
-        self._state = CommunityState.NOT_INIT
-        self.refresh()
-
-    def sizeHint(self):
-        return QSize(250, 250)
-
-    def handle_nodes_change(self):
-        if len(self.community.network.online_nodes) > 0:
-            if self.community.network.current_blockUID.sha_hash == Block.Empty_Hash:
-                state = CommunityState.NOT_INIT
-            else:
-                state = CommunityState.READY
-        else:
-            state = CommunityState.OFFLINE
-
-        if state != self._state:
-            self.refresh()
-
-    def cancel_refresh(self):
-        cancel_once_task(self, self.refresh)
-
-    @once_at_a_time
-    @asyncify
-    async def refresh(self):
-        self.busy.show()
-        self.setFixedSize(QSize(150, 150))
-        try:
-            current_block = await self.community.get_block()
-            members_pubkeys = await self.community.members_pubkeys()
-            amount = await self.app.current_account.amount(self.community)
-            localized_amount = await self.app.current_account.current_ref.instance(amount,
-                                                        self.community, self.app).localized(units=True,
-                                            international_system=self.app.preferences['international_system_of_units'])
-            if current_block['monetaryMass']:
-                localized_monetary_mass = await self.app.current_account.current_ref.instance(current_block['monetaryMass'],
-                                                        self.community, self.app).diff_localized(units=True,
-                                            international_system=self.app.preferences['international_system_of_units'])
-            else:
-                localized_monetary_mass = ""
-            status = self.app.current_account.pubkey in members_pubkeys
-            account_identity = await self.app.current_account.identity(self.community)
-
-            mstime_remaining_text = self.tr("Expired or never published")
-            outdistanced_text = self.tr("Outdistanced")
-
-            requirements = await account_identity.requirements(self.community)
-            mstime_remaining = 0
-            nb_certs = 0
-            if requirements:
-                mstime_remaining = requirements['membershipExpiresIn']
-                nb_certs = len(requirements['certifications'])
-                if not requirements['outdistanced']:
-                    outdistanced_text = self.tr("In WoT range")
-
-            if mstime_remaining > 0:
-                days, remainder = divmod(mstime_remaining, 3600*24)
-                hours, remainder = divmod(remainder, 3600)
-                minutes, seconds = divmod(remainder, 60)
-                mstime_remaining_text = self.tr("Expires in ")
-                if days > 0:
-                    mstime_remaining_text += "{days} days".format(days=days)
-                else:
-                    mstime_remaining_text += "{hours} hours and {min} min.".format(hours=hours,
-                                                                                    min=minutes)
-
-            status_value = self.tr("Member") if status else self.tr("Non-Member")
-            status_color = '#00AA00' if status else self.tr('#FF0000')
-            description = """<html>
-            <body>
-            <p>
-            <span style=" font-size:16pt; font-weight:600;">{currency}</span>
-            </p>
-            <p>{nb_members} {members_label}</p>
-            <p><span style="font-weight:600;">{monetary_mass_label}</span> : {monetary_mass}</p>
-            <p><span style="font-weight:600;">{status_label}</span> : <span style="color:{status_color};">{status}</span></p>
-            <p><span style="font-weight:600;">{nb_certs_label}</span> : {nb_certs} ({outdistanced_text})</p>
-            <p><span style="font-weight:600;">{mstime_remaining_label}</span> : {mstime_remaining}</p>
-            <p><span style="font-weight:600;">{balance_label}</span> : {balance}</p>
-            </body>
-            </html>""".format(currency=self.community.currency,
-                              nb_members=len(members_pubkeys),
-                              members_label=self.tr("members"),
-                              monetary_mass_label=self.tr("Monetary mass"),
-                              monetary_mass=localized_monetary_mass,
-                              status_color=status_color,
-                              status_label=self.tr("Status"),
-                              status=status_value,
-                              nb_certs_label=self.tr("Certs. received"),
-                              nb_certs=nb_certs,
-                              outdistanced_text=outdistanced_text,
-                              mstime_remaining_label=self.tr("Membership"),
-                              mstime_remaining=mstime_remaining_text,
-                              balance_label=self.tr("Balance"),
-                              balance=localized_amount)
-            self.text_label.setText(description)
-            self._state = CommunityState.READY
-        except NoPeerAvailable:
-            description = """<html>
-            <body>
-            <p>
-            <span style=" font-size:16pt; font-weight:600;">{currency}</span>
-            </p>
-            <p>{message}</p>
-            </body>
-            </html>""".format(currency=self.community.currency,
-                              message=self.tr("Not connected"))
-            self.text_label.setText(description)
-            self._state = CommunityState.OFFLINE
-        except errors.DuniterError as e:
-            if e.ucode == errors.BLOCK_NOT_FOUND:
-                description = """<html>
-                <body>
-                <p>
-                <span style=" font-size:16pt; font-weight:600;">{currency}</span>
-                </p>
-                <p>{message}</p>
-                </body>
-                </html>""".format(currency=self.community.currency,
-                              message=self.tr("Community not initialized"))
-                self.text_label.setText(description)
-                self._state = CommunityState.NOT_INIT
-            else:
-                raise
-
-        self.busy.hide()
-
-    def mousePressEvent(self, event):
-        self.grabMouse()
-        self.setStyleSheet(CommunityTile._pressed_stylesheet)
-        return super().mousePressEvent(event)
-
-    def mouseReleaseEvent(self, event):
-        self.releaseMouse()
-        self.setStyleSheet(CommunityTile._hover_stylesheet)
-        self.clicked.emit()
-        return super().mouseReleaseEvent(event)
-
-    def resizeEvent(self, event):
-        self.busy.resize(event.size())
-        super().resizeEvent(event)
-
-    def enterEvent(self, event):
-        self.setStyleSheet(CommunityTile._hover_stylesheet)
-        return super().enterEvent(event)
-
-    def leaveEvent(self, event):
-        self.setStyleSheet(CommunityTile._standard_stylesheet)
-        return super().leaveEvent(event)
diff --git a/src/sakia/gui/community_view.py b/src/sakia/gui/community_view.py
deleted file mode 100644
index 53f47851bb33874355621ab45729821b67fc6e20..0000000000000000000000000000000000000000
--- a/src/sakia/gui/community_view.py
+++ /dev/null
@@ -1,460 +0,0 @@
-"""
-Created on 2 févr. 2014
-
-@author: inso
-"""
-
-import logging
-import time
-from duniterpy.api import errors
-from PyQt5.QtCore import pyqtSlot, QDateTime, QLocale, QEvent, QT_TRANSLATE_NOOP, Qt
-from PyQt5.QtGui import QIcon, QPixmap
-from PyQt5.QtWidgets import QWidget, QMessageBox, QDialog, QPushButton, QTabBar, QAction, QMenu, QFileDialog
-
-from .graphs.wot_tab import WotTabWidget
-from .widgets import toast
-from .widgets.dialogs import QAsyncMessageBox, QAsyncFileDialog, dialog_async_exec
-from .identities_tab import IdentitiesTabWidget
-from .informations_tab import InformationsTabWidget
-from .network_tab import NetworkTabWidget
-from .transactions_tab import TransactionsTabWidget
-from .graphs.explorer_tab import ExplorerTabWidget
-from ..gen_resources.community_view_uic import Ui_CommunityWidget
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from ..tools.exceptions import MembershipNotFoundError, LookupFailureError, NoPeerAvailable
-
-
-class CommunityWidget(QWidget, Ui_CommunityWidget):
-
-    """
-    classdocs
-    """
-
-    _tab_history_label = QT_TRANSLATE_NOOP("CommunityWidget", "Transactions")
-    _tab_wot_label = QT_TRANSLATE_NOOP("CommunityWidget", "Web of Trust")
-    _tab_identities_label = QT_TRANSLATE_NOOP("CommunityWidget", "Search Identities")
-    _tab_network_label = QT_TRANSLATE_NOOP("CommunityWidget", "Network")
-    _tab_informations_label = QT_TRANSLATE_NOOP("CommunityWidget", "Informations")
-    _action_showinfo_text = QT_TRANSLATE_NOOP("CommunityWidget", "Show informations")
-    _action_explore_text = QT_TRANSLATE_NOOP("CommunityWidget", "Explore the Web of Trust")
-    _action_publish_uid_text = QT_TRANSLATE_NOOP("CommunityWidget", "Publish UID")
-    _action_revoke_uid_text = QT_TRANSLATE_NOOP("CommunityWidget", "Revoke UID")
-
-    def __init__(self, app, status_label, label_icon):
-        """
-        Constructor
-        """
-        super().__init__()
-        self.app = app
-        self.account = None
-        self.community = None
-        self.password_asker = None
-        self.status_label = status_label
-        self.label_icon = label_icon
-
-        self.status_info = []
-
-        self.tab_wot = WotTabWidget(self.app)
-        self.tab_identities = IdentitiesTabWidget(self.app)
-        self.tab_history = TransactionsTabWidget(self.app)
-        self.tab_informations = InformationsTabWidget(self.app)
-        self.tab_network = NetworkTabWidget(self.app)
-        self.tab_explorer = ExplorerTabWidget(self.app)
-
-        self.action_publish_uid = QAction(self.tr(CommunityWidget._action_publish_uid_text), self)
-        self.action_revoke_uid = QAction(self.tr(CommunityWidget._action_revoke_uid_text), self)
-        self.action_showinfo = QAction(self.tr(CommunityWidget._action_showinfo_text), self)
-        self.action_explorer = QAction(self.tr(CommunityWidget._action_explore_text), self)
-
-        super().setupUi(self)
-
-        tool_menu = QMenu(self.tr("Tools"), self.toolbutton_menu)
-        self.toolbutton_menu.setMenu(tool_menu)
-
-        self.tab_identities.view_in_wot.connect(self.tab_wot.draw_graph)
-        self.tab_identities.view_in_wot.connect(lambda: self.tabs.setCurrentWidget(self.tab_wot.widget))
-        self.tab_history.view_in_wot.connect(self.tab_wot.draw_graph)
-        self.tab_history.view_in_wot.connect(lambda: self.tabs.setCurrentWidget(self.tab_wot.widget))
-        self.tab_identities.money_sent.connect(lambda: self.tab_history.ui.table_history.model().sourceModel().refresh_transfers())
-        self.tab_wot.money_sent.connect(lambda: self.tab_history.ui.table_history.model().sourceModel().refresh_transfers())
-
-        self.tabs.addTab(self.tab_history.widget,
-                                 QIcon(':/icons/tx_icon'),
-                                self.tr(CommunityWidget._tab_history_label))
-
-        self.tabs.addTab(self.tab_wot.widget,
-                         QIcon(':/icons/wot_icon'),
-                         self.tr(CommunityWidget._tab_wot_label))
-
-        self.tabs.addTab(self.tab_identities.widget,
-                         QIcon(':/icons/members_icon'),
-                         self.tr(CommunityWidget._tab_identities_label))
-
-        self.tabs.addTab(self.tab_network,
-                                 QIcon(":/icons/network_icon"),
-                                 self.tr("Network"))
-
-        action_showinfo = QAction(self.tr("Show informations"), self.toolbutton_menu)
-        action_showinfo.triggered.connect(lambda : self.show_closable_tab(self.tab_informations,
-                                    QIcon(":/icons/informations_icon"), self.tr("Informations")))
-        tool_menu.addAction(action_showinfo)
-
-        action_showexplorer = QAction(self.tr("Show explorer"), self.toolbutton_menu)
-        action_showexplorer.triggered.connect(lambda : self.show_closable_tab(self.tab_explorer.widget,
-                                    QIcon(":/icons/explorer_icon"), self.tr("Explorer")))
-        tool_menu.addAction(action_showexplorer)
-
-        menu_advanced = QMenu(self.tr("Advanced"), self.toolbutton_menu)
-        action_gen_revokation = QAction(self.tr("Save revokation document"), menu_advanced)
-        action_gen_revokation.triggered.connect(self.action_save_revokation)
-        menu_advanced.addAction(action_gen_revokation)
-        tool_menu.addMenu(menu_advanced)
-
-        self.action_publish_uid.triggered.connect(self.publish_uid)
-        tool_menu.addAction(self.action_publish_uid)
-
-        self.button_membership.clicked.connect(self.send_membership_demand)
-
-    def show_closable_tab(self, tab, icon, title):
-        if self.tabs.indexOf(tab) == -1:
-            self.tabs.addTab(tab, icon, title)
-            style = self.app.qapp.style()
-            icon = style.standardIcon(style.SP_DockWidgetCloseButton)
-            close_button = QPushButton(icon, '')
-            close_button.clicked.connect(lambda: self.tabs.removeTab(self.tabs.indexOf(tab)))
-            close_button.setStyleSheet('border-style: inset;')
-            self.tabs.tabBar().setTabButton(self.tabs.indexOf(tab), QTabBar.RightSide, close_button)
-
-    def cancel_once_tasks(self):
-        cancel_once_task(self, self.refresh_block)
-        cancel_once_task(self, self.refresh_status)
-        logging.debug("Cancelled status")
-        cancel_once_task(self, self.refresh_quality_buttons)
-
-    def change_account(self, account, password_asker):
-        self.cancel_once_tasks()
-
-        self.account = account
-
-        self.password_asker = password_asker
-        self.tab_wot.change_account(account, self.password_asker)
-        self.tab_identities.change_account(account, self.password_asker)
-        self.tab_history.change_account(account, self.password_asker)
-        self.tab_informations.change_account(account)
-        self.tab_explorer.change_account(account, self.password_asker)
-
-    def change_community(self, community):
-        self.cancel_once_tasks()
-
-        self.tab_network.change_community(community)
-        self.tab_wot.change_community(community)
-        self.tab_history.change_community(community)
-        self.tab_identities.change_community(community)
-        self.tab_informations.change_community(community)
-        self.tab_explorer.change_community(community)
-
-        if self.community:
-            self.community.network.new_block_mined.disconnect(self.refresh_block)
-            self.community.network.nodes_changed.disconnect(self.refresh_status)
-        if community:
-            community.network.new_block_mined.connect(self.refresh_block)
-            community.network.nodes_changed.connect(self.refresh_status)
-            self.label_currency.setText(community.currency)
-        logging.debug("Changed community to {0}".format(community))
-        self.button_membership.setText(self.tr("Membership"))
-        self.button_membership.setEnabled(False)
-        self.button_certification.setEnabled(False)
-        self.action_publish_uid.setEnabled(False)
-        self.community = community
-        self.refresh_status()
-        self.refresh_quality_buttons()
-
-    @pyqtSlot(str)
-    def display_error(self, error):
-        QMessageBox.critical(self, ":(",
-                    error,
-                    QMessageBox.Ok)
-
-    @asyncify
-    async def action_save_revokation(self, checked=False):
-        password = await self.password_asker.async_exec()
-        if self.password_asker.result() == QDialog.Rejected:
-            return
-
-        raw_document = await self.account.generate_revokation(self.community, password)
-        # Testable way of using a QFileDialog
-        selected_files = await QAsyncFileDialog.get_save_filename(self, self.tr("Save a revokation document"),
-                                                "",  self.tr("All text files (*.txt)"))
-        if selected_files:
-            path = selected_files[0]
-            if not path.endswith('.txt'):
-                path = "{0}.txt".format(path)
-            with open(path, 'w') as save_file:
-                save_file.write(raw_document)
-
-        dialog = QMessageBox(QMessageBox.Information, self.tr("Revokation file"),
-                                           self.tr("""<div>Your revokation document has been saved.</div>
-<div><b>Please keep it in a safe place.</b></div>
-The publication of this document will remove your identity from the network.</p>"""), QMessageBox.Ok,
-                             self)
-        dialog.setTextFormat(Qt.RichText)
-        await dialog_async_exec(dialog)
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_block(self, block_number):
-        """
-        When a new block is found, start handling data.
-        @param: block_number: The number of the block mined
-        """
-        logging.debug("Refresh block")
-        self.status_info.clear()
-        try:
-            person = await self.app.identities_registry.future_find(self.app.current_account.pubkey, self.community)
-            expiration_time = await person.membership_expiration_time(self.community)
-            revokation_time = await person.identity_revocation_time(self.community)
-            parameters = await self.community.parameters()
-            sig_validity = parameters['sigValidity']
-            warning_expiration_time = int(sig_validity / 3)
-            will_expire_soon = (expiration_time < warning_expiration_time)
-            revokation_soon = (revokation_time < 2*warning_expiration_time)
-            if revokation_soon:
-                days = int(revokation_time / 3600 / 24)
-                if 'warning_revokation' not in self.status_info:
-                    self.status_info.append('warning_revokation')
-
-                if self.app.preferences['notifications'] and \
-                        self.account.notifications['warning_revokation'][1]+24*3600 < time.time():
-                    toast.display(self.tr("Identity revokation"),
-                              self.tr("<b>Warning : Your identity will be implicitely revoked\
-                               if you dont renew before {0} days</b>").format(days))
-                    self.account.notifications['warning_revokation'][1] = time.time()
-            elif will_expire_soon:
-                days = int(expiration_time / 3600 / 24)
-                if days > 0:
-                    if 'membership_expire_soon' not in self.status_info:
-                        self.status_info.append('membership_expire_soon')
-
-                    if self.app.preferences['notifications'] and\
-                            self.account.notifications['membership_expire_soon'][1]+24*3600 < time.time():
-                        toast.display(self.tr("Membership expiration"),
-                                  self.tr("<b>Warning : Membership expiration in {0} days</b>").format(days))
-                        self.account.notifications['membership_expire_soon'][1] = time.time()
-
-            certifiers_of = await person.unique_valid_certifiers_of(self.app.identities_registry,
-                                                                         self.community)
-            if len(certifiers_of) < parameters['sigQty']:
-                if 'warning_certifications' not in self.status_info:
-                    self.status_info.append('warning_certifications')
-                if self.app.preferences['notifications'] and\
-                        self.account.notifications['warning_certifications'][1]+24*3600 < time.time():
-                    toast.display(self.tr("Certifications number"),
-                              self.tr("<b>Warning : You are certified by only {0} persons, need {1}</b>")
-                              .format(len(certifiers_of),
-                                     parameters['sigQty']))
-                    self.account.notifications['warning_certifications'][1] = time.time()
-
-        except MembershipNotFoundError as e:
-            pass
-        except NoPeerAvailable:
-            logging.debug("No peer available")
-        self.refresh_data()
-
-    def refresh_data(self):
-        """
-        Refresh data
-        """
-        self.tab_history.refresh_balance()
-        self.refresh_status()
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_status(self):
-        """
-        Refresh status bar
-        """
-        logging.debug("Refresh status")
-        if self.community:
-            text = ""
-
-            current_block_number = self.community.network.current_blockUID.number
-            if current_block_number:
-                text += self.tr("Block {0}").format(current_block_number)
-                try:
-                    block = await self.community.get_block(current_block_number)
-                    text += " ({0})".format(QLocale.toString(
-                                QLocale(),
-                                QDateTime.fromTime_t(block['medianTime']),
-                                QLocale.dateTimeFormat(QLocale(), QLocale.NarrowFormat)
-                            ))
-                except NoPeerAvailable as e:
-                    logging.debug(str(e))
-                    text += " ( ### ) "
-                except errors.DuniterError as e:
-                    if e.ucode == errors.BLOCK_NOT_FOUND:
-                        logging.debug(str(e))
-
-            if len(self.community.network.synced_nodes) == 0:
-                self.button_membership.setEnabled(False)
-                self.button_certification.setEnabled(False)
-                self.button_send_money.setEnabled(False)
-            else:
-                self.button_send_money.setEnabled(True)
-                self.refresh_quality_buttons()
-
-            if self.community.network.quality > 0.66:
-                icon = ':/icons/connected'
-            elif self.community.network.quality > 0.33:
-                icon = ':/icons/weak_connect'
-            else:
-                icon = ':/icons/disconnected'
-
-            status_infotext = " - ".join([self.account.notifications[info][0] for info in self.status_info])
-            label_text = text
-            if status_infotext != "":
-                label_text += " - {0}".format(status_infotext)
-
-            self.status_label.setText(label_text)
-            self.label_icon.setPixmap(QPixmap(icon).scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation))
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_quality_buttons(self):
-        if self.account and self.community:
-            try:
-                account_identity = await self.account.identity(self.community)
-                published_uid = await account_identity.published_uid(self.community)
-                uid_is_revokable = await account_identity.uid_is_revokable(self.community)
-                if published_uid:
-                    logging.debug("UID Published")
-                    self.action_revoke_uid.setEnabled(uid_is_revokable)
-                    is_member = await account_identity.is_member(self.community)
-                    if is_member:
-                        self.button_membership.setText(self.tr("Renew membership"))
-                        self.button_membership.setEnabled(True)
-                        self.button_certification.setEnabled(True)
-                        self.action_publish_uid.setEnabled(False)
-                    else:
-                        logging.debug("Not a member")
-                        self.button_membership.setText(self.tr("Send membership demand"))
-                        self.button_membership.setEnabled(True)
-                        self.action_publish_uid.setEnabled(False)
-                        if await self.community.get_block(0) is not None:
-                            self.button_certification.setEnabled(False)
-                else:
-                    logging.debug("UID not published")
-                    self.button_membership.setEnabled(False)
-                    self.button_certification.setEnabled(False)
-                    self.action_publish_uid.setEnabled(True)
-            except LookupFailureError:
-                self.button_membership.setEnabled(False)
-                self.button_certification.setEnabled(False)
-                self.action_publish_uid.setEnabled(False)
-
-    def showEvent(self, event):
-        self.refresh_status()
-
-    def referential_changed(self):
-        if self.community and self.tab_history.ui.table_history.model():
-            self.tab_history.ui.table_history.model().sourceModel().refresh_transfers()
-            self.tab_history.refresh_balance()
-            self.tab_informations.refresh()
-
-    @asyncify
-    async def send_membership_demand(self, checked=False):
-        password = await self.password_asker.async_exec()
-        if self.password_asker.result() == QDialog.Rejected:
-            return
-        result = await self.account.send_membership(password, self.community, 'IN')
-        if result[0]:
-            if self.app.preferences['notifications']:
-                toast.display(self.tr("Membership"), self.tr("Success sending Membership demand"))
-            else:
-                await QAsyncMessageBox.information(self, self.tr("Membership"),
-                                                        self.tr("Success sending Membership demand"))
-        else:
-            if self.app.preferences['notifications']:
-                toast.display(self.tr("Membership"), result[1])
-            else:
-                await QAsyncMessageBox.critical(self, self.tr("Membership"),
-                                                        result[1])
-
-    @asyncify
-    async def send_membership_leaving(self):
-        reply = await QAsyncMessageBox.warning(self, self.tr("Warning"),
-                             self.tr("""Are you sure ?
-Sending a leaving demand  cannot be canceled.
-The process to join back the community later will have to be done again.""")
-.format(self.account.pubkey), QMessageBox.Ok | QMessageBox.Cancel)
-        if reply == QMessageBox.Ok:
-            password = self.password_asker.exec_()
-            if self.password_asker.result() == QDialog.Rejected:
-                return
-            result = await self.account.send_membership(password, self.community, 'OUT')
-            if result[0]:
-                if self.app.preferences['notifications']:
-                    toast.display(self.tr("Revoke"), self.tr("Success sending Revoke demand"))
-                else:
-                    await QAsyncMessageBox.information(self, self.tr("Revoke"),
-                                                            self.tr("Success sending Revoke demand"))
-            else:
-                if self.app.preferences['notifications']:
-                    toast.display(self.tr("Revoke"), result[1])
-                else:
-                    await QAsyncMessageBox.critical(self, self.tr("Revoke"),
-                                                         result[1])
-
-    @asyncify
-    async def publish_uid(self, checked=False):
-        password = await self.password_asker.async_exec()
-        if self.password_asker.result() == QDialog.Rejected:
-            return
-        result = await self.account.send_selfcert(password, self.community)
-        if result[0]:
-            if self.app.preferences['notifications']:
-                toast.display(self.tr("UID"), self.tr("Success publishing your UID"))
-            else:
-                await QAsyncMessageBox.information(self, self.tr("Membership"),
-                                                        self.tr("Success publishing your UID"))
-        else:
-            if self.app.preferences['notifications']:
-                toast.display(self.tr("UID"), result[1])
-            else:
-                await QAsyncMessageBox.critical(self, self.tr("UID"),
-                                                        result[1])
-
-    def retranslateUi(self, widget):
-        """
-        Method to complete translations missing from generated code
-        :param widget:
-        :return:
-        """
-        self.tabs.setTabText(self.tabs.indexOf(self.tab_wot.widget), self.tr(CommunityWidget._tab_wot_label))
-        self.tabs.setTabText(self.tabs.indexOf(self.tab_network), self.tr(CommunityWidget._tab_network_label))
-        self.tabs.setTabText(self.tabs.indexOf(self.tab_informations), self.tr(CommunityWidget._tab_informations_label))
-        self.tabs.setTabText(self.tabs.indexOf(self.tab_history.widget), self.tr(CommunityWidget._tab_history_label))
-        self.tabs.setTabText(self.tabs.indexOf(self.tab_identities.widget), self.tr(CommunityWidget._tab_identities_label))
-        self.action_publish_uid.setText(self.tr(CommunityWidget._action_publish_uid_text))
-        self.action_revoke_uid.setText(self.tr(CommunityWidget._action_revoke_uid_text))
-        self.action_showinfo.setText(self.tr(CommunityWidget._action_showinfo_text))
-        super().retranslateUi(self)
-
-    def showEvent(self, QShowEvent):
-        """
-
-        :param QShowEvent:
-        :return:
-        """
-        self.refresh_status()
-        super().showEvent(QShowEvent)
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-            self.refresh_status()
-        return super(CommunityWidget, self).changeEvent(event)
diff --git a/src/sakia/gui/contact.py b/src/sakia/gui/contact.py
deleted file mode 100644
index 8d3036b91222e6f993f91173a9294fdad1bc6a0b..0000000000000000000000000000000000000000
--- a/src/sakia/gui/contact.py
+++ /dev/null
@@ -1,95 +0,0 @@
-"""
-Created on 2 févr. 2014
-
-@author: inso
-"""
-import re
-import logging
-
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox
-from ..core.registry import IdentitiesRegistry
-from ..tools.exceptions import ContactAlreadyExists
-from ..gen_resources.contact_uic import Ui_ConfigureContactDialog
-
-
-class ConfigureContactDialog(QDialog, Ui_ConfigureContactDialog):
-
-    """
-    classdocs
-    """
-
-    def __init__(self, app, account, parent=None, contact=None, index_edit=None):
-        """
-        Open the dialog to create a new contact
-        :param sakia.core.Application app: the application
-        :param sakia.core.Account account: the account
-        :param PyQt5.QtWidgets.QWidget parent: the parent widget
-        :param dict contact: the contact with a key 'name' and a key 'pubkey'
-        :param int index_edit: the index of the edited contact in the account contacts list
-        :return:
-        """
-        super().__init__(parent)
-        self.setupUi(self)
-        self.app = app
-        self.account = account
-        self.index_edit = index_edit
-        self.contact = contact
-
-        if index_edit is not None:
-            self.contact = account.contacts[index_edit]
-            self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
-
-        if self.contact:
-            self.edit_name.setText(self.contact['name'])
-            self.edit_pubkey.setText(self.contact['pubkey'])
-
-    @classmethod
-    def from_identity(cls, app, parent, account, identity):
-        contact = {
-            'name': identity.uid,
-            'pubkey': identity.pubkey
-        }
-        return ConfigureContactDialog(app, account, parent, contact)
-
-    @classmethod
-    def new_contact(cls, app, account, parent):
-        """
-        Open the dialog to create a new contact
-        :param sakia.core.Application app: the application
-        :param sakia.core.Account account: the account
-        :param PyQt5.QtWidgets.QWidget parent: the parent widget
-        :return:
-        """
-        return ConfigureContactDialog(app, account, parent)
-
-    @classmethod
-    def edit_contact(cls, app, account, parent, index):
-        return ConfigureContactDialog(app, account, parent, None, index)
-
-    def accept(self):
-        name = self.edit_name.text()
-        pubkey = self.edit_pubkey.text()
-        if self.index_edit is not None:
-            self.account.edit_contact(self.index_edit, {'name': name,
-                          'pubkey': pubkey})
-            logging.debug(self.contact)
-        else:
-            try:
-                self.account.add_contact({'name': name,
-                                          'pubkey': pubkey})
-            except ContactAlreadyExists as e:
-                QMessageBox.critical(self, self.tr("Contact already exists"),
-                            str(e),
-                            QMessageBox.Ok)
-        self.app.save(self.account)
-        super().accept()
-
-    def name_edited(self, new_name):
-        name_ok = len(new_name) > 0
-        self.button_box.button(QDialogButtonBox.Ok).setEnabled(name_ok)
-
-    def pubkey_edited(self, new_pubkey):
-        pattern = re.compile("([1-9A-Za-z][^OIl]{42,45})")
-        self.button_box.button(
-            QDialogButtonBox.Ok).setEnabled(
-            pattern.match(new_pubkey)is not None)
diff --git a/src/sakia/core/net/api/bma/__init__.py b/src/sakia/gui/dialogs/__init__.py
similarity index 100%
rename from src/sakia/core/net/api/bma/__init__.py
rename to src/sakia/gui/dialogs/__init__.py
diff --git a/src/sakia/gen_resources/__init__.py b/src/sakia/gui/dialogs/certification/__init__.py
similarity index 100%
rename from src/sakia/gen_resources/__init__.py
rename to src/sakia/gui/dialogs/certification/__init__.py
diff --git a/src/sakia/gui/dialogs/certification/certification.ui b/src/sakia/gui/dialogs/certification/certification.ui
new file mode 100644
index 0000000000000000000000000000000000000000..f67156ae2a03696f7c30b8337562a0ea5a3be949
--- /dev/null
+++ b/src/sakia/gui/dialogs/certification/certification.ui
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CertificationDialog</class>
+ <widget class="QDialog" name="CertificationDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>517</width>
+    <height>338</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Certification</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="title">
+      <string>Select your identity</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <item>
+       <widget class="QComboBox" name="combo_pubkey"/>
+      </item>
+      <item>
+       <widget class="QGroupBox" name="groupBox_3">
+        <property name="title">
+         <string>Certifications stock</string>
+        </property>
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <item>
+          <widget class="QLabel" name="label_cert_stock">
+           <property name="text">
+            <string/>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupbox_certified">
+     <property name="title">
+      <string>Certify user</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3"/>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="button_box">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+ <slots>
+  <slot>open_manage_wallet_coins()</slot>
+  <slot>change_displayed_wallet(int)</slot>
+  <slot>transfer_mode_changed(bool)</slot>
+  <slot>recipient_mode_changed(bool)</slot>
+  <slot>change_current_community(int)</slot>
+  <slot>amount_changed()</slot>
+  <slot>relative_amount_changed()</slot>
+ </slots>
+</ui>
diff --git a/src/sakia/gui/dialogs/certification/controller.py b/src/sakia/gui/dialogs/certification/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..c656ede2529b9620d450611fa423af4852abab2a
--- /dev/null
+++ b/src/sakia/gui/dialogs/certification/controller.py
@@ -0,0 +1,178 @@
+import asyncio
+
+from PyQt5.QtCore import Qt, QObject
+from PyQt5.QtWidgets import QApplication
+
+from sakia.data.entities import Identity
+from sakia.decorators import asyncify
+from sakia.gui.sub.search_user.controller import SearchUserController
+from sakia.gui.sub.user_information.controller import UserInformationController
+from sakia.gui.password_asker import PasswordAskerDialog
+from .model import CertificationModel
+from .view import CertificationView
+import attr
+
+
+@attr.s()
+class CertificationController(QObject):
+    """
+    The Certification view
+    """
+
+    view = attr.ib()
+    model = attr.ib()
+    search_user = attr.ib(default=None)
+    user_information = attr.ib(default=None)
+
+    def __attrs_post_init__(self):
+        super().__init__()
+        self.view.button_box.accepted.connect(self.accept)
+        self.view.button_box.rejected.connect(self.reject)
+        self.view.combo_pubkey.currentIndexChanged.connect(self.change_connection)
+
+    @classmethod
+    def create(cls, parent, app):
+        """
+        Instanciate a Certification component
+        :param sakia.gui.component.controller.ComponentController parent:
+        :param sakia.app.Application app: sakia application
+        :return: a new Certification controller
+        :rtype: CertificationController
+        """
+        view = CertificationView(parent.view if parent else None, None, None)
+        model = CertificationModel(app)
+        certification = cls(view, model, None, None)
+
+        search_user = SearchUserController.create(certification, app, "")
+        certification.set_search_user(search_user)
+
+        user_information = UserInformationController.create(certification, app, "", None)
+        certification.set_user_information(user_information)
+
+        view.set_keys(certification.model.available_connections())
+        return certification
+
+    @classmethod
+    def open_dialog(cls, parent, app, connection):
+        """
+        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
+        :return:
+        """
+        dialog = cls.create(parent, app)
+        if connection:
+            dialog.view.combo_pubkey.setCurrentText(connection.title())
+        dialog.refresh()
+        return dialog.exec()
+
+    @classmethod
+    async def certify_identity(cls, parent, app, connection, identity):
+        """
+        Certify and identity
+        :param sakia.gui.component.controller.ComponentController parent: the parent
+        :param sakia.core.Application app: the application
+        :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_pubkey.setCurrentText(connection.title())
+        dialog.user_information.change_identity(identity)
+        dialog.refresh()
+        return await dialog.async_exec()
+
+    def set_search_user(self, search_user):
+        """
+
+        :param search_user:
+        :return:
+        """
+        self.search_user = search_user
+        self.view.set_search_user(search_user.view)
+        search_user.identity_selected.connect(self.refresh_user_information)
+
+    def set_user_information(self, user_information):
+        """
+
+        :param user_information:
+        :return:
+        """
+        self.user_information = user_information
+        self.view.set_user_information(user_information.view)
+        self.user_information.identity_loaded.connect(self.refresh)
+
+    @asyncify
+    async def accept(self):
+        """
+        Validate the dialog
+        """
+        self.view.button_box.setDisabled(True)
+        password = await PasswordAskerDialog(self.model.connection).async_exec()
+        if not password:
+            self.view.button_box.setEnabled(True)
+            return
+        QApplication.setOverrideCursor(Qt.WaitCursor)
+        result = await self.model.certify_identity(password, self.user_information.model.identity)
+
+        if result[0]:
+            QApplication.restoreOverrideCursor()
+            await self.view.show_success(self.model.notification())
+            self.view.accept()
+        else:
+            await self.view.show_error(self.model.notification(), result[1])
+            QApplication.restoreOverrideCursor()
+            self.view.button_box.setEnabled(True)
+
+    @asyncify
+    async def reject(self):
+        self.view.reject()
+
+    def refresh(self):
+        stock = self.model.get_cert_stock()
+        written, pending = self.model.nb_certifications()
+        days, hours, minutes, seconds = self.model.remaining_time()
+        self.view.display_cert_stock(written, pending, stock, days, hours, minutes)
+
+        if self.model.could_certify():
+            if written < stock or stock == 0:
+                if not self.user_information.model.identity:
+                    self.view.set_button_box(CertificationView.ButtonBoxState.SELECT_IDENTITY)
+                elif days+hours+minutes > 0:
+                    if days > 0:
+                        remaining_localized = self.tr("{days} days").format(days=days)
+                    else:
+                        remaining_localized = self.tr("{hours}h {min}min").format(hours=hours, min=minutes)
+                    self.view.set_button_box(CertificationView.ButtonBoxState.REMAINING_TIME_BEFORE_VALIDATION,
+                                             remaining=remaining_localized)
+                else:
+                    self.view.set_button_box(CertificationView.ButtonBoxState.OK)
+            else:
+                    self.view.set_button_box(CertificationView.ButtonBoxState.NO_MORE_CERTIFICATION)
+        else:
+            self.view.set_button_box(CertificationView.ButtonBoxState.NOT_A_MEMBER)
+
+    def refresh_user_information(self):
+        """
+        Refresh user information
+        """
+        self.user_information.search_identity(self.search_user.model.identity())
+
+    def change_connection(self, 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):
+        future = asyncio.Future()
+        self.view.finished.connect(lambda r: future.set_result(r))
+        self.view.open()
+        self.refresh()
+        return future
+
+    def exec(self):
+        self.refresh()
+        self.view.exec()
diff --git a/src/sakia/gui/dialogs/certification/model.py b/src/sakia/gui/dialogs/certification/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3b5a6bda0f8fa70ae42159a40b6c1cf2c85f4b6
--- /dev/null
+++ b/src/sakia/gui/dialogs/certification/model.py
@@ -0,0 +1,94 @@
+from PyQt5.QtCore import QObject
+from sakia.data.processors import IdentitiesProcessor, CertificationsProcessor, \
+    BlockchainProcessor, ConnectionsProcessor
+import attr
+
+
+@attr.s()
+class CertificationModel(QObject):
+    """
+    The model of Certification component
+    """
+
+    app = attr.ib()
+    connection = attr.ib(default=None)
+    _connections_processor = attr.ib(default=None)
+    _certifications_processor = attr.ib(default=None)
+    _identities_processor = attr.ib(default=None)
+    _blockchain_processor = attr.ib(default=None)
+
+    def __attrs_post_init__(self):
+        super().__init__()
+        self._connections_processor = ConnectionsProcessor.instanciate(self.app)
+        self._certifications_processor = CertificationsProcessor.instanciate(self.app)
+        self._identities_processor = IdentitiesProcessor.instanciate(self.app)
+        self._blockchain_processor = BlockchainProcessor.instanciate(self.app)
+
+    def change_connection(self, index):
+        """
+        Change current currency
+        :param int index: index of the community in the account list
+        """
+        self.connection = self.connections_repo.get_currencies()[index]
+
+    def get_cert_stock(self):
+        """
+
+        :return: the certifications stock
+        :rtype: int
+        """
+        return self._blockchain_processor.parameters(self.connection.currency).sig_stock
+
+    def remaining_time(self):
+        """
+        Get remaining time as a tuple to display
+        :return: a tuple containing (days, hours, minutes, seconds)
+        :rtype: tuple[int]
+        """
+        parameters = self._blockchain_processor.parameters(self.connection.currency)
+        blockchain_time = self._blockchain_processor.time(self.connection.currency)
+        remaining_time = self._certifications_processor.cert_issuance_delay(self.connection.currency,
+                                                                            self.connection.pubkey,
+                                                                            parameters, blockchain_time)
+
+        days, remainder = divmod(remaining_time, 3600 * 24)
+        hours, remainder = divmod(remainder, 3600)
+        minutes, seconds = divmod(remainder, 60)
+        return days, hours, minutes, seconds
+
+    def nb_certifications(self):
+        """
+        Get
+        :return: a tuple containing (written valid certifications, pending certifications)
+        :rtype: tuple[int]
+        """
+        certifications = self._certifications_processor.certifications_sent(self.connection.currency,
+                                                                            self.connection.pubkey)
+        nb_certifications = len([c for c in certifications if c.written_on])
+        nb_cert_pending = len([c for c in certifications if not c.written_on])
+        return nb_certifications, nb_cert_pending
+
+    def could_certify(self):
+        """
+        Check if the user could theorically certify
+        :return: true if the user can certifiy
+        :rtype: bool
+        """
+        is_member = self._identities_processor.get_identity(self.connection.currency,
+                                                            self.connection.pubkey,
+                                                            self.connection.pubkey)
+
+        return is_member and self._blockchain_processor.current_buid(self.connection.currency)
+
+    def available_connections(self):
+        return self._connections_processor.connections()
+
+    def set_connection(self,  index):
+        connections = self._connections_processor.connections()
+        self.connection = connections[index]
+
+    def notification(self):
+        return self.app.parameters.notifications
+
+    async def certify_identity(self, password, identity):
+        return await self.app.documents_service.certify(self.connection, password, identity)
diff --git a/src/sakia/gui/dialogs/certification/view.py b/src/sakia/gui/dialogs/certification/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ac5dc8a7ad7fc44a72555624b75397ed878afa7
--- /dev/null
+++ b/src/sakia/gui/dialogs/certification/view.py
@@ -0,0 +1,127 @@
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox
+from PyQt5.QtCore import QT_TRANSLATE_NOOP, pyqtSignal
+from .certification_uic import Ui_CertificationDialog
+from sakia.gui.widgets import toast
+from sakia.gui.widgets.dialogs import QAsyncMessageBox
+from enum import Enum
+
+
+class CertificationView(QDialog, Ui_CertificationDialog):
+    """
+    The view of the certification component
+    """
+
+    class ButtonBoxState(Enum):
+        NO_MORE_CERTIFICATION = 0
+        NOT_A_MEMBER = 1
+        REMAINING_TIME_BEFORE_VALIDATION = 2
+        OK = 3
+        SELECT_IDENTITY = 4
+
+    _button_box_values = {
+        ButtonBoxState.NO_MORE_CERTIFICATION: (False,
+                                               QT_TRANSLATE_NOOP("CertificationView", "No more certifications")),
+        ButtonBoxState.NOT_A_MEMBER: (False, QT_TRANSLATE_NOOP("CertificationView", "Not a member")),
+        ButtonBoxState.SELECT_IDENTITY: (False, QT_TRANSLATE_NOOP("CertificationView", "Please select an identity")),
+        ButtonBoxState.REMAINING_TIME_BEFORE_VALIDATION: (True,
+                                                          QT_TRANSLATE_NOOP("CertificationView",
+                                                                            "&Ok (Not validated before {remaining})")),
+        ButtonBoxState.OK: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok"))
+    }
+
+    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[sakia.data.entities.Connection] connections:
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.search_user = search_user_view
+        self.user_information_view = user_information_view
+
+    def set_keys(self, connections):
+        self.combo_pubkey.clear()
+        for c in connections:
+            self.combo_pubkey.addItem(c.title())
+
+    def set_selected_key(self, connection):
+        """
+        :param sakia.data.entities.Connection connection:
+        """
+        self.combo_pubkey.setCurrentText(connection.title())
+
+    def set_search_user(self, search_user_view):
+        """
+
+        :param sakia.gui.search_user.view.SearchUserView search_user_view:
+        :return:
+        """
+        self.search_user = search_user_view
+        self.groupbox_certified.layout().addWidget(search_user_view)
+        self.search_user.button_reset.hide()
+
+    def set_user_information(self, user_information_view):
+        self.user_information_view = user_information_view
+        self.groupbox_certified.layout().addWidget(user_information_view)
+
+    def pubkey_value(self):
+        return self.edit_pubkey.text()
+
+    async def show_success(self, notification):
+        if notification:
+            toast.display(self.tr("Certification"),
+                          self.tr("Success sending certification"))
+        else:
+            await QAsyncMessageBox.information(self.widget, self.tr("Certification"),
+                                         self.tr("Success sending certification"))
+
+    async def show_error(self, notification, error_txt):
+
+        if notification:
+            toast.display(self.tr("Certification"), self.tr("Could not broadcast certification : {0}"
+                                                            .format(error_txt)))
+        else:
+            await QAsyncMessageBox.critical(self.widget, self.tr("Certification"),
+                                            self.tr("Could not broadcast certification : {0}"
+                                                    .format(error_txt)))
+
+    def display_cert_stock(self, written, pending, stock,
+                           remaining_days, remaining_hours, remaining_minutes):
+        """
+        Display values in informations label
+        :param int written: number of written certifications
+        :param int pending: number of pending certifications
+        :param int stock: maximum certifications
+        :param int remaining_days:
+        :param int remaining_hours:
+        :param int remaining_minutes:
+        """
+        cert_text = self.tr("Certifications sent : {nb_certifications}/{stock}").format(
+            nb_certifications=written,
+            stock=stock)
+        if pending > 0:
+            cert_text += " (+{nb_cert_pending} certifications pending)".format(nb_cert_pending=pending)
+
+        if remaining_days > 0:
+            remaining_localized = self.tr("{days} days").format(days=remaining_days)
+        else:
+            remaining_localized = self.tr("{hours} hours and {min} min.").format(hours=remaining_hours,
+                                                                            min=remaining_minutes)
+        cert_text += "\n"
+        cert_text += self.tr("Remaining time before next certification validation : {0}".format(remaining_localized))
+        self.label_cert_stock.setText(cert_text)
+
+    def set_button_box(self, state, **kwargs):
+        """
+        Set button box state
+        :param sakia.gui.certification.view.CertificationView.ButtonBoxState state: the state of te button box
+        :param dict kwargs: the values to replace from the text in the state
+        :return:
+        """
+        button_box_state = CertificationView._button_box_values[state]
+        self.button_box.button(QDialogButtonBox.Ok).setEnabled(button_box_state[0])
+        self.button_box.button(QDialogButtonBox.Ok).setText(button_box_state[1].format(**kwargs))
diff --git a/src/sakia/gui/dialogs/connection_cfg/__init__.py b/src/sakia/gui/dialogs/connection_cfg/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..28a47ef45ea3b576216d2009ecd3fd877d27b4f0
--- /dev/null
+++ b/src/sakia/gui/dialogs/connection_cfg/__init__.py
@@ -0,0 +1 @@
+from .controller import ConnectionConfigController
\ No newline at end of file
diff --git a/src/sakia/gui/dialogs/connection_cfg/connection_cfg.ui b/src/sakia/gui/dialogs/connection_cfg/connection_cfg.ui
new file mode 100644
index 0000000000000000000000000000000000000000..a70c6198cab5d72587155461fde27ac00e9d70f2
--- /dev/null
+++ b/src/sakia/gui/dialogs/connection_cfg/connection_cfg.ui
@@ -0,0 +1,489 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConnectionConfigurationDialog</class>
+ <widget class="QDialog" name="ConnectionConfigurationDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>623</width>
+    <height>545</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Add a connection</string>
+  </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QStackedWidget" name="stacked_pages">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="page_node">
+      <layout class="QVBoxLayout" name="verticalLayout_12">
+       <item>
+        <widget class="QLabel" name="label_2">
+         <property name="text">
+          <string>Please enter the address of a node :</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_10">
+         <property name="rightMargin">
+          <number>5</number>
+         </property>
+         <item>
+          <widget class="QLineEdit" name="edit_server"/>
+         </item>
+         <item>
+          <widget class="QLabel" name="label_double_dot">
+           <property name="text">
+            <string>:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QSpinBox" name="spinbox_port">
+           <property name="maximum">
+            <number>65535</number>
+           </property>
+           <property name="value">
+            <number>8001</number>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QCheckBox" name="checkbox_secured">
+           <property name="text">
+            <string>SSL/TLS</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_13">
+         <property name="topMargin">
+          <number>6</number>
+         </property>
+         <item>
+          <widget class="QPushButton" name="button_register">
+           <property name="text">
+            <string>Register a new account</string>
+           </property>
+           <property name="icon">
+            <iconset resource="../../../../../res/icons/icons.qrc">
+             <normaloff>:/icons/new_membership</normaloff>:/icons/new_membership</iconset>
+           </property>
+           <property name="iconSize">
+            <size>
+             <width>32</width>
+             <height>32</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="button_connect">
+           <property name="text">
+            <string>Connect with an existing account</string>
+           </property>
+           <property name="icon">
+            <iconset resource="../../../../../res/icons/icons.qrc">
+             <normaloff>:/icons/connect_icon</normaloff>:/icons/connect_icon</iconset>
+           </property>
+           <property name="iconSize">
+            <size>
+             <width>32</width>
+             <height>32</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="button_wallet">
+           <property name="text">
+            <string>Connect a wallet</string>
+           </property>
+           <property name="icon">
+            <iconset resource="../../../../../res/icons/icons.qrc">
+             <normaloff>:/icons/wallet_icon</normaloff>:/icons/wallet_icon</iconset>
+           </property>
+           <property name="iconSize">
+            <size>
+             <width>50</width>
+             <height>32</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="verticalSpacer_8">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>20</width>
+             <height>40</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="page_connection">
+      <layout class="QVBoxLayout" name="verticalLayout_4">
+       <item>
+        <widget class="QGroupBox" name="groupBox">
+         <property name="title">
+          <string>Account parameters</string>
+         </property>
+         <layout class="QVBoxLayout" name="verticalLayout_2">
+          <item>
+           <spacer name="verticalSpacer_3">
+            <property name="orientation">
+             <enum>Qt::Vertical</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>20</width>
+              <height>40</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout">
+            <item>
+             <widget class="QLabel" name="label_action">
+              <property name="text">
+               <string>Account name (uid)</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="edit_uid"/>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_8">
+            <property name="topMargin">
+             <number>6</number>
+            </property>
+            <item>
+             <spacer name="horizontalSpacer_2">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>40</width>
+                <height>20</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+            <item>
+             <widget class="QPushButton" name="button_delete">
+              <property name="styleSheet">
+               <string notr="true">color: rgb(255, 0, 0);</string>
+              </property>
+              <property name="text">
+               <string>Delete account</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <spacer name="verticalSpacer_4">
+            <property name="orientation">
+             <enum>Qt::Vertical</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>20</width>
+              <height>40</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+          <item>
+           <layout class="QVBoxLayout" name="verticalLayout_6">
+            <item>
+             <widget class="QLabel" name="label_3">
+              <property name="text">
+               <string>Key parameters</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_5"/>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="edit_salt">
+              <property name="text">
+               <string/>
+              </property>
+              <property name="placeholderText">
+               <string>Secret key</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="edit_salt_bis">
+              <property name="placeholderText">
+               <string>Please repeat your secret key</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="Line" name="line">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="edit_password">
+              <property name="echoMode">
+               <enum>QLineEdit::Password</enum>
+              </property>
+              <property name="placeholderText">
+               <string>Your password</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="edit_password_repeat">
+              <property name="text">
+               <string/>
+              </property>
+              <property name="echoMode">
+               <enum>QLineEdit::Password</enum>
+              </property>
+              <property name="placeholderText">
+               <string>Please repeat your password</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_2">
+              <property name="topMargin">
+               <number>6</number>
+              </property>
+              <item>
+               <widget class="QLabel" name="label_6">
+                <property name="text">
+                 <string>Scrypt parameters</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QComboBox" name="combo_scrypt_params">
+                <item>
+                 <property name="text">
+                  <string>Simple</string>
+                 </property>
+                </item>
+                <item>
+                 <property name="text">
+                  <string>Secure</string>
+                 </property>
+                </item>
+                <item>
+                 <property name="text">
+                  <string>Hardest</string>
+                 </property>
+                </item>
+                <item>
+                 <property name="text">
+                  <string>Extreme</string>
+                 </property>
+                </item>
+               </widget>
+              </item>
+              <item>
+               <spacer name="horizontalSpacer_3">
+                <property name="orientation">
+                 <enum>Qt::Horizontal</enum>
+                </property>
+                <property name="sizeHint" stdset="0">
+                 <size>
+                  <width>40</width>
+                  <height>20</height>
+                 </size>
+                </property>
+               </spacer>
+              </item>
+              <item>
+               <widget class="QLabel" name="label">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>N :</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QSpinBox" name="spin_n"/>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_4">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>r :</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QSpinBox" name="spin_r"/>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_5">
+                <property name="text">
+                 <string>p :</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QSpinBox" name="spin_p">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_6">
+              <property name="topMargin">
+               <number>5</number>
+              </property>
+              <item>
+               <widget class="QPushButton" name="button_generate">
+                <property name="text">
+                 <string>Show public key</string>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_4">
+            <property name="topMargin">
+             <number>5</number>
+            </property>
+            <item>
+             <spacer name="horizontalSpacer">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>40</width>
+                <height>20</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+            <item>
+             <widget class="QPushButton" name="button_next">
+              <property name="text">
+               <string>Next</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="page_services">
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <item>
+        <widget class="QProgressBar" name="progress_bar">
+         <property name="value">
+          <number>24</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPlainTextEdit" name="plain_text_edit"/>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_currency">
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_info">
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../../../../../res/icons/icons.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+  <slot>open_process_add_community()</slot>
+  <slot>key_changed(int)</slot>
+  <slot>action_remove_community()</slot>
+  <slot>open_process_edit_community(QModelIndex)</slot>
+  <slot>next()</slot>
+  <slot>previous()</slot>
+  <slot>open_import_key()</slot>
+  <slot>open_generate_account_key()</slot>
+  <slot>action_edit_account_key()</slot>
+  <slot>action_edit_account_parameters()</slot>
+  <slot>action_show_pubkey()</slot>
+  <slot>action_delete_account()</slot>
+ </slots>
+</ui>
diff --git a/src/sakia/gui/dialogs/connection_cfg/controller.py b/src/sakia/gui/dialogs/connection_cfg/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd0e056ecce8ec321d0c6e969391ad17f2cbfbe7
--- /dev/null
+++ b/src/sakia/gui/dialogs/connection_cfg/controller.py
@@ -0,0 +1,333 @@
+import asyncio
+import logging
+
+from aiohttp.errors import DisconnectedError, ClientError, TimeoutError
+from duniterpy.documents import MalformedDocumentError
+from duniterpy.api.errors import DuniterError
+from sakia.errors import NoPeerAvailable
+from sakia.decorators import asyncify
+from sakia.data.processors import IdentitiesProcessor, NodesProcessor
+from sakia.data.connectors import BmaConnector
+from sakia.gui.password_asker import PasswordAskerDialog, detect_non_printable
+from .model import ConnectionConfigModel
+from .view import ConnectionConfigView
+from PyQt5.QtCore import QObject
+
+
+class ConnectionConfigController(QObject):
+    """
+    The AccountConfigController view
+    """
+
+    CONNECT = 0
+    REGISTER = 1
+    WALLET = 2
+
+    def __init__(self, parent, view, model):
+        """
+        Constructor of the AccountConfigController component
+
+        :param sakia.gui.dialogs.connection_cfg.view.ConnectionConfigView: the view
+        :param sakia.gui.dialogs.connection_cfg.model.ConnectionConfigView model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+
+        self.step_node = asyncio.Future()
+        self.step_key = asyncio.Future()
+        self.view.button_connect.clicked.connect(
+            lambda: self.step_node.set_result(ConnectionConfigController.CONNECT))
+        self.view.button_register.clicked.connect(
+            lambda: self.step_node.set_result(ConnectionConfigController.REGISTER))
+        self.view.button_wallet.clicked.connect(
+            lambda: self.step_node.set_result(ConnectionConfigController.WALLET))
+        self.password_asker = None
+        self.view.values_changed.connect(lambda: self.view.button_next.setEnabled(self.check_key()))
+        self.view.values_changed.connect(lambda: self.view.button_generate.setEnabled(self.check_key()))
+        self._logger = logging.getLogger('sakia')
+
+    @classmethod
+    def create(cls, parent, app):
+        """
+        Instanciate a AccountConfigController component
+        :param sakia.gui.component.controller.ComponentController parent:
+        :param sakia.app.Application app:
+        :return: a new AccountConfigController controller
+        :rtype: AccountConfigController
+        """
+        view = ConnectionConfigView(parent.view if parent else None)
+        model = ConnectionConfigModel(None, app, None,
+                                      IdentitiesProcessor(app.db.identities_repo, app.db.blockchains_repo,
+                                                          BmaConnector(NodesProcessor(app.db.nodes_repo),
+                                                                       app.parameters)))
+        account_cfg = cls(parent, view, model)
+        model.setParent(account_cfg)
+        return account_cfg
+
+    @classmethod
+    def create_connection(cls, parent, app):
+        """
+        Open a dialog to create a new account
+        :param parent:
+        :param app:
+        :return:
+        """
+        connection_cfg = cls.create(parent, app)
+        connection_cfg.view.set_creation_layout()
+        asyncio.ensure_future(connection_cfg.process())
+        return connection_cfg
+
+    @classmethod
+    def modify_connection(cls, parent, app, connection):
+        """
+        Open a dialog to modify an existing account
+        :param parent:
+        :param app:
+        :param account:
+        :return:
+        """
+        connection_cfg = cls.create(parent, app, connection=connection)
+        #connection_cfg.view.set_modification_layout(account.name)
+        connection_cfg._current_step = 1
+
+    def init_nodes_page(self):
+        self.view.set_steps_buttons_visible(True)
+        model = self.model.init_nodes_model()
+        self.view.tree_peers.customContextMenuRequested(self.show_context_menu)
+
+        self.view.set_nodes_model(model)
+        self.view.button_previous.setEnabled(False)
+        self.view.button_next.setText(self.config_dialog.tr("Ok"))
+
+    def init_name_page(self):
+        """
+        Initialize an account name page
+        """
+        if self.model.connection:
+            self.view.set_account_name(self.model.connection.uid)
+
+        self.view.button_previous.setEnabled(False)
+        self.view.button_next.setEnabled(False)
+
+    def check_name(self):
+        return len(self.view.edit_account_name.text()) > 2
+
+    async def process(self):
+        self._logger.debug("Begin process")
+        if self.model.connection:
+            mode = await self.step_node
+        else:
+            while not self.model.connection:
+                mode = await self.step_node
+                self._logger.debug("Create connection")
+                try:
+                    self.view.button_connect.setEnabled(False)
+                    self.view.button_register.setEnabled(False)
+                    await self.model.create_connection(self.view.edit_server.text(),
+                                                       self.view.spinbox_port.value(),
+                                                       self.view.checkbox_secured.isChecked())
+                    self.password_asker = PasswordAskerDialog(self.model.connection)
+                except (DisconnectedError, ClientError, MalformedDocumentError, ValueError, TimeoutError) as e:
+                    self._logger.debug(str(e))
+                    self.view.display_info(self.tr("Could not connect. Check hostname, ip address or port : <br/>"
+                                                   + str(e)))
+                    self.step_node = asyncio.Future()
+                    self.view.button_connect.setEnabled(True)
+                    self.view.button_register.setEnabled(True)
+
+        self._logger.debug("Key step")
+        self.view.set_currency(self.model.connection.currency)
+        if mode == ConnectionConfigController.REGISTER:
+            self._logger.debug("Registering mode")
+            self.view.button_next.clicked.connect(self.check_register)
+            self.view.stacked_pages.setCurrentWidget(self.view.page_connection)
+            connection_identity = await self.step_key
+        elif mode == ConnectionConfigController.CONNECT:
+            self._logger.debug("Connect mode")
+            self.view.button_next.clicked.connect(self.check_connect)
+            self.view.stacked_pages.setCurrentWidget(self.view.page_connection)
+            connection_identity = await self.step_key
+        elif mode == ConnectionConfigController.WALLET:
+            self._logger.debug("Wallet mode")
+            self.view.button_next.clicked.connect(self.check_wallet)
+            self.view.edit_uid.hide()
+            self.view.stacked_pages.setCurrentWidget(self.view.page_connection)
+            connection_identity = await self.step_key
+
+        self.model.insert_or_update_connection()
+        self.view.stacked_pages.setCurrentWidget(self.view.page_services)
+        self.view.progress_bar.setValue(0)
+        self.view.progress_bar.setMaximum(3)
+        try:
+            await self.model.initialize_blockchain(self.view.stream_log)
+            self.view.progress_bar.setValue(1)
+
+            if mode == ConnectionConfigController.REGISTER:
+                self.view.display_info(self.tr("Broadcasting identity..."))
+                self.view.stream_log("Broadcasting identity...")
+                result, connection_identity = await self.model.publish_selfcert()
+                if result[0]:
+                    await self.view.show_success(self.model.notification())
+                else:
+                    self.view.show_error(self.model.notification(), result[1])
+
+            self.view.progress_bar.setValue(2)
+
+            if mode in (ConnectionConfigController.REGISTER, ConnectionConfigController.CONNECT):
+                self.view.stream_log("Saving identity...")
+                self.model.connection.blockstamp = connection_identity.blockstamp
+                self.model.insert_or_update_connection()
+                self.model.insert_or_update_identity(connection_identity)
+                self.view.stream_log("Initializing identity informations...")
+                await self.model.initialize_identity(connection_identity, log_stream=self.view.stream_log)
+                self.view.stream_log("Initializing certifications informations...")
+                await self.model.initialize_certifications(connection_identity, log_stream=self.view.stream_log)
+
+            if mode in (ConnectionConfigController.REGISTER,
+                        ConnectionConfigController.CONNECT,
+                        ConnectionConfigController.WALLET):
+                self.view.stream_log("Initializing transactions history...")
+                transactions = await self.model.initialize_transactions(self.model.connection, log_stream=self.view.stream_log)
+                self.view.stream_log("Initializing dividends history...")
+                await self.model.initialize_dividends(self.model.connection, transactions, log_stream=self.view.stream_log)
+
+            self.view.progress_bar.setValue(3)
+            await self.model.initialize_sources(self.view.stream_log)
+
+            self._logger.debug("Validate changes")
+            self.model.app.db.commit()
+            if self.model.node_connector:
+                await self.model.node_connector.session.close()
+        except (NoPeerAvailable, DuniterError) as e:
+            raise
+            self._logger.debug(str(e))
+            self.view.stacked_pages.setCurrentWidget(self.view.page_connection)
+            self.step_node = asyncio.Future()
+            self.step_node.set_result(mode)
+            self.step_key = asyncio.Future()
+            self.view.button_next.disconnect()
+            self.view.edit_uid.show()
+            asyncio.ensure_future(self.process())
+            return
+        self.accept()
+
+    def check_key(self):
+        if self.model.app.parameters.expert_mode:
+            return True
+
+        if len(self.view.edit_salt.text()) < 6:
+            self.view.label_info.setText(self.tr("Forbidden : salt is too short"))
+            return False
+
+        if len(self.view.edit_password.text()) < 6:
+            self.view.label_info.setText(self.tr("Forbidden : password is too short"))
+            return False
+
+        if detect_non_printable(self.view.edit_salt.text()):
+            self.view.label_info.setText(self.tr("Forbidden : Invalid characters in salt field"))
+            return False
+
+        if detect_non_printable(self.view.edit_password.text()):
+            self.view.label_info.setText(
+                self.tr("Forbidden : Invalid characters in password field"))
+            return False
+
+        if self.view.edit_password.text() != \
+                self.view.edit_password_repeat.text():
+            self.view.label_info.setText(self.tr("Error : passwords are different"))
+            return False
+
+        if self.view.edit_salt.text() != \
+                self.view.edit_salt_bis.text():
+            self.view.label_info.setText(self.tr("Error : secret keys are different"))
+            return False
+
+        self.view.label_info.setText("")
+        return True
+
+    @asyncify
+    async def check_wallet(self, checked=False):
+        self._logger.debug("Is valid ? ")
+        self.view.display_info(self.tr("connecting..."))
+        try:
+            salt = self.view.edit_salt.text()
+            password = self.view.edit_password.text()
+            self.model.set_scrypt_infos(salt, password, self.view.scrypt_params)
+            self.model.set_uid("")
+            if not self.model.key_exists():
+                registered, found_identity = await self.model.check_registered()
+                self.view.button_connect.setEnabled(True)
+                self.view.button_register.setEnabled(True)
+                if registered[0] is False and registered[2] is None:
+                    self.step_key.set_result(None)
+                elif registered[2]:
+                    self.view.display_info(self.tr("""Your pubkey is associated to a pubkey.
+    Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
+            else:
+                self.view.display_info(self.tr("A connection already exists using this key."))
+
+        except NoPeerAvailable:
+            self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry"))
+
+    @asyncify
+    async def check_connect(self, checked=False):
+        self._logger.debug("Is valid ? ")
+        self.view.display_info(self.tr("connecting..."))
+        try:
+            salt = self.view.edit_salt.text()
+            password = self.view.edit_password.text()
+            self.model.set_scrypt_infos(salt, password, self.view.scrypt_params)
+            self.model.set_uid(self.view.edit_uid.text())
+            if not self.model.key_exists():
+                registered, found_identity = await self.model.check_registered()
+                self.view.button_connect.setEnabled(True)
+                self.view.button_register.setEnabled(True)
+                if registered[0] is False and registered[2] is None:
+                    self.view.display_info(self.tr("Could not find your identity on the network."))
+                elif registered[0] is False and registered[2]:
+                    self.view.display_info(self.tr("""Your pubkey or UID is different on the network.
+    Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
+                else:
+                    self.step_key.set_result(found_identity)
+            else:
+                self.view.display_info(self.tr("A connection already exists using this key."))
+
+        except NoPeerAvailable:
+            self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry"))
+
+    @asyncify
+    async def check_register(self, checked=False):
+        self._logger.debug("Is valid ? ")
+        self.view.display_info(self.tr("connecting..."))
+        try:
+            salt = self.view.edit_salt.text()
+            password = self.view.edit_password.text()
+            self.model.set_scrypt_infos(salt, password, self.view.scrypt_params)
+            self.model.set_uid(self.view.edit_uid.text())
+            if not self.model.key_exists():
+                registered, found_identity = await self.model.check_registered()
+                if registered[0] is False and registered[2] is None:
+                    self.step_key.set_result(None)
+                elif registered[0] is False and registered[2]:
+                    self.view.display_info(self.tr("""Your pubkey or UID was already found on the network.
+    Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
+                else:
+                    self.view.display_info("Your account already exists on the network")
+            else:
+                self.view.display_info(self.tr("A connection already exists using this key."))
+        except NoPeerAvailable:
+            self.view.display_info(self.tr("Could not connect. Check node peering entry"))
+
+    @asyncify
+    async def accept(self):
+        self.view.accept()
+
+    def async_exec(self):
+        future = asyncio.Future()
+        self.view.finished.connect(lambda r: future.set_result(r))
+        self.view.open()
+        return future
+
+    def exec(self):
+        return self.view.exec()
\ No newline at end of file
diff --git a/src/sakia/gui/dialogs/connection_cfg/model.py b/src/sakia/gui/dialogs/connection_cfg/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1a7c3b9b6c054bc7c6e6368a0956fce729eb8f7
--- /dev/null
+++ b/src/sakia/gui/dialogs/connection_cfg/model.py
@@ -0,0 +1,206 @@
+import aiohttp
+from PyQt5.QtCore import QObject
+from duniterpy.documents import BlockUID, BMAEndpoint, SecuredBMAEndpoint
+from duniterpy.api import bma, errors
+from duniterpy.key import SigningKey
+from sakia.data.entities import Connection, Identity, Node
+from sakia.data.connectors import NodeConnector
+from sakia.data.processors import ConnectionsProcessor, NodesProcessor, BlockchainProcessor, \
+    SourcesProcessor, CertificationsProcessor, TransactionsProcessor, DividendsProcessor
+
+
+class ConnectionConfigModel(QObject):
+    """
+    The model of AccountConfig component
+    """
+
+    def __init__(self, parent, app, connection, identities_processor, node_connector=None):
+        """
+
+        :param sakia.gui.dialogs.account_cfg.controller.AccountConfigController parent:
+        :param sakia.app.Application app: the main application
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.data.processors.IdentitiesProcessor identities_processor: the identities processor
+        :param sakia.data.connectors.NodeConnector node_connector: the node connector
+        """
+        super().__init__(parent)
+        self.app = app
+        self.connection = connection
+        self.node_connector = node_connector
+        self.identities_processor = identities_processor
+
+    async def create_connection(self, server, port, secured):
+        node_connector = await NodeConnector.from_address(None, secured, server, port,
+                                                               user_parameters=self.app.parameters)
+        currencies = self.app.db.connections_repo.get_currencies()
+        if len(currencies) > 0 and node_connector.node.currency != currencies[0]:
+            raise ValueError("""This node is running for {0} network.<br/>
+Current database is storing {1} network.""".format(node_connector.node.currency, currencies[0]))
+        self.node_connector = node_connector
+        self.connection = Connection(self.node_connector.node.currency, "", "")
+        self.node_connector.node.state = Node.ONLINE
+
+    def notification(self):
+        return self.app.parameters.notifications
+
+    def set_uid(self, uid):
+        self.connection.uid = uid
+
+    def set_scrypt_infos(self, salt, password, scrypt_params):
+        self.connection.salt = salt
+        self.connection.N = scrypt_params.N
+        self.connection.r = scrypt_params.r
+        self.connection.p = scrypt_params.p
+        self.connection.password = password
+        self.connection.pubkey = SigningKey(self.connection.salt, password, scrypt_params).pubkey
+
+    def insert_or_update_connection(self):
+        ConnectionsProcessor(self.app.db.connections_repo).commit_connection(self.connection)
+        NodesProcessor(self.app.db.nodes_repo).commit_node(self.node_connector.node)
+
+    def insert_or_update_identity(self, identity):
+        self.identities_processor.insert_or_update_identity(identity)
+
+    async def initialize_blockchain(self, log_stream):
+        """
+        Download blockchain information locally
+        :param function log_stream: a method to log data in the screen
+        :return:
+        """
+        blockchain_processor = BlockchainProcessor.instanciate(self.app)
+        await blockchain_processor.initialize_blockchain(self.node_connector.node.currency, log_stream)
+
+    async def initialize_sources(self, log_stream):
+        """
+        Download sources information locally
+        :param function log_stream: a method to log data in the screen
+        :return:
+        """
+        sources_processor = SourcesProcessor.instanciate(self.app)
+        await sources_processor.initialize_sources(self.node_connector.node.currency, self.connection.pubkey, log_stream)
+
+    async def initialize_identity(self, identity, log_stream):
+        """
+        Download identity information locally
+        :param sakia.data.entities.Identity identity: the identity to initialize
+        :param function log_stream: a method to log data in the screen
+        :return:
+        """
+        await self.identities_processor.initialize_identity(identity, log_stream)
+
+    async def initialize_certifications(self, identity, log_stream):
+        """
+        Download certifications information locally
+        :param sakia.data.entities.Identity identity: the identity to initialize
+        :param function log_stream: a method to log data in the screen
+        :return:
+        """
+        certifications_processor = CertificationsProcessor.instanciate(self.app)
+        await certifications_processor.initialize_certifications(identity, log_stream)
+
+    async def initialize_transactions(self, identity, log_stream):
+        """
+        Download certifications information locally
+        :param sakia.data.entities.Identity identity: the identity to initialize
+        :param function log_stream: a method to log data in the screen
+        :return:
+        """
+        transactions_processor = TransactionsProcessor.instanciate(self.app)
+        return await transactions_processor.initialize_transactions(identity, log_stream)
+
+    async def initialize_dividends(self, identity, transactions, log_stream):
+        """
+        Download certifications information locally
+        :param sakia.data.entities.Identity identity: the identity to initialize
+        :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx processor
+        :param function log_stream: a method to log data in the screen
+        :return:
+        """
+        dividends_processor = DividendsProcessor.instanciate(self.app)
+        return await dividends_processor.initialize_dividends(identity, transactions, log_stream)
+
+    async def publish_selfcert(self):
+        """"
+        Publish the self certification of the connection identity
+        """
+        return await self.app.documents_service.broadcast_identity(self.connection, self.connection.password)
+
+    async def check_registered(self):
+        """
+        Checks for the pubkey and the uid of an account on a given node
+        :return: (True if found, local value, network value)
+        """
+        identity = Identity(self.connection.currency, self.connection.pubkey, self.connection.uid)
+        found_identity = Identity(self.connection.currency, self.connection.pubkey, self.connection.uid)
+
+        def _parse_uid_lookup(data):
+            timestamp = BlockUID.empty()
+            found_uid = ""
+            for result in data['results']:
+                if result["pubkey"] == identity.pubkey:
+                    uids = result['uids']
+                    for uid_data in uids:
+                        if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
+                            timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"])
+                            found_identity.blockstamp = timestamp
+                            found_uid = uid_data["uid"]
+                            found_identity.signature = uid_data["self"]
+            return identity.uid == found_uid, identity.uid, found_uid
+
+        def _parse_pubkey_lookup(data):
+            timestamp = BlockUID.empty()
+            found_uid = ""
+            found_result = ["", ""]
+            for result in data['results']:
+                uids = result['uids']
+                for uid_data in uids:
+                    if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
+                        timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"])
+                        found_identity.blockstamp = timestamp
+                        found_uid = uid_data["uid"]
+                        found_identity.signature = uid_data["self"]
+                if found_uid == identity.uid:
+                    found_result = result['pubkey'], found_uid
+            if found_result[1] == identity.uid:
+                return identity.pubkey == found_result[0], identity.pubkey, found_result[0]
+            else:
+                return False, identity.pubkey, None
+
+        async def execute_requests(parser, search):
+            tries = 0
+            nonlocal registered
+            for endpoint in [e for e in self.node_connector.node.endpoints
+                             if isinstance(e, BMAEndpoint) or isinstance(e, SecuredBMAEndpoint)]:
+                if not registered[0] and not registered[2]:
+                    try:
+                        data = await self.node_connector.safe_request(endpoint, bma.wot.lookup,
+                                                                      req_args={'search': search},
+                                                                      proxy=self.app.parameters.proxy())
+                        if data:
+                            registered = parser(data)
+                        tries += 1
+                    except errors.DuniterError as e:
+                        if e.ucode in (errors.NO_MEMBER_MATCHING_PUB_OR_UID, errors.NO_MATCHING_IDENTITY):
+                                tries += 1
+                        else:
+                            raise
+                else:
+                    break
+
+        # cell 0 contains True if the user is already registered
+        # cell 1 contains the uid/pubkey selected locally
+        # cell 2 contains the uid/pubkey found on the network
+        registered = (False, identity.uid, None)
+        # We execute search based on pubkey
+        # And look for account UID
+        await execute_requests(_parse_uid_lookup, identity.pubkey)
+
+        # If the uid wasn't found when looking for the pubkey
+        # We look for the uid and check for the pubkey
+        if not registered[0] and not registered[2]:
+            await execute_requests(_parse_pubkey_lookup, identity.uid)
+
+        return registered, found_identity
+
+    def key_exists(self):
+        return self.connection.pubkey in ConnectionsProcessor.instanciate(self.app).pubkeys()
diff --git a/src/sakia/gui/dialogs/connection_cfg/view.py b/src/sakia/gui/dialogs/connection_cfg/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..45824214fcecfea1c04376f6a12f7ccac2e4fe09
--- /dev/null
+++ b/src/sakia/gui/dialogs/connection_cfg/view.py
@@ -0,0 +1,146 @@
+from PyQt5.QtWidgets import QDialog
+from PyQt5.QtCore import pyqtSignal, Qt
+from .connection_cfg_uic import Ui_ConnectionConfigurationDialog
+from duniterpy.key import SigningKey, ScryptParams
+from math import ceil, log
+from ...widgets import toast
+from ...widgets.dialogs import QAsyncMessageBox
+
+
+class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog):
+    """
+    Connection config view
+    """
+    values_changed = pyqtSignal()
+
+    def __init__(self, parent):
+        """
+        Constructor
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        self.edit_uid.textChanged.connect(self.values_changed)
+        self.edit_password.textChanged.connect(self.values_changed)
+        self.edit_password_repeat.textChanged.connect(self.values_changed)
+        self.edit_salt.textChanged.connect(self.values_changed)
+        self.button_generate.clicked.connect(self.action_show_pubkey)
+
+        self.combo_scrypt_params.currentIndexChanged.connect(self.handle_combo_change)
+        self.scrypt_params = ScryptParams(4096, 16, 1)
+        self.spin_n.setMaximum(2 ** 20)
+        self.spin_n.setValue(self.scrypt_params.N)
+        self.spin_n.valueChanged.connect(self.handle_n_change)
+        self.spin_r.setMaximum(128)
+        self.spin_r.setValue(self.scrypt_params.r)
+        self.spin_r.valueChanged.connect(self.handle_r_change)
+        self.spin_p.setMaximum(128)
+        self.spin_p.setValue(self.scrypt_params.p)
+        self.spin_p.valueChanged.connect(self.handle_p_change)
+        self.label_info.setTextFormat(Qt.RichText)
+
+    def handle_combo_change(self, index):
+        strengths = [
+            (2 ** 12, 16, 1),
+            (2 ** 14, 32, 2),
+            (2 ** 16, 32, 4),
+            (2 ** 18, 64, 8),
+        ]
+        self.spin_n.setValue(strengths[index][0])
+        self.spin_r.setValue(strengths[index][1])
+        self.spin_p.setValue(strengths[index][2])
+
+    def handle_n_change(self, value):
+        spinbox = self.sender()
+        self.scrypt_params.N = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.N)
+
+    def handle_r_change(self, value):
+        spinbox = self.sender()
+        self.scrypt_params.r = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.r)
+
+    def handle_p_change(self, value):
+        spinbox = self.sender()
+        self.scrypt_params.p = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.p)
+
+    @staticmethod
+    def compute_power_of_2(spinbox, value, param):
+        if value > 1:
+            if value > param:
+                value = pow(2, ceil(log(value) / log(2)))
+            else:
+                value -= 1
+                value = 2 ** int(log(value, 2))
+        else:
+            value = 1
+
+        spinbox.blockSignals(True)
+        spinbox.setValue(value)
+        spinbox.blockSignals(False)
+
+        return value
+
+    def display_info(self, info):
+        self.label_info.setText(info)
+
+    def set_currency(self, currency):
+        self.label_currency.setText(currency)
+
+    def add_node_parameters(self):
+        server = self.lineedit_add_address.text()
+        port = self.spinbox_add_port.value()
+        return server, port
+
+    async def show_success(self, notification):
+        if notification:
+            toast.display(self.tr("UID broadcast"), self.tr("Identity broadcasted to the network"))
+        else:
+            await QAsyncMessageBox.information(self, self.tr("UID broadcast"),
+                                               self.tr("Identity broadcasted to the network"))
+
+    def show_error(self, notification, error_txt):
+        if notification:
+            toast.display(self.tr("UID broadcast"), error_txt)
+        self.label_info.setText(self.tr("Error") + " " + error_txt)
+
+    def set_nodes_model(self, model):
+        self.tree_peers.setModel(model)
+
+    def set_creation_layout(self):
+        """
+        Hide unecessary buttons and display correct title
+        """
+        self.setWindowTitle(self.tr("New account"))
+        self.button_delete.hide()
+
+    def set_modification_layout(self, account_name):
+        """
+        Hide unecessary widgets for account modification
+        and display correct title
+        :return:
+        """
+        self.label_action.setText("Edit account uid")
+        self.edit_account_name.setPlaceholderText(account_name)
+        self.button_next.setEnabled(True)
+        self.setWindowTitle(self.tr("Configure {0}".format(account_name)))
+
+    def action_show_pubkey(self):
+        salt = self.edit_salt.text()
+        password = self.edit_password.text()
+        pubkey = SigningKey(salt, password, self.scrypt_params).pubkey
+        self.label_info.setText(pubkey)
+
+    def account_name(self):
+        return self.edit_account_name.text()
+
+    def set_communities_list_model(self, model):
+        """
+        Set communities list model
+        :param sakia.models.communities.CommunitiesListModel model:
+        """
+        self.list_communities.setModel(model)
+
+    def stream_log(self, log):
+        """
+        Add log to
+        :param str log:
+        """
+        self.plain_text_edit.insertPlainText("\n" + log)
diff --git a/src/sakia/gui/graphs/__init__.py b/src/sakia/gui/dialogs/revocation/__init__.py
similarity index 100%
rename from src/sakia/gui/graphs/__init__.py
rename to src/sakia/gui/dialogs/revocation/__init__.py
diff --git a/src/sakia/gui/dialogs/revocation/controller.py b/src/sakia/gui/dialogs/revocation/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..23d5c3de4047505a58d0f3be174c755558ad080a
--- /dev/null
+++ b/src/sakia/gui/dialogs/revocation/controller.py
@@ -0,0 +1,132 @@
+import asyncio
+
+from PyQt5.QtCore import QObject
+from duniterpy.documents import MalformedDocumentError
+from sakia.decorators import asyncify
+from .model import RevocationModel
+from .view import RevocationView
+
+
+class RevocationController(QObject):
+    """
+    The revocation view
+    """
+
+    def __init__(self, view, model):
+        """
+        Constructor of the revocation component
+
+        :param sakia.gui.dialogs.revocation.view.revocationView: the view
+        :param sakia.gui.dialogs.revocation.model.revocationModel model: the model
+        """
+        super().__init__()
+        self.view = view
+        self.model = model
+
+        self.view.button_next.clicked.connect(lambda checked: self.handle_next_step(False))
+        self.view.button_load.clicked.connect(self.load_from_file)
+        self.view.spinbox_port.valueChanged.connect(self.refresh_revocation_info)
+        self.view.edit_address.textChanged.connect(self.refresh_revocation_info)
+        self.view.radio_address.toggled.connect(self.refresh_revocation_info)
+        self.view.radio_currency.toggled.connect(self.refresh_revocation_info)
+
+        self._steps = (
+            {
+                'page': self.view.page_load_file,
+                'init': lambda: None,
+                'next': lambda: None
+            },
+            {
+                'page': self.view.page_destination,
+                'init': self.init_publication_page,
+                'next': self.publish
+            }
+        )
+        self._current_step = 0
+
+    @classmethod
+    def create(cls, parent, app, connection):
+        """
+        Instanciate a revocation component
+        :param sakia.app.Application app:
+        :return: a new revocation controller
+        :rtype: revocationController
+        """
+        view = RevocationView(parent.view)
+        model = RevocationModel(app, connection)
+        revocation = cls(view, model)
+        return revocation
+
+    @classmethod
+    def open_dialog(cls, parent, app, connection):
+        """
+        Certify and identity
+        :param sakia.gui.component.controller.ComponentController parent: the parent
+        :param sakia.app.Application app: the application
+        :param sakia.data.entities.Connection connection: the connection certifying the identity
+        :return:
+        """
+        dialog = cls.create(parent, app, connection=connection)
+        dialog.handle_next_step(init=True)
+        return dialog.exec()
+
+    def handle_next_step(self, init=False):
+        if self._current_step < len(self._steps) - 1:
+            if not init:
+                self.view.button_next.clicked.disconnect(self._steps[self._current_step]['next'])
+                self._current_step += 1
+            self._steps[self._current_step]['init']()
+            self.view.stackedWidget.setCurrentWidget(self._steps[self._current_step]['page'])
+            self.view.button_next.clicked.connect(self._steps[self._current_step]['next'])
+
+    def load_from_file(self):
+        selected_file = self.view.select_revocation_file()
+        try:
+            self.model.load_revocation(selected_file)
+            self.view.show_revoked_selfcert(self.model.revoked_identity)
+            self.view.button_next.setEnabled(True)
+        except FileNotFoundError:
+            pass
+        except MalformedDocumentError:
+            self.view.malformed_file_error()
+            self.button_next.setEnabled(False)
+
+    def refresh_revocation_info(self):
+        self.view.refresh_revocation_label(self.model.revoked_identity)
+
+    def init_publication_page(self):
+        communities_names = self.model.currencies_names()
+        self.view.set_currencies_names(communities_names)
+
+    def publish(self):
+        self.view.button_next.setEnabled(False)
+        if self.view.ask_for_confirmation():
+            self.accept()
+        else:
+            self.view.button_next.setEnabled(True)
+
+    @asyncify
+    async def accept(self):
+        if self.view.radio_currency.isChecked():
+            index = self.view.combo_currency.currentIndex()
+            result, error = await self.model.broadcast_to_network(index)
+        else:
+            server = self.view.edit_address.text()
+            port = self.view.spinbox_port.value()
+            secured = self.view.checkbox_secured.isChecked()
+            result, error = await self.model.send_to_node(server, port, secured)
+
+        if result:
+            self.view.accept()
+        else:
+            await self.view.revocation_broadcast_error(error)
+
+    def async_exec(self):
+        future = asyncio.Future()
+        self.view.finished.connect(lambda r: future.set_result(r))
+        self.view.open()
+        self.refresh()
+        return future
+
+    def exec(self):
+        self.view.exec()
diff --git a/src/sakia/gui/dialogs/revocation/model.py b/src/sakia/gui/dialogs/revocation/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..519c63220506eecc85ee8f476f5fee82bc71cbe1
--- /dev/null
+++ b/src/sakia/gui/dialogs/revocation/model.py
@@ -0,0 +1,64 @@
+from duniterpy.documents import Revocation, BMAEndpoint, SecuredBMAEndpoint
+from duniterpy.api import bma, errors
+from sakia.data.connectors import NodeConnector
+from asyncio import TimeoutError
+from socket import gaierror
+import jsonschema
+from aiohttp.errors import ClientError, DisconnectedError
+from aiohttp.errors import ClientResponseError
+from PyQt5.QtCore import QObject
+import aiohttp
+
+
+class RevocationModel(QObject):
+    """
+    The model of HomeScreen component
+    """
+
+    def __init__(self, app, connection):
+        super().__init__()
+        self.app = app
+        self.connection = connection
+
+        self.revocation_document = None
+        self.revoked_identity = None
+
+    def load_revocation(self, path):
+        """
+        Load a revocation document from a file
+        :param str path:
+        """
+        with open(path, 'r') as file:
+            file_content = file.read()
+            self.revocation_document = Revocation.from_signed_raw(file_content)
+            self.revoked_identity = Revocation.extract_self_cert(file_content)
+
+    def currencies_names(self):
+        return [c for c in self.app.db.connections_repo.get_currencies()]
+
+    async def broadcast_to_network(self, index):
+        currency = self.currencies_names()[index]
+        return await self.app.documents_service.broadcast_revocation(currency, self.revoked_identity,
+                                                               self.revocation_document)
+
+    async def send_to_node(self, server, port, secured):
+        signed_raw = self.revocation_document.signed_raw(self.revoked_identity)
+        node_connector = await NodeConnector.from_address(None, secured, server, port, self.app.parameters)
+        for endpoint in [e for e in node_connector.node.endpoints
+                         if isinstance(e, BMAEndpoint) or isinstance(e, SecuredBMAEndpoint)]:
+            try:
+                self._logger.debug("Broadcasting : \n" + signed_raw)
+                conn_handler = endpoint.conn_handler(node_connector.session, proxy=self.app.parameters.proxy())
+                result = await bma.wot.revoke(conn_handler, signed_raw)
+                if result.status == 200:
+                    return True, ""
+                else:
+                    return False, bma.api.parse_error(await result.text())["message"]
+            except errors.DuniterError as e:
+                return False, e.message
+            except (jsonschema.ValidationError, ClientError, gaierror,
+                    TimeoutError, ConnectionRefusedError, DisconnectedError, ValueError) as e:
+                return False, str(e)
+            finally:
+                node_connector.session.close()
+        return True, ""
diff --git a/res/ui/revocation.ui b/src/sakia/gui/dialogs/revocation/revocation.ui
similarity index 92%
rename from res/ui/revocation.ui
rename to src/sakia/gui/dialogs/revocation/revocation.ui
index d7edc4990e334b82dbe71405c249084bd05ad234..7688bd3f38c2b87a16a3a63b9b88f124f37ecfbc 100644
--- a/res/ui/revocation.ui
+++ b/src/sakia/gui/dialogs/revocation/revocation.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
-    <height>250</height>
+    <width>452</width>
+    <height>358</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -103,14 +103,14 @@ QGroupBox::title {
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
-          <widget class="QRadioButton" name="radio_community">
+          <widget class="QRadioButton" name="radio_currency">
            <property name="text">
             <string>To a co&amp;mmunity</string>
            </property>
           </widget>
          </item>
          <item>
-          <widget class="QComboBox" name="combo_community"/>
+          <widget class="QComboBox" name="combo_currency"/>
          </item>
         </layout>
        </item>
@@ -139,6 +139,13 @@ QGroupBox::title {
            </property>
           </widget>
          </item>
+         <item>
+          <widget class="QCheckBox" name="checkbox_secured">
+           <property name="text">
+            <string>SSL/TLS</string>
+           </property>
+          </widget>
+         </item>
         </layout>
        </item>
        <item>
@@ -166,6 +173,13 @@ QGroupBox::title {
             </property>
            </widget>
           </item>
+          <item>
+           <widget class="QLabel" name="label_target">
+            <property name="text">
+             <string/>
+            </property>
+           </widget>
+          </item>
           <item>
            <spacer name="verticalSpacer">
             <property name="orientation">
diff --git a/src/sakia/gui/dialogs/revocation/view.py b/src/sakia/gui/dialogs/revocation/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..4483a3d8c07c6b7770e2ba9a240b6803a38930c0
--- /dev/null
+++ b/src/sakia/gui/dialogs/revocation/view.py
@@ -0,0 +1,130 @@
+from enum import Enum
+
+from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox
+
+from sakia.decorators import asyncify
+from sakia.gui.widgets.dialogs import QAsyncMessageBox
+from .revocation_uic import Ui_RevocationDialog
+
+
+class RevocationView(QDialog, Ui_RevocationDialog):
+    """
+    Home screen view
+    """
+
+    class PublicationMode(Enum):
+        ADDRESS = 0
+        COMMUNITY = 1
+
+    def __init__(self, parent):
+        """
+        Constructor
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.button_next.setEnabled(False)
+
+        self.radio_address.toggled.connect(lambda c: self.publication_mode_changed(RevocationView.PublicationMode.ADDRESS))
+        self.radio_currency.toggled.connect(lambda c: self.publication_mode_changed(RevocationView.PublicationMode.COMMUNITY))
+
+    def publication_mode_changed(self, radio):
+        self.edit_address.setEnabled(radio == RevocationView.PublicationMode.ADDRESS)
+        self.spinbox_port.setEnabled(radio == RevocationView.PublicationMode.ADDRESS)
+        self.combo_currency.setEnabled(radio == RevocationView.PublicationMode.COMMUNITY)
+
+    def refresh_target(self):
+        if self.radio_currency.isChecked():
+            target = self.tr(
+                "All nodes of currency {name}".format(name=self.combo_currency.currentText()))
+        elif self.radio_address.isChecked():
+            target = self.tr("Address {address}:{port}".format(address=self.edit_address.text(),
+                                                               port=self.spinbox_port.value()))
+        else:
+            target = ""
+        self.label_target.setText("""
+<h4>Publication address</h4>
+<div>{target}</div>
+""".format(target=target))
+
+    def refresh_revocation_label(self, revoked_identity):
+        if revoked_identity:
+            text = self.tr("""
+<div>Identity revoked : {uid} (public key : {pubkey}...)</div>
+<div>Identity signed on block : {timestamp}</div>
+    """.format(uid=revoked_identity.uid,
+               pubkey=revoked_identity.pubkey[:12],
+               timestamp=revoked_identity.timestamp))
+
+            self.label_revocation_content.setText(text)
+
+            if self.radio_currency.isChecked():
+                target = self.tr("All nodes of currency {name}".format(name=self.combo_currency.currentText()))
+            elif self.radio_address.isChecked():
+                target = self.tr("Address {address}:{port}".format(address=self.edit_address.text(),
+                                                                   port=self.spinbox_port.value()))
+            else:
+                target = ""
+            self.label_revocation_info.setText("""
+<h4>Revocation document</h4>
+<div>{text}</div>
+<h4>Publication address</h4>
+<div>{target}</div>
+""".format(text=text,
+           target=target))
+        else:
+            self.label_revocation_content.setText("")
+
+    def select_revocation_file(self):
+        """
+        Get a revocation file using a file dialog
+        :rtype: str
+        """
+        selected_files = QFileDialog.getOpenFileName(self,
+                                    self.tr("Load a revocation file"),
+                                    "",
+                                    self.tr("All text files (*.txt)"))
+        selected_file = selected_files[0]
+        return selected_file
+
+    def malformed_file_error(self):
+        QMessageBox.critical(self, self.tr("Error loading document"),
+                             self.tr("Loaded document is not a revocation document"),
+                             buttons=QMessageBox.Ok)
+
+    async def revocation_broadcast_error(self, error):
+        await QAsyncMessageBox.critical(self, self.tr("Error broadcasting document"),
+                                        error)
+
+    def show_revoked_selfcert(self, selfcert):
+        text = self.tr("""
+        <div>Identity revoked : {uid} (public key : {pubkey}...)</div>
+        <div>Identity signed on block : {timestamp}</div>
+            """.format(uid=selfcert.uid,
+                       pubkey=selfcert.pubkey[:12],
+                       timestamp=selfcert.timestamp))
+        self.label_revocation_content.setText(text)
+
+    def set_currencies_names(self, names):
+        self.combo_currency.clear()
+        for name in names:
+            self.combo_currency.addItem(name)
+        self.radio_currency.setChecked(True)
+
+    def ask_for_confirmation(self):
+        answer = QMessageBox.warning(self, self.tr("Revocation"),
+                                     self.tr("""<h4>The publication of this document will remove your identity from the network.</h4>
+        <li>
+            <li> <b>This identity won't be able to join the targeted currency anymore.</b> </li>
+            <li> <b>This identity won't be able to generate Universal Dividends anymore.</b> </li>
+            <li> <b>This identity won't be able to certify individuals anymore.</b> </li>
+        </li>
+        Please think twice before publishing this document.
+        """), QMessageBox.Ok | QMessageBox.Cancel)
+        return answer == QMessageBox.Ok
+
+    @asyncify
+    async def accept(self):
+        await QAsyncMessageBox.information(self, self.tr("Revocation broadcast"),
+                                     self.tr("The document was successfully broadcasted."))
+        super().accept()
diff --git a/src/sakia/tests/functional/__init__.py b/src/sakia/gui/dialogs/transfer/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/__init__.py
rename to src/sakia/gui/dialogs/transfer/__init__.py
diff --git a/src/sakia/gui/dialogs/transfer/controller.py b/src/sakia/gui/dialogs/transfer/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..e50ffbcf3f0299dc5317ae7be4dae13b717d0b86
--- /dev/null
+++ b/src/sakia/gui/dialogs/transfer/controller.py
@@ -0,0 +1,221 @@
+import asyncio
+import logging
+
+from PyQt5.QtCore import Qt, QObject
+from PyQt5.QtWidgets import QApplication
+
+from sakia.data.processors import ConnectionsProcessor
+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
+from .view import TransferView
+
+
+class TransferController(QObject):
+    """
+    The transfer component controller
+    """
+
+    def __init__(self, view, model, search_user, user_information):
+        """
+        Constructor of the transfer component
+
+        :param sakia.gui.dialogs.transfer.view.TransferView: the view
+        :param sakia.gui.dialogs.transfer.model.TransferModel model: the model
+        """
+        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_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):
+        """
+        Instanciate a transfer component
+        :param sakia.gui.component.controller.ComponentController parent:
+        :param sakia.core.Application app:
+        :return: a new Transfer controller
+        :rtype: TransferController
+        """
+        view = TransferView(parent.view if parent else None, None, None)
+        model = TransferModel(app)
+        transfer = cls(view, model, None, None)
+
+        search_user = SearchUserController.create(transfer, app, "")
+        transfer.set_search_user(search_user)
+
+        user_information = UserInformationController.create(transfer, app, "", None)
+        transfer.set_user_information(user_information)
+
+        search_user.identity_selected.connect(user_information.search_identity)
+
+        view.set_keys(transfer.model.available_connections())
+        return transfer
+
+    @classmethod
+    def open_dialog(cls, parent, app, connection):
+        dialog = cls.create(parent, app)
+        if connection:
+            dialog.view.combo_connections.setCurrentText(connection.title())
+        dialog.refresh()
+        return dialog.exec()
+
+    @classmethod
+    async def send_money_to_identity(cls, parent, app, connection, identity):
+        dialog = cls.create(parent, app)
+        dialog.view.combo_connections.setCurrentText(connection.title())
+        dialog.user_information.change_identity(identity)
+        dialog.view.edit_pubkey.setText(identity.pubkey)
+        dialog.view.radio_pubkey.setChecked(True)
+
+        dialog.refresh()
+        return await dialog.async_exec()
+
+    @classmethod
+    def send_transfer_again(cls, parent, app, connection, resent_transfer):
+        dialog = cls.create(parent, app)
+        dialog.view.combo_connections.setCurrentText(connection.title())
+        dialog.view.edit_pubkey.setText(resent_transfer.receiver)
+        dialog.view.radio_pubkey.setChecked(True)
+
+        dialog.refresh()
+        relative = dialog.model.quant_to_rel(resent_transfer.amount)
+        dialog.view.set_spinboxes_parameters(1, resent_transfer.amount, relative)
+        dialog.view.change_relative_amount(relative)
+        dialog.view.change_quantitative_amount(resent_transfer.amount)
+
+        connections_processor = ConnectionsProcessor.instanciate(app)
+        wallet_index = connections_processor.connections().index(connection)
+        dialog.view.combo_connections.setCurrentIndex(wallet_index)
+        dialog.view.edit_pubkey.setText(resent_transfer.receiver)
+        dialog.view.radio_pubkey.setChecked(True)
+        dialog.view.edit_message.setText(resent_transfer.comment)
+
+        return dialog.exec()
+
+    def set_search_user(self, search_user):
+        """
+
+        :param search_user:
+        :return:
+        """
+        self.search_user = search_user
+        self.view.set_search_user(search_user.view)
+
+    def set_user_information(self, user_information):
+        """
+
+        :param user_information:
+        :return:
+        """
+        self.user_information = user_information
+        self.view.set_user_information(user_information.view)
+
+    def selected_pubkey(self):
+        """
+        Get selected pubkey in the widgets of the window
+        :return: the current pubkey
+        :rtype: str
+        """
+        pubkey = None
+
+        if self.view.recipient_mode() == TransferView.RecipientMode.SEARCH:
+            if self.search_user.current_identity():
+                pubkey = self.search_user.current_identity().pubkey
+        else:
+            pubkey = self.view.pubkey_value()
+        return pubkey
+
+    @asyncify
+    async def accept(self):
+        logging.debug("Accept transfer action...")
+        self.view.button_box.setEnabled(False)
+        comment = self.view.edit_message.text()
+
+        logging.debug("checking recipient mode...")
+        recipient = self.selected_pubkey()
+        amount = self.view.spinbox_amount.value() * 100
+        #TODO: Handle other amount base than 0
+        amount_base = 0
+
+        logging.debug("Showing password dialog...")
+        password = await PasswordAskerDialog(self.model.connection).async_exec()
+        if password == "":
+            self.view.button_box.setEnabled(True)
+            return
+
+        logging.debug("Setting cursor...")
+        QApplication.setOverrideCursor(Qt.WaitCursor)
+
+        logging.debug("Send money...")
+        result, transaction = await self.model.send_money(recipient, password, amount, amount_base, comment)
+        if result[0]:
+            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.new_transfer.emit(transaction)
+            self.view.accept()
+        else:
+            await self.view.show_error(self.model.notifications(), result[1])
+            self.model.app.new_transfer.emit(transaction)
+
+            QApplication.restoreOverrideCursor()
+            self.view.button_box.setEnabled(True)
+
+    def reject(self):
+        self.view.reject()
+
+    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 = self.model.quant_to_rel(amount/100)
+        current_base = self.model.current_base()
+
+        self.view.set_spinboxes_parameters(pow(10, current_base), amount, max_relative)
+
+    def handle_amount_change(self, value):
+        relative = self.model.quant_to_rel(value)
+        self.view.change_relative_amount(relative)
+        self.refresh_amount_suffix()
+
+    def refresh_amount_suffix(self):
+        self.view.spinbox_amount.setSuffix(" " + 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_connection(self, 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):
+        future = asyncio.Future()
+        self.view.finished.connect(lambda r: future.set_result(r))
+        self.view.open()
+        self.refresh()
+        return future
+
+    def exec(self):
+        self.refresh()
+        self.view.exec()
\ No newline at end of file
diff --git a/src/sakia/gui/dialogs/transfer/model.py b/src/sakia/gui/dialogs/transfer/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d07b33d80273f61e2fe62d38103c6a7fd5f4987
--- /dev/null
+++ b/src/sakia/gui/dialogs/transfer/model.py
@@ -0,0 +1,100 @@
+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:
+    """
+
+    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 __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)
+
+    def rel_to_quant(self, rel_value):
+        """
+        Get the quantitative value of a relative amount
+        :param float rel_value:
+        :rtype: int
+        """
+        dividend, base = 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 / 100
+
+    def quant_to_rel(self, amount):
+        """
+        Get the relative value of a given amount
+        :param int amount:
+        :rtype: float
+        """
+        dividend, base = self._blockchain_processor.last_ud(self.connection.currency)
+        relative = amount * 100 / (dividend * pow(10, base))
+        return relative
+
+    def wallet_value(self):
+        """
+        Get the value of the current wallet in the current community
+        """
+        return self._sources_processor.amount(self.connection.currency, self.connection.pubkey)
+
+    def current_base(self):
+        """
+        Get the current base of the network
+        """
+        dividend, base = self._blockchain_processor.last_ud(self.connection.currency)
+        return base
+
+    def localized_amount(self, amount):
+        """
+        Get the value of the current referential
+        """
+
+        localized = self.app.current_ref.instance(amount, self.connection.currency, self.app).diff_localized(True, True)
+        return localized
+
+    def cancel_previous(self):
+        if self.resent_transfer:
+            self.resent_transfer.cancel()
+
+    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
+        """
+
+        result = await self.app.documents_service.send_money(self.connection, password,
+                                                           recipient, amount, amount_base, comment)
+        self.app.db.commit()
+        return result
+
+    def notifications(self):
+        return self.app.parameters.notifications
diff --git a/res/ui/certification.ui b/src/sakia/gui/dialogs/transfer/transfer.ui
similarity index 55%
rename from res/ui/certification.ui
rename to src/sakia/gui/dialogs/transfer/transfer.ui
index 59f527b542ae9c55862bc1c29a8ae8438b2aa1c7..5b9a2593595ea6daca3a870e3a868b4efc3c158a 100644
--- a/res/ui/certification.ui
+++ b/src/sakia/gui/dialogs/transfer/transfer.ui
@@ -1,119 +1,57 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>CertificationDialog</class>
- <widget class="QDialog" name="CertificationDialog">
+ <class>TransferMoneyDialog</class>
+ <widget class="QDialog" name="TransferMoneyDialog">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>715</width>
-    <height>477</height>
+    <width>566</width>
+    <height>415</height>
    </rect>
   </property>
   <property name="windowTitle">
-   <string>Certification</string>
+   <string>Transfer money</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QGroupBox" name="groupBox_2">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
      <property name="title">
-      <string>Community</string>
+      <string>Select connection</string>
      </property>
-     <layout class="QVBoxLayout" name="verticalLayout_2">
+     <layout class="QHBoxLayout" name="horizontalLayout_4">
       <item>
-       <widget class="QComboBox" name="combo_community"/>
-      </item>
-      <item>
-       <widget class="QGroupBox" name="groupBox_3">
-        <property name="title">
-         <string>Certifications stock</string>
-        </property>
-        <layout class="QVBoxLayout" name="verticalLayout_4">
-         <item>
-          <widget class="QLabel" name="label_cert_stock">
-           <property name="text">
-            <string/>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </widget>
+       <widget class="QComboBox" name="combo_connections"/>
       </item>
      </layout>
     </widget>
    </item>
    <item>
-    <widget class="QGroupBox" name="groupBox">
+    <widget class="QGroupBox" name="group_box_recipient">
      <property name="title">
-      <string>Certify user</string>
+      <string>Transfer money to</string>
      </property>
      <layout class="QHBoxLayout" name="horizontalLayout_5">
       <item>
-       <layout class="QVBoxLayout" name="verticalLayout_3">
+       <layout class="QVBoxLayout" name="verticalLayout_4">
         <property name="topMargin">
          <number>6</number>
         </property>
+        <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">
-            <property name="orientation">
-             <enum>Qt::Horizontal</enum>
-            </property>
-            <property name="sizeType">
-             <enum>QSizePolicy::Maximum</enum>
-            </property>
-            <property name="sizeHint" stdset="0">
-             <size>
-              <width>20</width>
-              <height>20</height>
-             </size>
-            </property>
-           </spacer>
-          </item>
+         <layout class="QHBoxLayout" name="horizontalLayout">
           <item>
-           <widget class="QComboBox" name="combo_contact">
-            <property name="enabled">
-             <bool>true</bool>
-            </property>
+           <widget class="QRadioButton" name="radio_pubkey">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
-           </widget>
-          </item>
-         </layout>
-        </item>
-        <item>
-         <layout class="QHBoxLayout" name="horizontalLayout">
-          <item>
-           <widget class="QRadioButton" name="radio_pubkey">
             <property name="text">
-             <string>&amp;User public key</string>
+             <string>&amp;Recipient public key</string>
             </property>
             <property name="checked">
              <bool>false</bool>
@@ -142,7 +80,7 @@
              <bool>false</bool>
             </property>
             <property name="sizePolicy">
-             <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+             <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
@@ -161,25 +99,25 @@
          </layout>
         </item>
         <item>
-         <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <layout class="QHBoxLayout" name="layout_search_user">
           <property name="topMargin">
            <number>6</number>
           </property>
           <item>
            <widget class="QRadioButton" name="radio_search">
             <property name="sizePolicy">
-             <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+             <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
-             <string>Sea&amp;rch user</string>
+             <string>Search &amp;user</string>
             </property>
            </widget>
           </item>
           <item>
-           <spacer name="horizontalSpacer_3">
+           <spacer name="horizontalSpacer">
             <property name="orientation">
              <enum>Qt::Horizontal</enum>
             </property>
@@ -194,19 +132,6 @@
             </property>
            </spacer>
           </item>
-          <item>
-           <widget class="SearchUserWidget" name="search_user" native="true">
-            <property name="enabled">
-             <bool>false</bool>
-            </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-           </widget>
-          </item>
          </layout>
         </item>
        </layout>
@@ -214,6 +139,96 @@
      </layout>
     </widget>
    </item>
+   <item>
+    <widget class="QFrame" name="frame">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_8">
+      <item>
+       <widget class="QLabel" name="label_total">
+        <property name="text">
+         <string>Available money : </string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_3">
+        <item>
+         <widget class="QLabel" name="label_3">
+          <property name="text">
+           <string>Amount</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QDoubleSpinBox" name="spinbox_relative">
+          <property name="suffix">
+           <string> UD</string>
+          </property>
+          <property name="decimals">
+           <number>6</number>
+          </property>
+          <property name="maximum">
+           <double>99999999999999991611392.000000000000000</double>
+          </property>
+          <property name="singleStep">
+           <double>0.010000000000000</double>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QDoubleSpinBox" name="spinbox_amount">
+          <property name="wrapping">
+           <bool>false</bool>
+          </property>
+          <property name="frame">
+           <bool>true</bool>
+          </property>
+          <property name="readOnly">
+           <bool>false</bool>
+          </property>
+          <property name="buttonSymbols">
+           <enum>QAbstractSpinBox::UpDownArrows</enum>
+          </property>
+          <property name="showGroupSeparator" stdset="0">
+           <bool>false</bool>
+          </property>
+          <property name="decimals">
+           <number>2</number>
+          </property>
+          <property name="singleStep">
+           <double>1.000000000000000</double>
+          </property>
+          <property name="value">
+           <double>0.000000000000000</double>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_3">
+     <property name="title">
+      <string>Transaction message</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3">
+      <item>
+       <widget class="QLineEdit" name="edit_message">
+        <property name="inputMask">
+         <string/>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
    <item>
     <widget class="QDialogButtonBox" name="button_box">
      <property name="orientation">
@@ -226,14 +241,6 @@
    </item>
   </layout>
  </widget>
- <customwidgets>
-  <customwidget>
-   <class>SearchUserWidget</class>
-   <extends>QWidget</extends>
-   <header>sakia.gui.widgets.search_user</header>
-   <container>1</container>
-  </customwidget>
- </customwidgets>
  <resources/>
  <connections/>
  <slots>
diff --git a/src/sakia/gui/dialogs/transfer/view.py b/src/sakia/gui/dialogs/transfer/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..458215e38fc15adce2eb263965da38ccf245b5a1
--- /dev/null
+++ b/src/sakia/gui/dialogs/transfer/view.py
@@ -0,0 +1,146 @@
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox
+from PyQt5.QtGui import QRegExpValidator
+from PyQt5.QtCore import QT_TRANSLATE_NOOP, QRegExp
+from .transfer_uic import Ui_TransferMoneyDialog
+from enum import Enum
+from sakia.gui.widgets import toast
+from sakia.gui.widgets.dialogs import QAsyncMessageBox
+
+
+class TransferView(QDialog, Ui_TransferMoneyDialog):
+    """
+    Transfer component view
+    """
+
+    class ButtonBoxState(Enum):
+        NO_AMOUNT = 0
+        OK = 1
+
+    class RecipientMode(Enum):
+        PUBKEY = 1
+        SEARCH = 2
+
+    _button_box_values = {
+        ButtonBoxState.NO_AMOUNT: (False,
+                                   QT_TRANSLATE_NOOP("TransferView", "No amount. Please give the transfer amount")),
+        ButtonBoxState.OK: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok"))
+    }
+
+    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:
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        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))
+
+        regexp = QRegExp('^([ a-zA-Z0-9-_:/;*?\[\]\(\)\\\?!^+=@&~#{}|<>%.]{0,255})$')
+        validator = QRegExpValidator(regexp)
+        self.edit_message.setValidator(validator)
+
+        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_search.isChecked():
+            return TransferView.RecipientMode.SEARCH
+        else:
+            return TransferView.RecipientMode.PUBKEY
+
+    def set_keys(self, connections):
+        for conn in connections:
+            self.combo_connections.addItem(conn.title())
+
+    def set_search_user(self, search_user_view):
+        """
+
+        :param sakia.gui.search_user.view.SearchUserView search_user_view:
+        :return:
+        """
+        self.search_user = search_user_view
+        self.layout_search_user.addWidget(search_user_view)
+        self.search_user.button_reset.hide()
+
+    def set_user_information(self, user_information_view):
+        self.user_information_view = user_information_view
+        self.group_box_recipient.layout().addWidget(user_information_view)
+
+    def recipient_mode_changed(self, radio):
+        """
+        :param str radio:
+        """
+        self.edit_pubkey.setEnabled(radio == TransferView.RecipientMode.PUBKEY)
+        self.search_user.setEnabled(radio == TransferView.RecipientMode.SEARCH)
+
+    def change_quantitative_amount(self, amount):
+        """
+        Change relative amount with signals blocked
+        :param amount:
+        """
+        self.spinbox_amount.blockSignals(True)
+        self.spinbox_amount.setValue(amount)
+        self.spinbox_amount.blockSignals(False)
+
+    def change_relative_amount(self, relative):
+        """
+        Change the quantitative amount with signals blocks
+        :param relative:
+        """
+        self.spinbox_relative.blockSignals(True)
+        self.spinbox_relative.setValue(relative)
+        self.spinbox_relative.blockSignals(False)
+
+    def set_spinboxes_parameters(self, tick_quant, max_quant, max_rel):
+        """
+        Configure the spinboxes
+        It should depend on what the last UD base is
+        :param int tick_quant:
+        :param int max_quant:
+        :param float max_rel:
+        """
+        self.spinbox_amount.setMaximum(max_quant/100)
+        self.spinbox_relative.setMaximum(max_rel)
+        self.spinbox_amount.setSingleStep(tick_quant)
+
+    def refresh_labels(self, total_text):
+        """
+        Refresh displayed texts
+        :param str total_text:
+        :param str currency:
+        """
+        self.label_total.setText("{0}".format(total_text))
+
+    def set_button_box(self, state, **kwargs):
+        """
+        Set button box state
+        :param sakia.gui.transfer.view.TransferView.ButtonBoxState state: the state of te button box
+        :param dict kwargs: the values to replace from the text in the state
+        :return:
+        """
+        button_box_state = TransferView._button_box_values[state]
+        self.button_box.button(QDialogButtonBox.Ok).setEnabled(button_box_state[0])
+        self.button_box.button(QDialogButtonBox.Ok).setText(button_box_state[1].format(**kwargs))
+
+    async def show_success(self, notification, recipient):
+        if notification:
+            toast.display(self.tr("Transfer"),
+                      self.tr("Success sending money to {0}").format(recipient))
+        else:
+            await QAsyncMessageBox.information(self.widget, self.tr("Transfer"),
+                      self.tr("Success sending money to {0}").format(recipient))
+
+    async def show_error(self, notification, error_txt):
+        if notification:
+            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/graphs/explorer_tab.py b/src/sakia/gui/graphs/explorer_tab.py
deleted file mode 100644
index 78da13791d23c561ea03600e5662c2fcfcb54993..0000000000000000000000000000000000000000
--- a/src/sakia/gui/graphs/explorer_tab.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import logging
-
-from PyQt5.QtCore import QEvent, pyqtSignal
-from PyQt5.QtWidgets import QWidget
-
-from ...tools.exceptions import NoPeerAvailable
-from ...tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from ...core.graph import ExplorerGraph
-from .graph_tab import GraphTabWidget
-from ...gen_resources.explorer_tab_uic import Ui_ExplorerTabWidget
-
-
-class ExplorerTabWidget(GraphTabWidget, Ui_ExplorerTabWidget):
-
-    money_sent = pyqtSignal()
-
-    def __init__(self, app, account=None, community=None, password_asker=None,
-                 widget=QWidget, view=Ui_ExplorerTabWidget):
-        """
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.Account account: The account displayed in the widget
-        :param sakia.core.Community community: The community displayed in the widget
-        :param sakia.gui.Password_Asker: password_asker: The widget to ask for passwords
-        """
-        # construct from qtDesigner
-        super().__init__(app, account, community, password_asker, widget)
-        self.ui = view()
-        self.ui.setupUi(self.widget)
-        self.ui.search_user_widget.init(app)
-
-        self.set_scene(self.ui.graphicsView.scene())
-        self.graph = None
-        self.app = app
-        self.draw_task = None
-
-        # nodes list for menu from search
-        self.nodes = list()
-
-        # create node metadata from account
-        self._current_identity = None
-        self.ui.button_go.clicked.connect(self.go_clicked)
-        self.ui.search_user_widget.identity_selected.connect(self.draw_graph)
-        self.ui.search_user_widget.reset.connect(self.reset)
-
-    def cancel_once_tasks(self):
-        cancel_once_task(self, self.refresh_informations_frame)
-        cancel_once_task(self, self.reset)
-
-    def change_account(self, account, password_asker):
-        self.ui.search_user_widget.change_account(account)
-        self.account = account
-        self.password_asker = password_asker
-
-    def change_community(self, community):
-        self.community = community
-        if self.graph:
-            self.graph.stop_exploration()
-        self.graph = ExplorerGraph(self.app, self.community)
-        self.graph.graph_changed.connect(self.refresh)
-        self.ui.search_user_widget.change_community(community)
-        self.graph.current_identity_changed.connect(self.ui.graphicsView.scene().update_current_identity)
-        self.reset()
-
-    def go_clicked(self):
-        if self.graph:
-            self.graph.stop_exploration()
-            self.draw_graph(self._current_identity)
-
-    def draw_graph(self, identity):
-        """
-        Draw community graph centered on the identity
-
-        :param sakia.core.registry.Identity identity: Graph node identity
-        """
-        logging.debug("Draw graph - " + identity.uid)
-
-        if self.community:
-            #Connect new identity
-            if self._current_identity != identity:
-                self._current_identity = identity
-
-            self.graph.start_exploration(identity, self.ui.steps_slider.value())
-
-            # draw graph in qt scene
-            self.ui.graphicsView.scene().clear()
-            self.ui.graphicsView.scene().update_wot(self.graph.nx_graph, identity, self.ui.steps_slider.maximum())
-
-    def refresh(self):
-        """
-        Refresh graph scene to current metadata
-        """
-        if self._current_identity:
-            # draw graph in qt scene
-            self.ui.graphicsView.scene().update_wot(self.graph.nx_graph, self._current_identity, self.ui.steps_slider.maximum())
-        else:
-            self.reset()
-
-    @once_at_a_time
-    @asyncify
-    async def reset(self, checked=False):
-        """
-        Reset graph scene to wallet identity
-        """
-        if self.account and self.community:
-            try:
-                parameters = await self.community.parameters()
-                self.ui.steps_slider.setMaximum(parameters['stepMax'])
-                self.ui.steps_slider.setValue(int(0.33 * parameters['stepMax']))
-                identity = await self.account.identity(self.community)
-                self.draw_graph(identity)
-            except NoPeerAvailable:
-                logging.debug("No peer available")
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-            self.refresh()
-        return super().changeEvent(event)
diff --git a/src/sakia/gui/graphs/graph_tab.py b/src/sakia/gui/graphs/graph_tab.py
deleted file mode 100644
index d308245b821fb6a43169b3780b0204436cc7c47a..0000000000000000000000000000000000000000
--- a/src/sakia/gui/graphs/graph_tab.py
+++ /dev/null
@@ -1,182 +0,0 @@
-from PyQt5.QtWidgets import QWidget
-from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal, QObject
-from PyQt5.QtGui import QCursor
-
-from ...tools.exceptions import MembershipNotFoundError
-from ...tools.decorators import asyncify, once_at_a_time
-from ...core.registry import BlockchainState
-from ..widgets.context_menu import ContextMenu
-
-
-class GraphTabWidget(QObject):
-
-    money_sent = pyqtSignal()
-
-    def __init__(self, app, account=None, community=None, password_asker=None, widget=QWidget):
-        """
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.Account account: The account displayed in the widget
-        :param sakia.core.Community community: The community displayed in the widget
-        :param sakia.gui.Password_Asker: password_asker: The widget to ask for passwords
-        :param class widget: The class of the graph tab
-        """
-        super().__init__()
-
-        self.widget = widget()
-        self.account = account
-        self.community = community
-        self.password_asker = password_asker
-
-        self.app = app
-
-    def set_scene(self, scene):
-        """
-        Set the scene and connects the signals
-        :param sakia.gui.views.scenes.base_scene.BaseScene scene: the scene
-        :return:
-        """
-        # add scene events
-        scene.node_context_menu_requested.connect(self.node_context_menu)
-        scene.node_clicked.connect(self.handle_node_click)
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_informations_frame(self):
-        parameters = self.community.parameters
-        try:
-            identity = await self.account.identity(self.community)
-            membership = identity.membership(self.community)
-            renew_block = membership['blockNumber']
-            last_renewal = self.community.get_block(renew_block)['medianTime']
-            expiration = last_renewal + parameters['sigValidity']
-        except MembershipNotFoundError:
-            last_renewal = None
-            expiration = None
-
-        certified = await identity.unique_valid_certified_by(self.app.identities_registry, self.community)
-        certifiers = await identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
-        if last_renewal and expiration:
-            date_renewal = QLocale.toString(
-                QLocale(),
-                QDateTime.fromTime_t(last_renewal).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
-            )
-            date_expiration = QLocale.toString(
-                QLocale(),
-                QDateTime.fromTime_t(expiration).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat)
-            )
-
-            if self.account.pubkey in self.community.members_pubkeys():
-                # set infos in label
-                self.label_general.setText(
-                    self.tr("""
-                    <table cellpadding="5">
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    </table>
-                    """).format(
-                        self.account.name, self.account.pubkey,
-                        self.tr("Membership"),
-                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
-                        self.tr("Your web of trust"),
-                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
-                                                                                             len(certified))
-                    )
-                )
-            else:
-                # set infos in label
-                self.label_general.setText(
-                    self.tr("""
-                    <table cellpadding="5">
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                    </table>
-                    """).format(
-                        self.account.name, self.account.pubkey,
-                        self.tr("Not a member"),
-                        self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration),
-                        self.tr("Your web of trust"),
-                        self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
-                                                                                             len(certified))
-                    )
-                )
-        else:
-            # set infos in label
-            self.label_general.setText(
-                self.tr("""
-                <table cellpadding="5">
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                </table>
-                """).format(
-                    self.account.name, self.account.pubkey,
-                    self.tr("Not a member"),
-                    self.tr("Your web of trust"),
-                    self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers),
-                                                                                         len(certified))
-                )
-            )
-
-    @pyqtSlot(str, dict)
-    def handle_node_click(self, pubkey, metadata):
-        self.draw_graph(
-            self.app.identities_registry.from_handled_data(
-                metadata['text'],
-                pubkey,
-                None,
-                BlockchainState.VALIDATED,
-                self.community
-            )
-        )
-
-    @once_at_a_time
-    @asyncify
-    async def draw_graph(self, identity):
-        """
-        Draw community graph centered on the identity
-
-        :param sakia.core.registry.Identity identity: Graph node identity
-        """
-        pass
-
-    @once_at_a_time
-    @asyncify
-    async def reset(self, checked=False):
-        """
-        Reset graph scene to wallet identity
-        """
-        pass
-
-    def refresh(self):
-        """
-        Refresh graph scene to current metadata
-        """
-        pass
-
-    @asyncify
-    async def node_context_menu(self, pubkey):
-        """
-        Open the node context menu
-        :param str pubkey: the pubkey of the node to open
-        """
-        identity = await self.app.identities_registry.future_find(pubkey, self.community)
-        menu = ContextMenu.from_data(self.widget, self.app, self.account, self.community, self.password_asker,
-                                     (identity,))
-        menu.view_identity_in_wot.connect(self.draw_graph)
-
-        # Show the context menu.
-        menu.qmenu.popup(QCursor.pos())
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-            self.refresh()
-        return super().changeEvent(event)
diff --git a/src/sakia/gui/graphs/wot_tab.py b/src/sakia/gui/graphs/wot_tab.py
deleted file mode 100644
index 31eedfdce5b7470cc8343bc5ee1dce59ec012c56..0000000000000000000000000000000000000000
--- a/src/sakia/gui/graphs/wot_tab.py
+++ /dev/null
@@ -1,145 +0,0 @@
-import logging
-import asyncio
-
-from PyQt5.QtCore import QEvent, pyqtSignal, QT_TRANSLATE_NOOP, QObject
-from PyQt5.QtWidgets import QWidget
-from ...tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from ...core.graph import WoTGraph
-from ...gen_resources.wot_tab_uic import Ui_WotTabWidget
-from ...gui.widgets.busy import Busy
-from .graph_tab import GraphTabWidget
-
-
-class WotTabWidget(GraphTabWidget):
-
-    money_sent = pyqtSignal()
-
-    def __init__(self, app, account=None, community=None, password_asker=None, widget=QWidget, view=Ui_WotTabWidget):
-        """
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.Account account: The account displayed in the widget
-        :param sakia.core.Community community: The community displayed in the widget
-        :param sakia.gui.Password_Asker: password_asker: The widget to ask for passwords
-        :param class widget: The class of the PyQt5 widget used for this tab
-        :param class view: The class of the UI View for this tab
-        """
-        super().__init__(app, account, community, password_asker, widget)
-        # construct from qtDesigner
-        self.ui = view()
-        self.ui.setupUi(self.widget)
-
-        self.ui.search_user_widget.init(app)
-        self.widget.installEventFilter(self)
-        self.busy = Busy(self.ui.graphicsView)
-        self.busy.hide()
-
-        self.set_scene(self.ui.graphicsView.scene())
-
-        self.account = account
-        self.community = community
-        self.password_asker = password_asker
-        self.app = app
-        self.draw_task = None
-
-        self.ui.search_user_widget.identity_selected.connect(self.draw_graph)
-        self.ui.search_user_widget.reset.connect(self.reset)
-
-        # create node metadata from account
-        self._current_identity = None
-
-    def cancel_once_tasks(self):
-        cancel_once_task(self, self.draw_graph)
-        cancel_once_task(self, self.refresh_informations_frame)
-        cancel_once_task(self, self.reset)
-
-    def change_account(self, account, password_asker):
-        self.cancel_once_tasks()
-        self.ui.search_user_widget.change_account(account)
-        self.account = account
-        self.password_asker = password_asker
-
-    def change_community(self, community):
-        self.cancel_once_tasks()
-        self.ui.search_user_widget.change_community(community)
-        self._auto_refresh(community)
-        self.community = community
-        self.reset()
-
-    def _auto_refresh(self, new_community):
-        if self.community:
-            try:
-                self.community.network.new_block_mined.disconnect(self.refresh)
-            except TypeError as e:
-                if "connected" in str(e):
-                    logging.debug("new block mined not connected")
-        if self.app.preferences["auto_refresh"]:
-            if new_community:
-                new_community.network.new_block_mined.connect(self.refresh)
-            elif self.community:
-                self.community.network.new_block_mined.connect(self.refresh)
-
-    @once_at_a_time
-    @asyncify
-    async def draw_graph(self, identity):
-        """
-        Draw community graph centered on the identity
-
-        :param sakia.core.registry.Identity identity: Center identity
-        """
-        logging.debug("Draw graph - " + identity.uid)
-        self.busy.show()
-
-        if self.community:
-            identity_account = await self.account.identity(self.community)
-
-            # create empty graph instance
-            graph = WoTGraph(self.app, self.community)
-            await graph.initialize(identity, identity_account)
-            # draw graph in qt scene
-            self.ui.graphicsView.scene().update_wot(graph.nx_graph, identity)
-
-            # if selected member is not the account member...
-            if identity.pubkey != identity_account.pubkey:
-                # add path from selected member to account member
-                path = await graph.get_shortest_path_to_identity(identity, identity_account)
-                if path:
-                    self.ui.graphicsView.scene().update_path(graph.nx_graph, path)
-        self.busy.hide()
-
-    @once_at_a_time
-    @asyncify
-    async def reset(self, checked=False):
-        """
-        Reset graph scene to wallet identity
-        """
-        if self.account and self.community:
-            identity = await self.account.identity(self.community)
-            self.draw_graph(identity)
-
-    def refresh(self):
-        """
-        Refresh graph scene to current metadata
-        """
-        if self._current_identity:
-            self.draw_graph(self._current_identity)
-        else:
-            self.reset()
-
-    def eventFilter(self, source, event):
-        if event.type() == QEvent.Resize:
-            self.busy.resize(event.size())
-            self.widget.resizeEvent(event)
-        return self.widget.eventFilter(source, event)
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-            self._auto_refresh(None)
-            self.refresh()
-        return super(WotTabWidget, self).changeEvent(event)
diff --git a/src/sakia/gui/homescreen.py b/src/sakia/gui/homescreen.py
deleted file mode 100644
index 3eb44eadf63bb86595608e6baabae20bf93c3714..0000000000000000000000000000000000000000
--- a/src/sakia/gui/homescreen.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""
-Created on 31 janv. 2015
-
-@author: vit
-"""
-
-from PyQt5.QtWidgets import QWidget, QFrame, QGridLayout, QAction
-from PyQt5.QtCore import QEvent, Qt, pyqtSlot, pyqtSignal
-from ..gen_resources.homescreen_uic import Ui_HomescreenWidget
-from .community_tile import CommunityTile
-from ..core.community import Community
-import logging
-
-
-class FrameCommunities(QFrame):
-    community_tile_clicked = pyqtSignal(Community)
-
-    def __init__(self, parent):
-        super().__init__(parent)
-        self.grid_layout = QGridLayout()
-        self.setLayout(self.grid_layout)
-        self.grid_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
-        self.setFrameShape(QFrame.StyledPanel)
-        self.setFrameShadow(QFrame.Raised)
-        self.tiles = []
-
-    def sizeHint(self):
-        return self.parentWidget().size()
-
-    def refresh(self, app):
-        for t in self.tiles:
-            t.cancel_refresh()
-            t.setParent(None)
-        self.tiles = []
-        if app.current_account:
-            for c in app.current_account.communities:
-                community_tile = CommunityTile(self, app, c)
-                community_tile.clicked.connect(self.click_on_tile)
-                self.layout().addWidget(community_tile)
-                self.tiles.append(community_tile)
-
-    def refresh_content(self):
-        for t in self.tiles:
-            t.refresh()
-
-    @pyqtSlot()
-    def click_on_tile(self):
-        tile = self.sender()
-        logging.debug("Click on tile")
-        self.community_tile_clicked.emit(tile.community)
-
-
-class HomeScreenWidget(QWidget, Ui_HomescreenWidget):
-    """
-    classdocs
-    """
-
-    def __init__(self, app, status_label):
-        """
-        Constructor
-        """
-        super().__init__()
-        self.setupUi(self)
-        self.app = app
-        self.frame_communities = FrameCommunities(self)
-        self.layout().addWidget(self.frame_communities)
-        self.status_label = status_label
-
-    def refresh(self):
-        self.frame_communities.refresh(self.app)
-        if self.app.current_account:
-            self.frame_connected.show()
-            self.label_connected.setText(self.tr("Connected as {0}".format(self.app.current_account.name)))
-            self.frame_disconnected.hide()
-        else:
-            self.frame_disconnected.show()
-            self.frame_connected.hide()
-
-    def referential_changed(self):
-        self.frame_communities.refresh_content()
-
-    def showEvent(self, QShowEvent):
-        """
-
-        :param QShowEvent:
-        :return:
-        """
-        self.frame_communities.refresh_content()
-        self.status_label.setText("")
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-        return super(HomeScreenWidget, self).changeEvent(event)
-
-
diff --git a/src/sakia/gui/identities_tab.py b/src/sakia/gui/identities_tab.py
deleted file mode 100644
index 21e5ab64e61f2f5ee56208bfdee97bfcfc56c90d..0000000000000000000000000000000000000000
--- a/src/sakia/gui/identities_tab.py
+++ /dev/null
@@ -1,199 +0,0 @@
-"""
-Created on 2 févr. 2014
-
-@author: inso
-"""
-
-import logging
-
-from PyQt5.QtCore import Qt, pyqtSignal, QEvent, QT_TRANSLATE_NOOP, QObject
-from PyQt5.QtGui import QCursor
-from PyQt5.QtWidgets import QWidget, QAction, QMenu, QDialog, \
-                            QAbstractItemView
-from duniterpy.api import bma, errors
-from duniterpy.documents import BlockUID
-
-from ..models.identities import IdentitiesFilterProxyModel, IdentitiesTableModel
-from ..gen_resources.identities_tab_uic import Ui_IdentitiesTab
-from ..core.registry import Identity, BlockchainState
-from ..tools.exceptions import NoPeerAvailable
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from .widgets.context_menu import ContextMenu
-
-
-class IdentitiesTabWidget(QObject):
-
-    """
-    classdocs
-    """
-    view_in_wot = pyqtSignal(object)
-    money_sent = pyqtSignal()
-
-    _direct_connections_text = QT_TRANSLATE_NOOP("IdentitiesTabWidget", "Search direct certifications")
-    _search_placeholder = QT_TRANSLATE_NOOP("IdentitiesTabWidget", "Research a pubkey, an uid...")
-
-    def __init__(self, app, account=None, community=None, password_asker=None,
-                 widget=QWidget, view=Ui_IdentitiesTab):
-        """
-        Init
-
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.Account account: The account displayed in the widget
-        :param sakia.core.Community community: The community displayed in the widget
-        :param sakia.gui.Password_Asker: password_asker: The widget to ask for passwords
-        :param class widget: The class of the PyQt5 widget used for this tab
-        :param class view: The class of the UI View for this tab
-        """
-        super().__init__()
-        self.widget = widget()
-        self.ui = view()
-        self.ui.setupUi(self.widget)
-
-        self.app = app
-        self.community = community
-        self.account = account
-        self.password_asker = password_asker
-
-        self.direct_connections = QAction(self.tr(IdentitiesTabWidget._direct_connections_text), self)
-        self.ui.edit_textsearch.setPlaceholderText(self.tr(IdentitiesTabWidget._search_placeholder))
-
-        identities_model = IdentitiesTableModel()
-        proxy = IdentitiesFilterProxyModel()
-        proxy.setSourceModel(identities_model)
-        self.ui.table_identities.setModel(proxy)
-        self.ui.table_identities.setSelectionBehavior(QAbstractItemView.SelectRows)
-        self.ui.table_identities.customContextMenuRequested.connect(self.identity_context_menu)
-        self.ui.table_identities.sortByColumn(0, Qt.AscendingOrder)
-        self.ui.table_identities.resizeColumnsToContents()
-        identities_model.modelAboutToBeReset.connect(lambda: self.ui.table_identities.setEnabled(False))
-        identities_model.modelReset.connect(lambda: self.ui.table_identities.setEnabled(True))
-
-        self.direct_connections.triggered.connect(self._async_search_direct_connections)
-        self.ui.button_search.addAction(self.direct_connections)
-        self.ui.button_search.clicked.connect(self._async_execute_search_text)
-
-    def cancel_once_tasks(self):
-        cancel_once_task(self, self.identity_context_menu)
-        cancel_once_task(self, self._async_execute_search_text)
-        cancel_once_task(self, self._async_search_direct_connections)
-        cancel_once_task(self, self.refresh_identities)
-
-    def change_account(self, account, password_asker):
-        self.cancel_once_tasks()
-        self.account = account
-        self.password_asker = password_asker
-        if self.account is None:
-            self.community = None
-
-    def change_community(self, community):
-        self.cancel_once_tasks()
-        self.community = community
-        self.ui.table_identities.model().change_community(community)
-        self._async_search_direct_connections()
-
-    @once_at_a_time
-    @asyncify
-    async def identity_context_menu(self, point):
-        index = self.ui.table_identities.indexAt(point)
-        model = self.ui.table_identities.model()
-        if index.isValid() and index.row() < model.rowCount():
-            source_index = model.mapToSource(index)
-            pubkey_col = model.sourceModel().columns_ids.index('pubkey')
-            pubkey_index = model.sourceModel().index(source_index.row(),
-                                                   pubkey_col)
-            pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole)
-            identity = await self.app.identities_registry.future_find(pubkey, self.community)
-            menu = ContextMenu.from_data(self.widget, self.app, self.account, self.community, self.password_asker,
-                                         (identity,))
-            menu.view_identity_in_wot.connect(self.view_in_wot)
-
-            # Show the context menu.
-            menu.qmenu.popup(QCursor.pos())
-
-    @once_at_a_time
-    @asyncify
-    async def _async_execute_search_text(self, checked):
-        cancel_once_task(self, self._async_search_direct_connections)
-
-        self.ui.busy.show()
-        text = self.ui.edit_textsearch.text()
-        if len(text) < 2:
-            return
-        try:
-            response = await self.community.bma_access.future_request(bma.wot.Lookup, {'search': text})
-            identities = []
-            for identity_data in response['results']:
-                for uid_data in identity_data['uids']:
-                    identity = Identity.from_handled_data(uid_data['uid'],
-                                                         identity_data['pubkey'],
-                                                         BlockUID.from_str(uid_data['meta']['timestamp']),
-                                                         BlockchainState.BUFFERED)
-                    identities.append(identity)
-
-            self.ui.edit_textsearch.clear()
-            self.ui.edit_textsearch.setPlaceholderText(text)
-            await self.refresh_identities(identities)
-        except errors.DuniterError as e:
-            if e.ucode == errors.BLOCK_NOT_FOUND:
-                logging.debug(str(e))
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        finally:
-            self.ui.busy.hide()
-
-    @once_at_a_time
-    @asyncify
-    async def _async_search_direct_connections(self, checked=False):
-        """
-        Search members of community and display found members
-        """
-        cancel_once_task(self, self._async_execute_search_text)
-
-        if self.account and self.community:
-            try:
-                self.ui.edit_textsearch.setPlaceholderText(self.tr(IdentitiesTabWidget._search_placeholder))
-                await self.refresh_identities([])
-                self.ui.busy.show()
-                self_identity = await self.account.identity(self.community)
-                account_connections = []
-                certs_of = await self_identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
-                for p in certs_of:
-                    account_connections.append(p['identity'])
-                certifiers_of = [p for p in account_connections]
-                certs_by = await self_identity.unique_valid_certified_by(self.app.identities_registry, self.community)
-                for p in certs_by:
-                    account_connections.append(p['identity'])
-                certified_by = [p for p in account_connections
-                          if p.pubkey not in [i.pubkey for i in certifiers_of]]
-                identities = certifiers_of + certified_by
-                self.ui.busy.hide()
-                await self.refresh_identities(identities)
-            except NoPeerAvailable:
-                self.ui.busy.hide()
-
-    async def refresh_identities(self, identities):
-        """
-        Refresh the table with specified identities.
-        If no identities is passed, use the account connections.
-        """
-        await self.ui.table_identities.model().sourceModel().refresh_identities(identities)
-        self.ui.table_identities.resizeColumnsToContents()
-
-    def retranslateUi(self, widget):
-        self.direct_connections.setText(self.tr(IdentitiesTabWidget._direct_connections_text))
-        super().retranslateUi(self)
-
-    def resizeEvent(self, event):
-        self.ui.busy.resize(event.size())
-        super().resizeEvent(event)
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-        return super(IdentitiesTabWidget, self).changeEvent(event)
-
diff --git a/src/sakia/gui/import_account.py b/src/sakia/gui/import_account.py
deleted file mode 100644
index 59d73ff48fd9b2839406094b297ecd2b5c0ed25d..0000000000000000000000000000000000000000
--- a/src/sakia/gui/import_account.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""
-Created on 22 mai 2014
-
-@author: inso
-"""
-import re
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, QFileDialog
-
-from ..tools.exceptions import Error
-from ..gen_resources.import_account_uic import Ui_ImportAccountDialog
-
-
-class ImportAccountDialog(QDialog, Ui_ImportAccountDialog):
-
-    """
-    classdocs
-    """
-
-    def __init__(self, app, parent=None):
-        """
-        Constructor
-        """
-        super().__init__()
-        self.setupUi(self)
-        self.app = app
-        self.main_window = parent
-        self.selected_file = ""
-        self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
-
-    def accept(self):
-        account_name = self.edit_name.text()
-        try:
-            self.app.import_account(self.selected_file, account_name)
-        except Exception as e:
-            QMessageBox.critical(self, self.tr("Error"),
-                                 "{0}".format(e),
-                                 QMessageBox.Ok)
-            return
-        QMessageBox.information(self, self.tr("Account import"),
-                                self.tr("Account imported succefully !"))
-        super().accept()
-
-    def import_account(self):
-        self.selected_file = QFileDialog.getOpenFileName(self,
-                                          self.tr("Import an account file"),
-                                          "",
-                                          self.tr("All account files (*.acc)"))
-        self.selected_file = self.selected_file[0]
-        self.edit_file.setText(self.selected_file)
-        self.check()
-
-    def name_changed(self):
-        self.check()
-
-    def check(self):
-        name = self.edit_name.text()
-        if name == "":
-            self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
-            self.label_errors.setText(self.tr("Please enter a name"))
-            return
-        for account in self.app.accounts:
-            if name == account:
-                self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
-                self.label_errors.setText(self.tr("Name already exists"))
-                return
-        if self.selected_file[-4:] != ".acc":
-            self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
-            self.label_errors.setText(self.tr("File is not an account format"))
-            return
-        self.label_errors.setText("")
-        self.button_box.button(QDialogButtonBox.Ok).setEnabled(True)
diff --git a/src/sakia/gui/informations_tab.py b/src/sakia/gui/informations_tab.py
deleted file mode 100644
index af6be48e89a9534237fa120560e25c2ea6355819..0000000000000000000000000000000000000000
--- a/src/sakia/gui/informations_tab.py
+++ /dev/null
@@ -1,312 +0,0 @@
-"""
-Created on 31 janv. 2015
-
-@author: vit
-"""
-
-import logging
-import math
-from PyQt5.QtCore import QLocale, QDateTime, QEvent
-from PyQt5.QtWidgets import QWidget
-from ..gen_resources.informations_tab_uic import Ui_InformationsTabWidget
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from ..tools.exceptions import NoPeerAvailable
-from .widgets import Busy
-from ..core.money import Referentials
-
-
-class InformationsTabWidget(QWidget, Ui_InformationsTabWidget):
-    """
-    classdocs
-    """
-
-    def __init__(self, app):
-        """
-        Constructor of the InformationsTabWidget
-
-        :param sakia.core.app.Application app: Application instance
-
-        :return:
-        """
-        super().__init__()
-        self.setupUi(self)
-        self.app = app
-        self.account = None
-        self.community = None
-        self.busy = Busy(self.scrollArea)
-        self.busy.hide()
-
-    def change_account(self, account):
-        """
-
-        :param sakia.core.app.Account account: Account instance selected
-        """
-        cancel_once_task(self, self.refresh_labels)
-        self.account = account
-
-    def change_community(self, community):
-        cancel_once_task(self, self.refresh_labels)
-        self.community = community
-        self.refresh()
-
-    def refresh(self):
-        if self.account and self.community:
-            self.refresh_labels()
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_labels(self):
-        self.busy.show()
-        #  try to request money parameters
-        try:
-            params = await self.community.parameters()
-        except NoPeerAvailable as e:
-            logging.debug('community parameters error : ' + str(e))
-            return False
-
-        #  try to request money variables from last ud block
-        try:
-            block_ud = await self.community.get_ud_block()
-        except NoPeerAvailable as e:
-            logging.debug('community get_ud_block error : ' + str(e))
-            return False
-        try:
-            block_ud_minus_1 = await self.community.get_ud_block(x=1)
-        except NoPeerAvailable as e:
-            logging.debug('community get_ud_block error : ' + str(e))
-            return False
-
-        if block_ud:
-            # display float values
-            localized_ud = await self.account.current_ref.instance(block_ud['dividend'] * math.pow(10, block_ud['unitbase']),
-                                                               self.community,
-                                                               self.app) \
-                .diff_localized(True, self.app.preferences['international_system_of_units'])
-
-            computed_dividend = await self.community.computed_dividend()
-            # display float values
-            localized_ud_plus_1 = await self.account.current_ref.instance(computed_dividend,
-                                                    self.community, self.app)\
-                .diff_localized(True, self.app.preferences['international_system_of_units'])
-
-            localized_mass = await self.account.current_ref.instance(block_ud['monetaryMass'],
-                                                    self.community, self.app)\
-                .diff_localized(True, self.app.preferences['international_system_of_units'])
-
-            localized_ud_median_time = QLocale.toString(
-                        QLocale(),
-                        QDateTime.fromTime_t(block_ud['medianTime']),
-                        QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
-                    )
-
-            localized_next_ud_median_time = QLocale.toString(
-                        QLocale(),
-                        QDateTime.fromTime_t(block_ud['medianTime'] + params['dt']),
-                        QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
-                    )
-
-            if block_ud_minus_1:
-                mass_minus_1 = (float(0) if block_ud['membersCount'] == 0 else
-                        block_ud_minus_1['monetaryMass'] / block_ud['membersCount'])
-                localized_mass_minus_1_per_member = await self.account.current_ref.instance(mass_minus_1,
-                                                                  self.community, self.app)\
-                    .diff_localized(True, self.app.preferences['international_system_of_units'])
-                localized_mass_minus_1 = await self.account.current_ref.instance(block_ud_minus_1['monetaryMass'],
-                                                                  self.community, self.app)\
-                    .diff_localized(True, self.app.preferences['international_system_of_units'])
-                # avoid divide by zero !
-                if block_ud['membersCount'] == 0 or block_ud_minus_1['monetaryMass'] == 0:
-                    actual_growth = float(0)
-                else:
-                    actual_growth = (block_ud['dividend'] * math.pow(10, block_ud['unitbase'])) / (block_ud_minus_1['monetaryMass'] / block_ud['membersCount'])
-
-                localized_ud_median_time_minus_1 = QLocale.toString(
-                    QLocale(),
-                    QDateTime.fromTime_t(block_ud_minus_1['medianTime']),
-                    QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
-                )
-            else:
-                localized_mass_minus_1_per_member = QLocale().toString(
-                        float(0), 'f', self.app.preferences['digits_after_comma']
-                )
-                localized_mass_minus_1 = QLocale().toString(
-                        float(0), 'f', self.app.preferences['digits_after_comma']
-                )
-                actual_growth = float(0)
-                localized_ud_median_time_minus_1 = "####"
-
-            # set infos in label
-            self.label_general.setText(
-                    self.tr("""
-                <table cellpadding="5">
-                <tr><td align="right"><b>{:}</b></div></td><td>{:} {:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
-                <tr><td align="right"><b>{:2.2%} / {:} days</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                </table>
-                """).format(
-                    localized_ud,
-                    self.tr('Universal Dividend UD(t) in'),
-                    self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                    localized_mass_minus_1,
-                    self.tr('Monetary Mass M(t-1) in'),
-                    self.account.current_ref.instance(0, self.community, self.app, None).units,
-                    block_ud['membersCount'],
-                    self.tr('Members N(t)'),
-                    localized_mass_minus_1_per_member,
-                    self.tr('Monetary Mass per member M(t-1)/N(t) in'),
-                    self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                    actual_growth,
-                    params['dt'] / 86400,
-                    self.tr('Actual growth c = UD(t)/[M(t-1)/N(t)]'),
-                    localized_ud_median_time_minus_1,
-                    self.tr('Penultimate UD date and time (t-1)'),
-                    localized_ud_median_time,
-                    self.tr('Last UD date and time (t)'),
-                    localized_next_ud_median_time,
-                    self.tr('Next UD date and time (t+1)')
-                )
-            )
-        else:
-            self.label_general.setText(self.tr('No Universal Dividend created yet.'))
-
-        if block_ud:
-            # set infos in label
-            self.label_rules.setText(
-                    self.tr("""
-                <table cellpadding="5">
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-                </table>
-                """).format(
-                    self.tr('{:2.0%} / {:} days').format(params['c'], params['dt'] / 86400),
-                    self.tr('Fundamental growth (c) / Delta time (dt)'),
-                    self.tr('UD(t+1) = MAX { UD(t) ; c &#215; M(t) / N(t+1) }'),
-                    self.tr('Universal Dividend (formula)'),
-                    self.tr('{:} = MAX {{ {:} {:} ; {:2.0%} &#215; {:} {:} / {:} }}').format(
-                        localized_ud_plus_1,
-                        localized_ud,
-                        self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                        params['c'],
-                        localized_mass,
-                        self.account.current_ref.instance(0, self.community, self.app, None).diff_units,
-                        block_ud['membersCount']
-                    ),
-                    self.tr('Universal Dividend (computed)')
-                )
-            )
-        else:
-            self.label_rules.setText(self.tr('No Universal Dividend created yet.'))
-
-        # set infos in label
-        ref_template = """
-        <table cellpadding="5">
-        <tr><th>{:}</th><td>{:}</td></tr>
-        <tr><th>{:}</th><td>{:}</td></tr>
-        <tr><th>{:}</th><td>{:}</td></tr>
-        <tr><th>{:}</th><td>{:}</td></tr>
-        </table>
-        """
-        templates = []
-        for ref_class in Referentials:
-            ref = ref_class(0, self.community, self.app, None)
-            # print(ref_class.__class__.__name__)
-            # if ref_class.__class__.__name__ == 'RelativeToPast':
-            #     continue
-            templates.append(ref_template.format(self.tr('Name'), ref.translated_name(),
-                                        self.tr('Units'), ref.units,
-                                        self.tr('Formula'), ref.formula,
-                                        self.tr('Description'), ref.description
-                                        )
-                             )
-
-        self.label_referentials.setText('<hr>'.join(templates))
-
-        # set infos in label
-        self.label_money.setText(
-                self.tr("""
-            <table cellpadding="5">
-            <tr><td align="right"><b>{:2.0%} / {:} days</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:2.0%}</b></td><td>{:}</td></tr>
-            </table>
-            """).format(
-                        params['c'],
-                        params['dt'] / 86400,
-                        self.tr('Fundamental growth (c)'),
-                        params['ud0'],
-                        self.tr('Initial Universal Dividend UD(0) in'),
-                        self.community.short_currency,
-                        params['dt'] / 86400,
-                        self.tr('Time period (dt) in days (86400 seconds) between two UD'),
-                        params['medianTimeBlocks'],
-                        self.tr('Number of blocks used for calculating median time'),
-                        params['avgGenTime'],
-                        self.tr('The average time in seconds for writing 1 block (wished time)'),
-                        params['dtDiffEval'],
-                        self.tr('The number of blocks required to evaluate again PoWMin value'),
-                        params['blocksRot'],
-                        self.tr('The number of previous blocks to check for personalized difficulty'),
-                        params['percentRot'],
-                        self.tr('The percent of previous issuers to reach for personalized difficulty')
-                )
-        )
-
-        # set infos in label
-        self.label_wot.setText(
-                self.tr("""
-            <table cellpadding="5">
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
-            </table>
-            """).format(
-                        params['sigPeriod'] / 86400,
-                        self.tr('Minimum delay between 2 certifications (in days)'),
-                        params['sigValidity'] / 86400,
-                        self.tr('Maximum age of a valid signature (in days)'),
-                        params['sigQty'],
-                        self.tr('Minimum quantity of signatures to be part of the WoT'),
-                        params['sigStock'],
-                        self.tr('Maximum quantity of active certifications made by member.'),
-                        params['sigWindow'],
-                        self.tr('Maximum delay a certification can wait before being expired for non-writing.'),
-                        params['xpercent'],
-                        self.tr('Minimum percent of sentries to reach to match the distance rule'),
-                        params['msValidity'] / 86400,
-                        self.tr('Maximum age of a valid membership (in days)'),
-                        params['stepMax'],
-                        self.tr('Maximum distance between each WoT member and a newcomer'),
-                )
-        )
-        self.busy.hide()
-
-    def resizeEvent(self, event):
-        self.busy.resize(event.size())
-        super().resizeEvent(event)
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-            self.refresh()
-        return super(InformationsTabWidget, self).changeEvent(event)
diff --git a/src/sakia/tests/functional/certification/__init__.py b/src/sakia/gui/main_window/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/certification/__init__.py
rename to src/sakia/gui/main_window/__init__.py
diff --git a/src/sakia/gui/main_window/controller.py b/src/sakia/gui/main_window/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..27ff212cb33129bc6aba0679c907bdbb417422f9
--- /dev/null
+++ b/src/sakia/gui/main_window/controller.py
@@ -0,0 +1,143 @@
+import logging
+
+from PyQt5.QtWidgets import QMessageBox, QApplication
+from PyQt5.QtCore import QEvent, pyqtSlot, QObject
+from PyQt5.QtGui import QIcon
+
+from ..password_asker import PasswordAskerDialog
+from ...__init__ import __version__
+from ..widgets import toast
+from .view import MainWindowView
+from .model import MainWindowModel
+from .status_bar.controller import StatusBarController
+from .toolbar.controller import ToolbarController
+from ..navigation.controller import NavigationController
+
+
+class MainWindowController(QObject):
+    """
+    classdocs
+    """
+
+    def __init__(self, view, model, password_asker, status_bar, toolbar, navigation):
+        """
+        Init
+        :param MainWindowView view: the ui of the mainwindow component
+        :param sakia.gui.main_window.model.MainWindowModel: the model of the mainwindow component
+        :param sakia.gui.status_bar.controller.StatusBarController status_bar: the controller of the status bar component
+        :param sakia.gui.toolbar.controller.ToolbarController toolbar: the controller of the toolbar component
+        :param sakia.gui.navigation.contoller.NavigationController navigation: the controller of the navigation
+
+        :param PasswordAsker password_asker: the password asker of the application
+        :type: sakia.core.app.Application
+        """
+        # Set up the user interface from Designer.
+        super().__init__()
+        self.view = view
+        self.model = model
+        self.initialized = False
+        self.password_asker = password_asker
+        self.status_bar = status_bar
+        self.toolbar = toolbar
+        self.navigation = navigation
+        self.stacked_widgets = {}
+        self.view.bottom_layout.insertWidget(0, self.navigation.view)
+        self.view.top_layout.addWidget(self.toolbar.view)
+        self.view.setStatusBar(self.status_bar.view)
+
+        QApplication.setWindowIcon(QIcon(":/icons/sakia_logo"))
+
+    @classmethod
+    def create(cls, app, password_asker, status_bar, toolbar, navigation):
+        """
+        Instanciate a navigation component
+        :param sakia.gui.status_bar.controller.StatusBarController status_bar: the controller of the status bar component
+        :param sakia.gui.toolbar.controller.ToolbarController toolbar: the controller of the toolbar component
+        :param sakia.gui.navigation.contoller.NavigationController navigation: the controller of the navigation
+
+        :return: a new Navigation controller
+        :rtype: MainWindowController
+        """
+        view = MainWindowView()
+        model = MainWindowModel(None, app)
+        main_window = cls(view, model, password_asker, status_bar, toolbar, navigation)
+        model.setParent(main_window)
+        return main_window
+
+    @classmethod
+    def startup(cls, app):
+        """
+
+        :param sakia.app.Application app:
+        :return:
+        """
+        password_asker = PasswordAskerDialog(None)
+        navigation = NavigationController.create(None, app)
+        toolbar = ToolbarController.create(app, navigation)
+        main_window = cls.create(app, password_asker=password_asker,
+                                 status_bar=StatusBarController.create(app),
+                                 navigation=navigation,
+                                 toolbar=toolbar
+                                 )
+        currencies = app.db.connections_repo.get_currencies()
+        if currencies:
+            currency = currencies[0]
+        else:
+            currency = ""
+
+        #app.version_requested.connect(main_window.latest_version_requested)
+        #app.account_imported.connect(main_window.import_account_accepted)
+        #app.account_changed.connect(main_window.change_account)
+
+        main_window.view.showMaximized()
+        main_window.refresh(currency)
+        return main_window
+
+    @pyqtSlot(str)
+    def display_error(self, error):
+        QMessageBox.critical(self, ":(",
+                             error,
+                             QMessageBox.Ok)
+
+    @pyqtSlot(int)
+    def referential_changed(self, index):
+        pass
+
+    @pyqtSlot()
+    def latest_version_requested(self):
+        latest = self.app.available_version
+        logging.debug("Latest version requested")
+        if not latest[0]:
+            version_info = self.tr("Please get the latest release {version}") \
+                .format(version=latest[1])
+            version_url = latest[2]
+
+            if self.app.preferences['notifications']:
+                toast.display("sakia", """{version_info}""".format(
+                version_info=version_info,
+                version_url=version_url))
+
+    def refresh(self, currency):
+        """
+        Refresh main window
+        When the selected account changes, all the widgets
+        in the window have to be refreshed
+        """
+        self.status_bar.refresh()
+        self.toolbar.enable_actions(len(self.navigation.model.navigation[0]['children']) > 0)
+        self.view.setWindowTitle(self.tr("sakia {0} - {currency}").format(__version__, currency=currency))
+
+    def eventFilter(self, target, event):
+        """
+        Event filter on the widget
+        :param QObject target: the target of the event
+        :param QEvent event: the event
+        :return: bool
+        """
+        if target == self.widget:
+            if event.type() == QEvent.LanguageChange:
+                self.ui.retranslateUi(self)
+                self.refresh()
+            return self.widget.eventFilter(target, event)
+        return False
+
diff --git a/res/ui/mainwindow.ui b/src/sakia/gui/main_window/mainwindow.ui
similarity index 62%
rename from res/ui/mainwindow.ui
rename to src/sakia/gui/main_window/mainwindow.ui
index d92357d930a1c8257d97ae0eaa38dbe2dfe9b4f5..47a6ed732665c0bcb738dfd657fd4bd7d7572934 100644
--- a/res/ui/mainwindow.ui
+++ b/src/sakia/gui/main_window/mainwindow.ui
@@ -14,78 +14,15 @@
    <string notr="true">Sakia</string>
   </property>
   <widget class="QWidget" name="centralwidget">
-   <layout class="QVBoxLayout" name="verticalLayout_6"/>
+   <layout class="QVBoxLayout" name="verticalLayout_6">
+    <item>
+     <layout class="QHBoxLayout" name="top_layout"/>
+    </item>
+    <item>
+     <layout class="QHBoxLayout" name="bottom_layout"/>
+    </item>
+   </layout>
   </widget>
-  <widget class="QMenuBar" name="menubar">
-   <property name="geometry">
-    <rect>
-     <x>0</x>
-     <y>0</y>
-     <width>900</width>
-     <height>30</height>
-    </rect>
-   </property>
-   <widget class="QMenu" name="menu_file">
-    <property name="title">
-     <string>Fi&amp;le</string>
-    </property>
-    <addaction name="action_import"/>
-    <addaction name="action_export"/>
-    <addaction name="separator"/>
-    <addaction name="actionPreferences"/>
-    <addaction name="action_quit"/>
-   </widget>
-   <widget class="QMenu" name="menu_account">
-    <property name="title">
-     <string>Acco&amp;unt</string>
-    </property>
-    <widget class="QMenu" name="menu_contacts_list">
-     <property name="title">
-      <string>Co&amp;ntacts</string>
-     </property>
-     <addaction name="separator"/>
-    </widget>
-    <widget class="QMenu" name="menu_change_account">
-     <property name="title">
-      <string>&amp;Open</string>
-     </property>
-    </widget>
-    <widget class="QMenu" name="menuAdvanced">
-     <property name="title">
-      <string>Advanced</string>
-     </property>
-     <addaction name="action_revoke_identity"/>
-    </widget>
-    <addaction name="menu_change_account"/>
-    <addaction name="action_configure_parameters"/>
-    <addaction name="action_add_account"/>
-    <addaction name="separator"/>
-    <addaction name="actionCertification"/>
-    <addaction name="actionTransfer_money"/>
-    <addaction name="separator"/>
-    <addaction name="action_add_a_contact"/>
-    <addaction name="menu_contacts_list"/>
-    <addaction name="separator"/>
-    <addaction name="menuAdvanced"/>
-   </widget>
-   <widget class="QMenu" name="menu_help">
-    <property name="title">
-     <string>&amp;Help</string>
-    </property>
-    <addaction name="actionAbout"/>
-   </widget>
-   <widget class="QMenu" name="menu_duniter">
-    <property name="title">
-     <string>&amp;Duniter</string>
-    </property>
-    <addaction name="actionManage_local_node"/>
-   </widget>
-   <addaction name="menu_file"/>
-   <addaction name="menu_account"/>
-   <addaction name="menu_duniter"/>
-   <addaction name="menu_help"/>
-  </widget>
-  <widget class="QStatusBar" name="statusbar"/>
   <action name="actionManage_accounts">
    <property name="text">
     <string>Manage accounts</string>
@@ -198,12 +135,12 @@
   </action>
   <action name="action_revoke_identity">
    <property name="text">
-    <string>Revoke an identity</string>
+    <string>&amp;Revoke an identity</string>
    </property>
   </action>
  </widget>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../../res/icons/icons.qrc"/>
  </resources>
  <connections/>
  <slots>
diff --git a/src/sakia/gui/main_window/model.py b/src/sakia/gui/main_window/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..81714afea5e593e32bdb1aea37d63e4d47cef4f4
--- /dev/null
+++ b/src/sakia/gui/main_window/model.py
@@ -0,0 +1,12 @@
+from PyQt5.QtCore import QObject
+
+
+class MainWindowModel(QObject):
+    """
+    The model of Navigation component
+    """
+
+    def __init__(self, parent, app):
+        super().__init__(parent)
+        self.app = app
+
diff --git a/src/sakia/tests/functional/identities_tab/__init__.py b/src/sakia/gui/main_window/status_bar/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/identities_tab/__init__.py
rename to src/sakia/gui/main_window/status_bar/__init__.py
diff --git a/src/sakia/gui/main_window/status_bar/controller.py b/src/sakia/gui/main_window/status_bar/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..16c4e6d2eeed9c59e2955864dd530be3b750323e
--- /dev/null
+++ b/src/sakia/gui/main_window/status_bar/controller.py
@@ -0,0 +1,65 @@
+from PyQt5.QtCore import QLocale, pyqtSlot, QDateTime, QTimer, QObject
+from .model import StatusBarModel
+from .view import StatusBarView
+import logging
+
+
+class StatusBarController(QObject):
+    """
+    The navigation panel
+    """
+
+    def __init__(self, view, model):
+        """
+        Constructor of the navigation component
+
+        :param sakia.gui.status_bar.view.StatusBarView view: the presentation
+        :param sakia.core.status_bar.model.StatusBarModel model: the model
+        """
+        super().__init__()
+        self.view = view
+        self.model = model
+        view.combo_referential.currentIndexChanged[int].connect(self.referential_changed)
+        self.update_time()
+
+    @classmethod
+    def create(cls, app):
+        """
+        Instanciate a navigation component
+        :param sakia.gui.main_window.controller.MainWindowController parent:
+        :return: a new Navigation controller
+        :rtype: NavigationController
+        """
+        view = StatusBarView(None)
+
+        model = StatusBarModel(None, app)
+        status_bar = cls(view, model)
+        return status_bar
+
+    @pyqtSlot()
+    def update_time(self):
+        dateTime = QDateTime.currentDateTime()
+        self.view.label_time.setText("{0}".format(QLocale.toString(
+                        QLocale(),
+                        QDateTime.currentDateTime(),
+                        QLocale.dateTimeFormat(QLocale(), QLocale.NarrowFormat)
+                    )))
+        timer = QTimer()
+        timer.timeout.connect(self.update_time)
+        timer.start(1000)
+
+    def refresh(self):
+        """
+        Refresh main window
+        When the selected account changes, all the widgets
+        in the window have to be refreshed
+        """
+        logging.debug("Refresh started")
+        for ref in self.model.referentials():
+            self.view.combo_referential.addItem(ref.translated_name())
+
+        self.view.combo_referential.setCurrentIndex(self.model.default_referential())
+
+    @pyqtSlot(int)
+    def referential_changed(self, index):
+        self.model.app.change_referential(index)
\ No newline at end of file
diff --git a/src/sakia/gui/main_window/status_bar/model.py b/src/sakia/gui/main_window/status_bar/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..f55202aa76019d2a6af704a4a6f2bbb24e4e993a
--- /dev/null
+++ b/src/sakia/gui/main_window/status_bar/model.py
@@ -0,0 +1,23 @@
+from PyQt5.QtCore import QObject
+from sakia.money import Referentials
+
+
+class StatusBarModel(QObject):
+    """
+    The model of status bar component
+    """
+
+    def __init__(self, parent, app):
+        """
+        The status bar model
+        :param parent:
+        :param sakia.app.Application app: the app
+        """
+        super().__init__(parent)
+        self.app = app
+
+    def referentials(self):
+        return Referentials
+
+    def default_referential(self):
+        return self.app.parameters.referential
diff --git a/src/sakia/gui/main_window/status_bar/view.py b/src/sakia/gui/main_window/status_bar/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2751520667c30895b4d71d841838f169456010a
--- /dev/null
+++ b/src/sakia/gui/main_window/status_bar/view.py
@@ -0,0 +1,25 @@
+from PyQt5.QtWidgets import QStatusBar
+from PyQt5.QtWidgets import QLabel, QComboBox
+from PyQt5.QtCore import Qt
+
+
+class StatusBarView(QStatusBar):
+    """
+    The model of Navigation component
+    """
+
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.label_icon = QLabel("", parent)
+
+        self.status_label = QLabel("", parent)
+        self.status_label.setTextFormat(Qt.RichText)
+
+        self.label_time = QLabel("", parent)
+
+        self.combo_referential = QComboBox(parent)
+
+        self.addPermanentWidget(self.label_icon, 1)
+        self.addPermanentWidget(self.status_label, 2)
+        self.addPermanentWidget(self.label_time)
+        self.addPermanentWidget(self.combo_referential)
\ No newline at end of file
diff --git a/src/sakia/tests/functional/main_window/__init__.py b/src/sakia/gui/main_window/toolbar/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/main_window/__init__.py
rename to src/sakia/gui/main_window/toolbar/__init__.py
diff --git a/res/ui/about.ui b/src/sakia/gui/main_window/toolbar/about.ui
similarity index 100%
rename from res/ui/about.ui
rename to src/sakia/gui/main_window/toolbar/about.ui
diff --git a/src/sakia/gui/main_window/toolbar/controller.py b/src/sakia/gui/main_window/toolbar/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c3fc1966867de4e07f6cc59ae5387bc04704633
--- /dev/null
+++ b/src/sakia/gui/main_window/toolbar/controller.py
@@ -0,0 +1,115 @@
+from PyQt5.QtCore import Qt, QObject
+from PyQt5.QtWidgets import QDialog, QMessageBox
+
+from sakia.decorators import asyncify
+from sakia.gui.dialogs.connection_cfg.controller import ConnectionConfigController
+from sakia.gui.dialogs.certification.controller import CertificationController
+from sakia.gui.dialogs.revocation.controller import RevocationController
+from sakia.gui.dialogs.transfer.controller import TransferController
+from sakia.gui.widgets import toast
+from sakia.gui.widgets.dialogs import QAsyncMessageBox, QAsyncFileDialog, dialog_async_exec
+from sakia.gui.password_asker import PasswordAskerDialog
+from sakia.gui.preferences import PreferencesDialog
+from .model import ToolbarModel
+from .view import ToolbarView
+
+
+class ToolbarController(QObject):
+    """
+    The navigation panel
+    """
+
+    def __init__(self, view, model):
+        """
+        :param sakia.gui.component.controller.ComponentController parent: the parent
+        :param sakia.gui.main_window.toolbar.view.ToolbarView view:
+        :param sakia.gui.main_window.toolbar.model.ToolbarModel model:
+        """
+        super().__init__()
+        self.view = view
+        self.model = model
+        self.view.button_certification.clicked.connect(self.open_certification_dialog)
+        self.view.button_send_money.clicked.connect(self.open_transfer_money_dialog)
+        self.view.button_membership.clicked.connect(self.send_join_demand)
+        self.view.action_add_connection.triggered.connect(self.open_add_connection_dialog)
+        self.view.action_parameters.triggered.connect(self.open_settings_dialog)
+        self.view.action_about.triggered.connect(self.open_about_dialog)
+        self.view.action_revoke_uid.triggered.connect(self.open_revocation_dialog)
+
+    @classmethod
+    def create(cls, app, navigation):
+        """
+        Instanciate a navigation component
+        :param sakia.app.Application app:
+        :param sakia.gui.navigation.controller.NavigationController navigation:
+        :return: a new Navigation controller
+        :rtype: NavigationController
+        """
+        view = ToolbarView(None)
+        model = ToolbarModel(app, navigation.model)
+        toolbar = cls(view, model)
+        return toolbar
+
+    def enable_actions(self, enabled):
+        self.view.button_certification.setEnabled(enabled)
+        self.view.button_send_money.setEnabled(enabled)
+        self.view.button_membership.setEnabled(enabled)
+
+    @asyncify
+    async def send_join_demand(self, checked=False):
+        connection = await self.view.ask_for_connection(self.model.connections())
+        if not connection:
+            return
+        password = await PasswordAskerDialog(connection).async_exec()
+        if not password:
+            return
+        result = await self.model.send_join(connection, password)
+        if result[0]:
+            if self.model.notifications():
+                toast.display(self.tr("Membership"), self.tr("Success sending Membership demand"))
+            else:
+                await QAsyncMessageBox.information(self, self.tr("Membership"),
+                                                        self.tr("Success sending Membership demand"))
+        else:
+            if self.model.notifications():
+                toast.display(self.tr("Membership"), result[1])
+            else:
+                await QAsyncMessageBox.critical(self, self.tr("Membership"),
+                                                        result[1])
+
+    def open_certification_dialog(self):
+        CertificationController.open_dialog(self, self.model.app,
+                                            self.model.navigation_model.current_connection())
+
+    def open_revocation_dialog(self):
+        RevocationController.open_dialog(self, self.model.app, self.model.navigation_model.current_connection())
+
+    def open_transfer_money_dialog(self):
+        TransferController.open_dialog(self, self.model.app, self.model.navigation_model.current_connection())
+
+    def open_settings_dialog(self):
+        PreferencesDialog(self.model.app).exec()
+
+    def open_add_connection_dialog(self):
+        connection_config = ConnectionConfigController.create_connection(self, self.model.app)
+        connection_config.exec()
+        if connection_config.view.result() == QDialog.Accepted:
+            self.model.app.instanciate_services()
+            self.model.app.start_coroutines()
+            self.model.app.new_connection.emit(connection_config.model.connection)
+            self.enable_actions(True)
+
+    def open_about_dialog(self):
+        text = self.model.about_text()
+        self.view.show_about(text)
+
+    def retranslateUi(self, widget):
+        """
+        Method to complete translations missing from generated code
+        :param widget:
+        :return:
+        """
+        self.action_publish_uid.setText(self.tr(ToolbarController.action_publish_uid_text))
+        self.action_revoke_uid.setText(self.tr(ToolbarController.action_revoke_uid_text))
+        self.action_showinfo.setText(self.tr(ToolbarController.action_showinfo_text))
+        super().retranslateUi(self)
diff --git a/src/sakia/gui/main_window/toolbar/model.py b/src/sakia/gui/main_window/toolbar/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..abf8adb0500a8f67df99214671bc8179c0067d16
--- /dev/null
+++ b/src/sakia/gui/main_window/toolbar/model.py
@@ -0,0 +1,62 @@
+from PyQt5.QtCore import QObject
+from sakia.data.processors import ConnectionsProcessor
+import attr
+from sakia import __version__
+
+
+@attr.s()
+class ToolbarModel(QObject):
+    """
+    The model of Navigation component
+
+    :param sakia.app.Application app: the application
+    :param sakia.gui.navigation.model.NavigationModel navigation_model: The navigation model
+    """
+
+    app = attr.ib()
+    navigation_model = attr.ib()
+
+    def __attrs_post_init__(self):
+        super().__init__()
+
+    async def send_join(self, connection, password):
+        return await self.app.documents_service.send_membership(connection, password, "IN")
+
+    def notifications(self):
+        return self.app.parameters.notifications
+
+    def connections(self):
+        return ConnectionsProcessor.instanciate(self.app).connections()
+
+    def about_text(self):
+        latest = self.app.available_version
+        version_info = ""
+        version_url = ""
+        if not latest[0]:
+            version_info = "Latest release : {version}" \
+                            .format(version=latest[1])
+            version_url = latest[2]
+
+        new_version_text = """
+            <p><b>{version_info}</b></p>
+            <p><a href={version_url}>Download link</a></p>
+            """.format(version_info=version_info,
+                       version_url=version_url)
+        return """
+        <h1>Sakia</h1>
+
+        <p>Python/Qt Duniter client</p>
+
+        <p>Version : {:}</p>
+        {new_version_text}
+
+        <p>License : GPLv3</p>
+
+        <p><b>Authors</b></p>
+
+        <p>inso</p>
+        <p>vit</p>
+        <p>canercandan</p>
+        <p>Moul</p>
+        """.format(__version__,
+                   new_version_text=new_version_text)
\ No newline at end of file
diff --git a/src/sakia/gui/main_window/toolbar/toolbar.ui b/src/sakia/gui/main_window/toolbar/toolbar.ui
new file mode 100644
index 0000000000000000000000000000000000000000..39850025c90911ad23c57e76d28c2c2c7c1480a8
--- /dev/null
+++ b/src/sakia/gui/main_window/toolbar/toolbar.ui
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SakiaToolbar</class>
+ <widget class="QFrame" name="SakiaToolbar">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>666</width>
+    <height>237</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Frame</string>
+  </property>
+  <property name="frameShape">
+   <enum>QFrame::StyledPanel</enum>
+  </property>
+  <property name="frameShadow">
+   <enum>QFrame::Raised</enum>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QPushButton" name="button_send_money">
+     <property name="text">
+      <string>Send money</string>
+     </property>
+     <property name="icon">
+      <iconset resource="../../../../../res/icons/icons.qrc">
+       <normaloff>:/icons/payment_icon</normaloff>:/icons/payment_icon</iconset>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>32</width>
+       <height>32</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QPushButton" name="button_certification">
+     <property name="text">
+      <string>Certification</string>
+     </property>
+     <property name="icon">
+      <iconset resource="../../../../../res/icons/icons.qrc">
+       <normaloff>:/icons/certification_icon</normaloff>:/icons/certification_icon</iconset>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>32</width>
+       <height>32</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QPushButton" name="button_membership">
+     <property name="text">
+      <string>Renew membership</string>
+     </property>
+     <property name="icon">
+      <iconset resource="../../../../../res/icons/icons.qrc">
+       <normaloff>:/icons/renew_membership</normaloff>:/icons/renew_membership</iconset>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>32</width>
+       <height>32</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>1000</width>
+       <height>221</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QToolButton" name="toolbutton_menu">
+     <property name="text">
+      <string/>
+     </property>
+     <property name="icon">
+      <iconset resource="../../../../../res/icons/icons.qrc">
+       <normaloff>:/icons/menu_icon</normaloff>:/icons/menu_icon</iconset>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>32</width>
+       <height>32</height>
+      </size>
+     </property>
+     <property name="popupMode">
+      <enum>QToolButton::InstantPopup</enum>
+     </property>
+     <property name="autoRaise">
+      <bool>false</bool>
+     </property>
+     <property name="arrowType">
+      <enum>Qt::NoArrow</enum>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../../../../../res/icons/icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/sakia/gui/main_window/toolbar/view.py b/src/sakia/gui/main_window/toolbar/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..aff49df9eaeec92d5644b6a5d35c78b9bf0aebba
--- /dev/null
+++ b/src/sakia/gui/main_window/toolbar/view.py
@@ -0,0 +1,56 @@
+from PyQt5.QtWidgets import QFrame, QAction, QMenu, QSizePolicy, QInputDialog, QDialog
+from sakia.gui.widgets.dialogs import dialog_async_exec
+from PyQt5.QtCore import QObject, QT_TRANSLATE_NOOP, Qt
+from .toolbar_uic import Ui_SakiaToolbar
+from .about_uic import Ui_AboutPopup
+
+
+class ToolbarView(QFrame, Ui_SakiaToolbar):
+    """
+    The model of Navigation component
+    """
+    _action_publish_uid_text = QT_TRANSLATE_NOOP("ToolbarView", "Publish UID")
+    _action_revoke_uid_text = QT_TRANSLATE_NOOP("ToolbarView", "Revoke UID")
+
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.setupUi(self)
+
+        tool_menu = QMenu(self.tr("Tools"), self.toolbutton_menu)
+        self.toolbutton_menu.setMenu(tool_menu)
+
+        self.action_revoke_uid = QAction(self.tr(ToolbarView._action_revoke_uid_text), self)
+        tool_menu.addAction(self.action_revoke_uid)
+
+        self.action_add_connection = QAction(self.tr("Add a connection"), tool_menu)
+        tool_menu.addAction(self.action_add_connection)
+
+        self.action_parameters = QAction(self.tr("Settings"), tool_menu)
+        tool_menu.addAction(self.action_parameters)
+
+        self.action_about = QAction(self.tr("About"), tool_menu)
+        tool_menu.addAction(self.action_about)
+
+        self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Minimum)
+        self.setMaximumHeight(60)
+
+    async def ask_for_connection(self, connections):
+        connections_titles = [c.title() for c in connections]
+        input_dialog = QInputDialog()
+        input_dialog.setComboBoxItems(connections_titles)
+        input_dialog.setWindowTitle(self.tr("Membership"))
+        input_dialog.setLabelText(self.tr("Select a connection"))
+        await dialog_async_exec(input_dialog)
+        result = input_dialog.textValue()
+
+        if input_dialog.result() == QDialog.Accepted:
+            for c in connections:
+                if c.title() == result:
+                    return c
+
+    def show_about(self, text):
+        dialog = QDialog(self)
+        about_dialog = Ui_AboutPopup()
+        about_dialog.setupUi(dialog)
+        about_dialog.label.setText(text)
+        dialog.exec()
diff --git a/src/sakia/gui/main_window/view.py b/src/sakia/gui/main_window/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..024a392eabe5ec46580a1084c58aa25674210443
--- /dev/null
+++ b/src/sakia/gui/main_window/view.py
@@ -0,0 +1,13 @@
+from PyQt5.QtWidgets import QMainWindow
+from .mainwindow_uic import Ui_MainWindow
+
+
+class MainWindowView(QMainWindow, Ui_MainWindow):
+    """
+    The model of Navigation component
+    """
+
+    def __init__(self):
+        super().__init__(None)
+        self.setupUi(self)
+
diff --git a/src/sakia/gui/mainwindow.py b/src/sakia/gui/mainwindow.py
deleted file mode 100644
index 5d66b78e060f2d8ad39799d10ec09d94e7944b9a..0000000000000000000000000000000000000000
--- a/src/sakia/gui/mainwindow.py
+++ /dev/null
@@ -1,460 +0,0 @@
-"""
-Created on 1 févr. 2014
-
-@author: inso
-"""
-import aiohttp
-import logging
-import traceback
-
-from PyQt5.QtWidgets import QMainWindow, QAction, QFileDialog, \
-    QMessageBox, QLabel, QComboBox, QDialog, QApplication, QErrorMessage
-from PyQt5.QtCore import QLocale, QEvent, \
-    pyqtSlot, QDateTime, QTimer, Qt, QObject, QUrl
-from PyQt5.QtGui import QIcon
-
-from ..gen_resources.mainwindow_uic import Ui_MainWindow
-from ..gen_resources.about_uic import Ui_AboutPopup
-from .process_cfg_account import ProcessConfigureAccount
-from .transfer import TransferMoneyDialog
-from .community_view import CommunityWidget
-from .contact import ConfigureContactDialog
-from .import_account import ImportAccountDialog
-from .certification import CertificationDialog
-from .revocation import RevocationDialog
-from .password_asker import PasswordAskerDialog
-from .preferences import PreferencesDialog
-from .process_cfg_community import ProcessConfigureCommunity
-from .homescreen import HomeScreenWidget
-from .node_manager import NodeManager
-from ..core import money
-from ..core.community import Community
-from ..tools.decorators import asyncify
-from ..__init__ import __version__
-from .widgets import toast
-from .widgets.dialogs import QAsyncMessageBox
-
-
-class MainWindow(QObject):
-    """
-    classdocs
-    """
-
-    def __init__(self, app, account, homescreen, community_view, node_manager,
-                 widget, ui,
-                 label_icon, label_status, label_time, combo_referential,
-                 password_asker):
-        """
-        Init
-        :param sakia.core.app.Application app: application
-        :param sakia.core.Account account: the account
-        :param HomeScreenWidgetcreen homescreen: the homescreen
-        :param CommunityView community_view: the community view
-        :param NodeManager node_manager: the local node manager dialog
-        :param QMainWindow widget: the widget of the main window
-        :param Ui_MainWindow ui: the ui of the widget
-        :param QLabel label_icon: the label of the icon in the statusbar
-        :param QLabel label_status: the label of the status in the statusbar
-        :param QLabel label_time: the label of the time in the statusbar
-        :param QCombobox combo_referential: the combo of the referentials in the statusbar
-        :param PasswordAsker password_asker: the password asker of the application
-        :type: sakia.core.app.Application
-        """
-        # Set up the user interface from Designer.
-        super().__init__()
-        self.app = app
-        self.account = account
-        self.initialized = False
-        self.password_asker = password_asker
-        self.import_dialog = None
-        self.widget = widget
-        self.ui = ui
-        self.ui.setupUi(self.widget)
-        self.widget.installEventFilter(self)
-
-        QApplication.setWindowIcon(QIcon(":/icons/sakia_logo"))
-
-        self.label_icon = label_icon
-
-        self.status_label = label_status
-        self.status_label.setTextFormat(Qt.RichText)
-
-        self.label_time = label_time
-
-        self.combo_referential = combo_referential
-        self.combo_referential.setEnabled(False)
-        self.combo_referential.currentIndexChanged[int].connect(self.referential_changed)
-
-        self.homescreen = homescreen
-
-        self.community_view = community_view
-
-        self.node_manager = node_manager
-
-
-    def _init_ui(self):
-        """
-        Connects elements of the UI to the local slots
-        """
-        self.ui.statusbar.addPermanentWidget(self.label_icon, 1)
-        self.ui.statusbar.addPermanentWidget(self.status_label, 2)
-        self.ui.statusbar.addPermanentWidget(self.label_time)
-        self.ui.statusbar.addPermanentWidget(self.combo_referential)
-
-        self.ui.action_add_account.triggered.connect(self.open_add_account_dialog)
-        self.ui.action_quit.triggered.connect(self.widget.close)
-        self.ui.actionTransfer_money.triggered.connect(self.open_transfer_money_dialog)
-        self.ui.action_add_a_contact.triggered.connect(self.open_add_contact_dialog)
-        self.ui.action_configure_parameters.triggered.connect(self.open_configure_account_dialog)
-        self.ui.action_import.triggered.connect(self.import_account)
-        self.ui.action_export.triggered.connect(self.export_account)
-        self.ui.actionCertification.triggered.connect(self.open_certification_dialog)
-        self.ui.actionPreferences.triggered.connect(self.open_preferences_dialog)
-        self.ui.actionAbout.triggered.connect(self.open_about_popup)
-        self.ui.action_revoke_identity.triggered.connect(self.open_revocation_dialog)
-
-        self.ui.actionManage_local_node.triggered.connect(self.open_duniter_ui)
-        self.ui.menu_duniter.setDisabled(True)
-
-    def _init_homescreen(self):
-        """
-        Initialize homescreen signals/slots and data
-        :return:
-        """
-        self.homescreen.status_label = self.status_label
-        self.homescreen.frame_communities.community_tile_clicked.connect(self.change_community)
-        self.homescreen.toolbutton_new_account.clicked.connect(self.open_add_account_dialog)
-        self.homescreen.toolbutton_new_account.addAction(self.ui.action_add_account)
-        self.homescreen.toolbutton_new_account.addAction(self.ui.action_import)
-        self.homescreen.button_add_community.clicked.connect(self.action_open_add_community)
-        self.homescreen.button_disconnect.clicked.connect(lambda :self.action_change_account(""))
-        self.widget.centralWidget().layout().addWidget(self.homescreen)
-        self.homescreen.toolbutton_connect.setMenu(self.ui.menu_change_account)
-
-    def _init_community_view(self):
-        """
-        Initialize the community view signals/slots and data
-        :return:
-        """
-        self.community_view.status_label = self.status_label
-        self.community_view.label_icon = self.label_icon
-        self.community_view.button_home.clicked.connect(lambda: self.change_community(None))
-        self.community_view.button_certification.clicked.connect(self.open_certification_dialog)
-        self.community_view.button_send_money.clicked.connect(self.open_transfer_money_dialog)
-        self.widget.centralWidget().layout().addWidget(self.community_view)
-
-    @classmethod
-    def startup(cls, app):
-        qmainwindow = QMainWindow()
-
-        main_window = cls(app, None, HomeScreenWidget(app, None),
-                          CommunityWidget(app, None, None),
-                          None, #NodeManager.create(qmainwindow),
-                          qmainwindow, Ui_MainWindow(),
-                          QLabel("", qmainwindow), QLabel("", qmainwindow),
-                          QLabel("", qmainwindow), QComboBox(qmainwindow),
-                          PasswordAskerDialog(None))
-        app.version_requested.connect(main_window.latest_version_requested)
-        app.account_imported.connect(main_window.import_account_accepted)
-        app.account_changed.connect(main_window.change_account)
-        main_window._init_ui()
-        main_window._init_homescreen()
-        main_window._init_community_view()
-
-        main_window.update_time()
-        if app.preferences['maximized']:
-            main_window.widget.showMaximized()
-        else:
-            main_window.widget.show()
-        if app.current_account:
-            main_window.password_asker = PasswordAskerDialog(app.current_account)
-            main_window.community_view.change_account(app.current_account, main_window.password_asker)
-            main_window.app.current_account.contacts_changed.connect(main_window.refresh_contacts)
-        main_window.refresh()
-        return main_window
-
-    def change_account(self):
-        if self.account:
-            self.account.contacts_changed.disconnect(self.refresh_contacts)
-        self.account = self.app.current_account
-        self.password_asker.change_account(self.account)
-        self.community_view.change_account(self.account, self.password_asker)
-        if self.account:
-            self.account.contacts_changed.connect(self.refresh_contacts)
-        self.refresh()
-
-    @asyncify
-    async def open_add_account_dialog(self, checked=False):
-        dialog = ProcessConfigureAccount(self.app, None)
-        result = await dialog.async_exec()
-        if result == QDialog.Accepted:
-            self.action_change_account(self.account.name)
-
-    @asyncify
-    async def open_configure_account_dialog(self, checked=False):
-        dialog = ProcessConfigureAccount(self.app, self.account)
-        result = await dialog.async_exec()
-        if result == QDialog.Accepted:
-            if self.account:
-                self.action_change_account(self.account.name)
-            else:
-                self.refresh()
-
-    @pyqtSlot(str)
-    def display_error(self, error):
-        QMessageBox.critical(self, ":(",
-                             error,
-                             QMessageBox.Ok)
-
-    @pyqtSlot(int)
-    def referential_changed(self, index):
-        if self.account:
-            self.account.set_display_referential(index)
-            if self.community_view:
-                self.community_view.referential_changed()
-                self.homescreen.referential_changed()
-
-    @pyqtSlot()
-    def update_time(self):
-        dateTime = QDateTime.currentDateTime()
-        self.label_time.setText("{0}".format(QLocale.toString(
-                        QLocale(),
-                        QDateTime.currentDateTime(),
-                        QLocale.dateTimeFormat(QLocale(), QLocale.NarrowFormat)
-                    )))
-        timer = QTimer()
-        timer.timeout.connect(self.update_time)
-        timer.start(1000)
-
-    @pyqtSlot()
-    def delete_contact(self):
-        contact = self.sender().data()
-        self.account.remove_contact(contact)
-
-    @pyqtSlot()
-    def edit_contact(self):
-        index = self.sender().data()
-        dialog = ConfigureContactDialog.edit_contact(self.app, self.account, self.widget, index)
-        dialog.exec_()
-
-    def action_change_account(self, account_name):
-        self.app.change_current_account(self.app.get_account(account_name))
-
-    @pyqtSlot()
-    def action_open_add_community(self):
-        dialog = ProcessConfigureCommunity(self.app,
-                                           self.account, None,
-                                           self.password_asker)
-        if dialog.exec_() == QDialog.Accepted:
-            self.app.save(self.account)
-            dialog.community.start_coroutines()
-            self.homescreen.refresh()
-
-    def open_transfer_money_dialog(self):
-        dialog = TransferMoneyDialog(self.app,
-                                     self.account,
-                                     self.password_asker,
-                                     self.community_view.community,
-                                     None)
-        if dialog.exec() == QDialog.Accepted:
-            self.community_view.tab_history.table_history.model().sourceModel().refresh_transfers()
-
-    def open_certification_dialog(self):
-        CertificationDialog.open_dialog(self.app,
-                                     self.account,
-                                     self.community_view.community,
-                                     self.password_asker)
-
-    def open_revocation_dialog(self):
-        RevocationDialog.open_dialog(self.app,
-                                     self.account)
-
-    def open_add_contact_dialog(self):
-        dialog = ConfigureContactDialog.new_contact(self.app, self.account, self.widget)
-        dialog.exec_()
-
-    def open_preferences_dialog(self):
-        dialog = PreferencesDialog(self.app)
-        dialog.exec_()
-
-    def open_about_popup(self):
-        """
-        Open about popup window
-        """
-        aboutDialog = QDialog(self.widget)
-        aboutUi = Ui_AboutPopup()
-        aboutUi.setupUi(aboutDialog)
-
-        latest = self.app.available_version
-        version_info = ""
-        version_url = ""
-        if not latest[0]:
-            version_info = self.tr("Latest release : {version}") \
-                .format(version=latest[1])
-            version_url = latest[2]
-
-            new_version_text = """
-                <p><b>{version_info}</b></p>
-                <p><a href="{version_url}">{link_text}</a></p>
-                """.format(version_info=version_info,
-                            version_url=version_url,
-                            link_text=self.tr("Download link"))
-        else:
-            new_version_text = ""
-
-        text = self.tr("""
-        <h1>sakia</h1>
-
-        <p>Python/Qt duniter client</p>
-        <p><a href="https://github.com/duniter/sakia">https://github.com/duniter/sakia</a></p>
-
-        <p>Version : {:}</p>
-        {new_version_text}
-
-        <p>License : GPLv3</p>
-
-        <p><b>Authors</b></p>
-
-        <p>inso</p>
-        <p>vit</p>
-        <p>Moul</p>
-        <p>canercandan</p>
-        """).format(__version__, new_version_text=new_version_text)
-
-        aboutUi.label.setText(text)
-        aboutDialog.show()
-
-    @pyqtSlot()
-    def latest_version_requested(self):
-        latest = self.app.available_version
-        logging.debug("Latest version requested")
-        if not latest[0]:
-            version_info = self.tr("Please get the latest release {version}") \
-                .format(version=latest[1])
-            version_url = latest[2]
-
-            if self.app.preferences['notifications']:
-                toast.display("sakia", """{version_info}""".format(
-                version_info=version_info,
-                version_url=version_url))
-
-    @pyqtSlot(Community)
-    def change_community(self, community):
-        if community:
-            self.homescreen.hide()
-            self.community_view.show()
-        else:
-            self.community_view.hide()
-            self.homescreen.show()
-
-        self.community_view.change_community(community)
-
-    def refresh_accounts(self):
-        self.ui.menu_change_account.clear()
-        for account_name in sorted(self.app.accounts.keys()):
-            action = QAction(account_name, self.widget)
-            action.triggered.connect(lambda checked, account_name=account_name: self.action_change_account(account_name))
-            self.ui.menu_change_account.addAction(action)
-
-    def refresh_contacts(self):
-        self.ui.menu_contacts_list.clear()
-        if self.account:
-            for index, contact in enumerate(self.account.contacts):
-                contact_menu = self.ui.menu_contacts_list.addMenu(contact['name'])
-                edit_action = contact_menu.addAction(self.tr("Edit"))
-                edit_action.triggered.connect(self.edit_contact)
-                edit_action.setData(index)
-                delete_action = contact_menu.addAction(self.tr("Delete"))
-                delete_action.setData(contact)
-                delete_action.triggered.connect(self.delete_contact)
-
-    @asyncify
-    async def open_duniter_ui(self, checked=False):
-        if not self.node_manager.widget.isVisible():
-            self.node_manager.open_home_page()
-
-    def refresh(self):
-        """
-        Refresh main window
-        When the selected account changes, all the widgets
-        in the window have to be refreshed
-        """
-        logging.debug("Refresh started")
-        self.refresh_accounts()
-        self.community_view.hide()
-        self.homescreen.show()
-        self.homescreen.refresh()
-
-        if self.account is None:
-            self.widget.setWindowTitle(self.tr("sakia {0}").format(__version__))
-            self.ui.action_add_a_contact.setEnabled(False)
-            self.ui.actionCertification.setEnabled(False)
-            self.ui.actionTransfer_money.setEnabled(False)
-            self.ui.action_configure_parameters.setEnabled(False)
-            self.ui.action_set_as_default.setEnabled(False)
-            self.ui.menu_contacts_list.setEnabled(False)
-            self.combo_referential.setEnabled(False)
-            self.status_label.setText(self.tr(""))
-        else:
-            self.combo_referential.blockSignals(True)
-            self.combo_referential.clear()
-            for ref in money.Referentials:
-                self.combo_referential.addItem(ref.translated_name())
-            logging.debug(self.app.preferences)
-
-            self.combo_referential.setEnabled(True)
-            self.combo_referential.blockSignals(False)
-            self.combo_referential.setCurrentIndex(self.app.preferences['ref'])
-            self.ui.action_add_a_contact.setEnabled(True)
-            self.ui.actionCertification.setEnabled(True)
-            self.ui.actionTransfer_money.setEnabled(True)
-            self.ui.menu_contacts_list.setEnabled(True)
-            self.ui.action_configure_parameters.setEnabled(True)
-            self.widget.setWindowTitle(self.tr("sakia {0} - Account : {1}").format(__version__,
-                                                                               self.account.name))
-
-        self.refresh_contacts()
-
-    def import_account(self):
-        import_dialog = ImportAccountDialog(self.app, self.widget)
-        import_dialog.exec_()
-
-    def import_account_accepted(self, account_name):
-        # open account after import
-        self.action_change_account(account_name)
-
-    def export_account(self):
-        # Testable way of using a QFileDialog
-        export_dialog = QFileDialog(self.widget)
-        export_dialog.setObjectName('ExportFileDialog')
-        export_dialog.setWindowTitle(self.tr("Export an account"))
-        export_dialog.setNameFilter(self.tr("All account files (*.acc)"))
-        export_dialog.setLabelText(QFileDialog.Accept, self.tr('Export'))
-        export_dialog.setOption(QFileDialog.DontUseNativeDialog, True)
-        export_dialog.accepted.connect(self.export_account_accepted)
-        export_dialog.show()
-
-    def export_account_accepted(self):
-        export_dialog = self.sender()
-        selected_file = export_dialog.selectedFiles()
-        if selected_file:
-            if selected_file[0][-4:] == ".acc":
-                path = selected_file[0]
-            else:
-                path = selected_file[0] + ".acc"
-            self.app.export_account(path, self.account)
-
-    def eventFilter(self, target, event):
-        """
-        Event filter on the widget
-        :param QObject target: the target of the event
-        :param QEvent event: the event
-        :return: bool
-        """
-        if target == self.widget:
-            if event.type() == QEvent.LanguageChange:
-                self.ui.retranslateUi(self)
-                self.refresh()
-            return self.widget.eventFilter(target, event)
-        return False
-
diff --git a/src/sakia/gui/member.py b/src/sakia/gui/member.py
deleted file mode 100644
index 5d2744dea1d3e3e9953e514727da816f423814bd..0000000000000000000000000000000000000000
--- a/src/sakia/gui/member.py
+++ /dev/null
@@ -1,155 +0,0 @@
-import datetime
-
-from PyQt5.QtCore import QObject, QEvent, QLocale, QDateTime
-from PyQt5.QtWidgets import QDialog, QWidget
-
-from ..core.graph import WoTGraph
-from .widgets.busy import Busy
-from ..tools.decorators import asyncify
-from ..gen_resources.member_uic import Ui_MemberView
-from ..tools.exceptions import MembershipNotFoundError, LookupFailureError, NoPeerAvailable
-from ..core.registry import LocalState
-
-
-class MemberDialog(QObject):
-    """
-    A widget showing informations about a member
-    """
-
-    def __init__(self, app, account, community, identity, widget, ui):
-        """
-        Init MemberDialog
-
-        :param sakia.core.app.Application app:   Application instance
-        :param sakia.core.account.Account account:   Account instance
-        :param sakia.core.community.Community community: Community instance
-        :param sakia.core.registry.identity.Identity identity: Identity instance
-        :param PyQt5.QtWidget widget: The class of the widget
-        :param sakia.gen_resources.member_uic.Ui_DialogMember ui: the class of the ui applyed to the widget
-        :return:
-        """
-        super().__init__()
-        self.widget = widget
-        self.ui = ui
-        self.ui.setupUi(self.widget)
-        self.ui.busy = Busy(self.widget)
-        self.widget.installEventFilter(self)
-        self.app = app
-        self.community = community
-        self.account = account
-        self.identity = identity
-
-    @classmethod
-    def open_dialog(cls, app, account, community, identity):
-        dialog = cls(app, account, community, identity, QDialog(), Ui_MemberView())
-        dialog.refresh()
-        dialog.refresh_path()
-        dialog.exec()
-
-    @classmethod
-    def as_widget(cls, parent_widget, app, account, community, identity):
-        return cls(app, account, community, identity, QWidget(parent_widget), Ui_MemberView())
-
-    def change_community(self, community):
-        """
-        Change current community
-        :param sakia.core.Community community: the new community
-        """
-        self.community = community
-        self.refresh()
-
-    @asyncify
-    async def refresh(self):
-        if self.identity and self.identity.local_state != LocalState.NOT_FOUND:
-            self.ui.busy.show()
-            self.ui.label_uid.setText(self.identity.uid)
-            self.ui.label_properties.setText("")
-            try:
-                identity_selfcert = await self.identity.selfcert(self.community)
-                publish_time = await self.community.time(identity_selfcert.timestamp.number)
-
-                join_date = await self.identity.get_join_date(self.community)
-                if join_date is None:
-                    join_date = self.tr('not a member')
-                else:
-                    join_date = datetime.datetime.fromtimestamp(join_date).strftime("%d/%m/%Y %I:%M")
-
-            except MembershipNotFoundError:
-                join_date = "###"
-            except (LookupFailureError, NoPeerAvailable):
-                publish_time = None
-                join_date = "###"
-
-            if publish_time:
-                uid_publish_date = QLocale.toString(
-                        QLocale(),
-                        QDateTime.fromTime_t(publish_time),
-                        QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
-                    )
-            else:
-                uid_publish_date = "###"
-
-            text = self.tr("""
-                <table cellpadding="5">
-                <tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>
-                <tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>
-                """).format(
-                self.tr('Public key'),
-                self.identity.pubkey,
-                self.tr('UID Published on'),
-                uid_publish_date,
-                self.tr('Join date'),
-                join_date
-            )
-            # close html text
-            text += "</table>"
-
-            # set text in label
-            self.ui.label_properties.setText(text)
-            self.ui.busy.hide()
-
-    @asyncify
-    async def refresh_path(self):
-        text = ""
-        self.ui.label_path.setText("")
-        # calculate path to account member
-        graph = WoTGraph(self.app, self.community)
-        path = None
-        # if selected member is not the account member...
-        if self.identity.pubkey != self.account.pubkey:
-            # add path from us to him
-            account_identity = await self.account.identity(self.community)
-            path = await graph.get_shortest_path_to_identity(self.identity,
-                                                            account_identity)
-            nodes = graph.nx_graph.nodes(data=True)
-
-        if path:
-            distance = len(path) - 1
-            text += self.tr(
-                """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>"""
-                    .format(self.tr('Distance'), distance))
-            if distance > 1:
-                index = 0
-                for node_id in path:
-                    node = [n for n in nodes if n[0] == node_id][0]
-                    if index == 0:
-                        text += self.tr("""<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""")\
-                            .format(self.tr('Path'), node[1]['text'])
-                    else:
-                        text += self.tr("""<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""")\
-                            .format('', node[1]['text'] )
-                    if index == distance and node_id != self.account.pubkey:
-                        text += self.tr("""<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""")\
-                            .format('', self.account.name)
-                    index += 1
-        self.ui.label_path.setText(text)
-
-    def eventFilter(self, source, event):
-        if event.type() == QEvent.Resize:
-            self.ui.busy.resize(event.size())
-            self.widget.resizeEvent(event)
-        return self.widget.eventFilter(source, event)
-
-    def exec(self):
-        self.widget.exec()
diff --git a/src/sakia/tests/functional/preferences/__init__.py b/src/sakia/gui/navigation/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/preferences/__init__.py
rename to src/sakia/gui/navigation/__init__.py
diff --git a/src/sakia/gui/navigation/controller.py b/src/sakia/gui/navigation/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2d21ba33b97028eb4b4ce69185f037f944d052c
--- /dev/null
+++ b/src/sakia/gui/navigation/controller.py
@@ -0,0 +1,214 @@
+from .model import NavigationModel
+from .view import NavigationView
+from sakia.models.generic_tree import GenericTreeModel
+from .txhistory.controller import TxHistoryController
+from .homescreen.controller import HomeScreenController
+from .network.controller import NetworkController
+from .identities.controller import IdentitiesController
+from .informations.controller import InformationsController
+from .graphs.wot.controller import WotController
+from sakia.data.entities import Connection
+from PyQt5.QtCore import pyqtSignal, QObject, Qt
+from PyQt5.QtWidgets import QMenu, QAction, QMessageBox, QDialog, QFileDialog
+from PyQt5.QtGui import QCursor
+from sakia.decorators import asyncify
+from sakia.gui.password_asker import PasswordAskerDialog
+from sakia.gui.widgets.dialogs import QAsyncMessageBox
+from sakia.gui.widgets import toast
+
+
+class NavigationController(QObject):
+    """
+    The navigation panel
+    """
+    currency_changed = pyqtSignal(str)
+    connection_changed = pyqtSignal(Connection)
+
+    def __init__(self, parent, view, model):
+        """
+        Constructor of the navigation component
+
+        :param sakia.gui.navigation.view.NavigationView view: the view
+        :param sakia.gui.navigation.model.NavigationModel model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+        self.components = {
+            'TxHistory': TxHistoryController,
+            'HomeScreen': HomeScreenController,
+            'Network': NetworkController,
+            'Identities': IdentitiesController,
+            'Informations': InformationsController,
+            'Wot': WotController
+        }
+        self.view.current_view_changed.connect(self.handle_view_change)
+        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
+        self.view.customContextMenuRequested.connect(self.tree_context_menu)
+        self._components_controllers = []
+
+    @classmethod
+    def create(cls, parent, app):
+        """
+        Instanciate a navigation component
+        :param sakia.app.Application app: the application
+        :return: a new Navigation controller
+        :rtype: NavigationController
+        """
+        view = NavigationView(None)
+        model = NavigationModel(None, app)
+        navigation = cls(parent, view, model)
+        model.setParent(navigation)
+        navigation.init_navigation()
+        app.new_connection.connect(navigation.add_connection)
+        return navigation
+
+    def parse_node(self, node_data):
+        if 'component' in node_data:
+            component_class = self.components[node_data['component']]
+            component = component_class.create(self, self.model.app, **node_data['dependencies'])
+            self._components_controllers.append(component)
+            widget = self.view.add_widget(component.view)
+            node_data['widget'] = widget
+        if 'children' in node_data:
+            for child in node_data['children']:
+                self.parse_node(child)
+
+    def init_navigation(self):
+        self.model.init_navigation_data()
+
+        for node in self.model.navigation:
+            self.parse_node(node)
+
+        self.view.set_model(self.model)
+
+    def handle_view_change(self, raw_data):
+        """
+        Handle view change
+        :param dict raw_data:
+        :return:
+        """
+        user_identity = raw_data.get('user_identity', None)
+        currency = raw_data.get('currency', None)
+        if user_identity != self.model.current_data('user_identity'):
+            self.account_changed.emit(user_identity)
+        if currency != self.model.current_data('currency'):
+            self.currency_changed.emit(currency)
+        self.model.set_current_data(raw_data)
+
+    def add_connection(self, connection):
+        raw_node = self.model.add_connection(connection)
+        self.view.add_connection(raw_node)
+        self.parse_node(raw_node)
+
+    def tree_context_menu(self, point):
+        mapped = self.view.tree_view.mapFromParent(point)
+        index = self.view.tree_view.indexAt(mapped)
+        raw_data = self.view.tree_view.model().data(index, GenericTreeModel.ROLE_RAW_DATA)
+        if raw_data and raw_data["component"] == "Informations":
+            menu = QMenu(self.view)
+            if raw_data['misc']['connection'].uid:
+                action_gen_revokation = QAction(self.tr("Save revokation document"), menu)
+                menu.addAction(action_gen_revokation)
+                action_gen_revokation.triggered.connect(lambda c:
+                                                        self.action_save_revokation(raw_data['misc']['connection']))
+
+                action_publish_uid = QAction(self.tr("Publish UID"), menu)
+                menu.addAction(action_publish_uid)
+                action_publish_uid.triggered.connect(lambda c:
+                                                        self.publish_uid(raw_data['misc']['connection']))
+                action_publish_uid.setEnabled(self.model.identity_published(raw_data['misc']['connection']))
+
+                action_leave = QAction(self.tr("Leave the currency"), menu)
+                menu.addAction(action_leave)
+                action_leave.triggered.connect(lambda c: self.send_leave(raw_data['misc']['connection']))
+                action_leave.setEnabled(self.model.identity_is_member(raw_data['misc']['connection']))
+
+            copy_pubkey = QAction(menu.tr("Copy pubkey to clipboard"), menu.parent())
+            copy_pubkey.triggered.connect(lambda checked,
+                                                 c=raw_data['misc']['connection']: \
+                                              NavigationModel.copy_pubkey_to_clipboard(c))
+            menu.addAction(copy_pubkey)
+
+            action_remove = QAction(self.tr("Remove the connection"), menu)
+            menu.addAction(action_remove)
+            action_remove.triggered.connect(lambda c: self.remove_connection(raw_data['misc']['connection']))
+            # Show the context menu.
+
+            menu.popup(QCursor.pos())
+
+    @asyncify
+    async def publish_uid(self, connection):
+        password = await self.password_asker.async_exec()
+        if self.password_asker.result() == QDialog.Rejected:
+            return
+        result = await self.account.send_selfcert(password, self.community)
+        if result[0]:
+            if self.app.preferences['notifications']:
+                toast.display(self.tr("UID"), self.tr("Success publishing your UID"))
+            else:
+                await QAsyncMessageBox.information(self, self.tr("Membership"),
+                                                        self.tr("Success publishing your UID"))
+        else:
+            if self.app.preferences['notifications']:
+                toast.display(self.tr("UID"), result[1])
+            else:
+                await QAsyncMessageBox.critical(self, self.tr("UID"),
+                                                        result[1])
+
+    @asyncify
+    async def send_leave(self):
+        reply = await QAsyncMessageBox.warning(self, self.tr("Warning"),
+                                               self.tr("""Are you sure ?
+Sending a leaving demand  cannot be canceled.
+The process to join back the community later will have to be done again.""")
+                                               .format(self.account.pubkey), QMessageBox.Ok | QMessageBox.Cancel)
+        if reply == QMessageBox.Ok:
+            password = PasswordAskerDialog(self.model.navigation_model.navigation.current_connection()).async_exec()
+            if not password:
+                return
+            result = await self.model.send_leave(password)
+            if result[0]:
+                if self.app.preferences['notifications']:
+                    toast.display(self.tr("Revoke"), self.tr("Success sending Revoke demand"))
+                else:
+                    await QAsyncMessageBox.information(self, self.tr("Revoke"),
+                                                       self.tr("Success sending Revoke demand"))
+            else:
+                if self.app.preferences['notifications']:
+                    toast.display(self.tr("Revoke"), result[1])
+                else:
+                    await QAsyncMessageBox.critical(self, self.tr("Revoke"),
+                                                    result[1])
+
+    @asyncify
+    async def remove_connection(self, connection):
+        reply = await QAsyncMessageBox.question(self.view, self.tr("Removing the connection"),
+                                                self.tr("""Are you sure ? This won't remove your money"
+neither your identity from the network."""), QMessageBox.Ok | QMessageBox.Cancel)
+        if reply == QMessageBox.Ok:
+            await self.model.remove_connection(connection)
+            self.init_navigation()
+
+    def action_save_revokation(self, connection):
+        password = PasswordAskerDialog(connection).exec()
+        if not password:
+            return
+
+        raw_document = self.model.generate_revokation(connection, password)
+        # Testable way of using a QFileDialog
+        selected_files = QFileDialog.getSaveFileName(self.view, self.tr("Save a revokation document"),
+                                                       "", self.tr("All text files (*.txt)"))
+        if selected_files:
+            path = selected_files[0]
+            if not path.endswith('.txt'):
+                path = "{0}.txt".format(path)
+            with open(path, 'w') as save_file:
+                save_file.write(raw_document)
+
+        dialog = QMessageBox(QMessageBox.Information, self.tr("Revokation file"),
+                             self.tr("""<div>Your revokation document has been saved.</div>
+<div><b>Please keep it in a safe place.</b></div>
+The publication of this document will remove your identity from the network.</p>"""), QMessageBox.Ok)
+        dialog.setTextFormat(Qt.RichText)
+        dialog.exec()
diff --git a/src/sakia/tests/functional/process_cfg_account/__init__.py b/src/sakia/gui/navigation/graphs/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/process_cfg_account/__init__.py
rename to src/sakia/gui/navigation/graphs/__init__.py
diff --git a/src/sakia/tests/functional/process_cfg_community/__init__.py b/src/sakia/gui/navigation/graphs/base/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/process_cfg_community/__init__.py
rename to src/sakia/gui/navigation/graphs/base/__init__.py
diff --git a/src/sakia/gui/navigation/graphs/base/controller.py b/src/sakia/gui/navigation/graphs/base/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1af19acbd1f9485bd71ac5a975fb3a8d638bfc0
--- /dev/null
+++ b/src/sakia/gui/navigation/graphs/base/controller.py
@@ -0,0 +1,74 @@
+import asyncio
+
+from PyQt5.QtCore import pyqtSlot, QObject
+from PyQt5.QtGui import QCursor
+
+from sakia.decorators import asyncify, once_at_a_time
+from sakia.gui.widgets.context_menu import ContextMenu
+
+
+class BaseGraphController(QObject):
+    """
+    The homescreen view
+    """
+
+    def __init__(self, parent, view, model, password_asker):
+        """
+        Constructor of the homescreen component
+
+        :param sakia.gui.homescreen.view.HomeScreenView: the view
+        :param sakia.gui.homescreen.model.HomeScreenModel model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+        self.password_asker = password_asker
+
+    def set_scene(self, scene):
+        """
+        Set the scene and connects the signals
+        :param sakia.gui.views.scenes.base_scene.BaseScene scene: the scene
+        :return:
+        """
+        # add scene events
+        scene.node_context_menu_requested.connect(self.node_context_menu)
+        scene.node_clicked.connect(self.handle_node_click)
+
+    @pyqtSlot(str, dict)
+    def handle_node_click(self, pubkey, metadata):
+        asyncio.ensure_future(self.draw_graph(metadata['identity']))
+
+    async def draw_graph(self, identity):
+        """
+        Draw community graph centered on the identity
+
+        :param sakia.core.registry.Identity identity: Graph node identity
+        """
+        raise NotImplementedError("draw_graph not implemented")
+
+    @once_at_a_time
+    @asyncify
+    async def reset(self, checked=False):
+        """
+        Reset graph scene to wallet identity
+        """
+        raise NotImplementedError("reset not implemented")
+
+    @once_at_a_time
+    @asyncify
+    def refresh(self):
+        """
+        Refresh graph scene to current metadata
+        """
+        raise NotImplementedError("refresh not implemented")
+
+    def node_context_menu(self, identity):
+        """
+        Open the node context menu
+        :param sakia.data.entities.Identity identity: the identity of the node to open
+        """
+        menu = ContextMenu.from_data(self.view, self.model.app, self.model.connection, (identity,))
+        menu.view_identity_in_wot.connect(self.draw_graph)
+
+        # Show the context menu.
+        menu.qmenu.popup(QCursor.pos())
diff --git a/src/sakia/gui/views/edges/base_edge.py b/src/sakia/gui/navigation/graphs/base/edge.py
similarity index 100%
rename from src/sakia/gui/views/edges/base_edge.py
rename to src/sakia/gui/navigation/graphs/base/edge.py
diff --git a/src/sakia/gui/navigation/graphs/base/model.py b/src/sakia/gui/navigation/graphs/base/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..12771e47fd8bdae35bc5b993a4a66d0638f699b8
--- /dev/null
+++ b/src/sakia/gui/navigation/graphs/base/model.py
@@ -0,0 +1,30 @@
+from PyQt5.QtCore import QObject
+
+class BaseGraphModel(QObject):
+    """
+    The model of Navigation component
+    """
+
+    def __init__(self, parent, app, connection, blockchain_service, identities_service):
+        """
+        Constructor of a model of WoT component
+
+        :param sakia.gui.identities.controller.IdentitiesController parent: the controller
+        :param sakia.app.Application app: the app
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        :param sakia.services.IdentitiesService identities_service: the identities service
+        """
+        super().__init__(parent)
+        self.app = app
+        self.connection = connection
+        self.blockchain_service = blockchain_service
+        self.identities_service = identities_service
+
+    def get_identity(self, pubkey):
+        """
+        Get identity from pubkey
+        :param str pubkey: Identity pubkey
+        :rtype: sakia.core.registry.Identity
+        """
+        return self.identities_service.get_identity(pubkey, self.connection.currency)
diff --git a/src/sakia/gui/views/nodes/base_node.py b/src/sakia/gui/navigation/graphs/base/node.py
similarity index 94%
rename from src/sakia/gui/views/nodes/base_node.py
rename to src/sakia/gui/navigation/graphs/base/node.py
index 8c1d3c39a2bfd181664c5cc38692b2d366b5382e..f07802526c77144ced6941477173af888b6590c5 100644
--- a/src/sakia/gui/views/nodes/base_node.py
+++ b/src/sakia/gui/navigation/graphs/base/node.py
@@ -1,8 +1,9 @@
-from PyQt5.QtWidgets import QGraphicsEllipseItem, QGraphicsSceneHoverEvent, \
-    QGraphicsSceneContextMenuEvent
 from PyQt5.QtCore import Qt
 from PyQt5.QtGui import QMouseEvent
-from ....core.graph.constants import NodeStatus
+from PyQt5.QtWidgets import QGraphicsEllipseItem, QGraphicsSceneHoverEvent, \
+    QGraphicsSceneContextMenuEvent
+
+from sakia.data.graphs.constants import NodeStatus
 
 
 class BaseNode(QGraphicsEllipseItem):
@@ -65,5 +66,5 @@ class BaseNode(QGraphicsEllipseItem):
 
         :param event: scene context menu event
         """
-        self.scene().node_context_menu_requested.emit(self.id)
+        self.scene().node_context_menu_requested.emit(self.metadata['identity'])
 
diff --git a/src/sakia/gui/views/scenes/base_scene.py b/src/sakia/gui/navigation/graphs/base/scene.py
similarity index 78%
rename from src/sakia/gui/views/scenes/base_scene.py
rename to src/sakia/gui/navigation/graphs/base/scene.py
index 36cd72880a4def8abc332cd47f0b5c06e68aaede..3c5a46e58bddacffc84eb12d74c0a3f54527154d 100644
--- a/src/sakia/gui/views/scenes/base_scene.py
+++ b/src/sakia/gui/navigation/graphs/base/scene.py
@@ -1,10 +1,11 @@
+from sakia.data.entities import Identity
 from PyQt5.QtCore import pyqtSignal
 from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneContextMenuEvent
 
 
 class BaseScene(QGraphicsScene):
     # This defines signals taking string arguments
-    node_context_menu_requested = pyqtSignal(str)
+    node_context_menu_requested = pyqtSignal(Identity)
     node_hovered = pyqtSignal(str)
     node_clicked = pyqtSignal(str, dict)
 
diff --git a/src/sakia/gui/navigation/graphs/base/view.py b/src/sakia/gui/navigation/graphs/base/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..4358c5e7c787dd617dc69170e6a8d9196f54ed1a
--- /dev/null
+++ b/src/sakia/gui/navigation/graphs/base/view.py
@@ -0,0 +1,25 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtCore import QEvent
+
+
+class BaseGraphView(QWidget):
+    """
+    Base graph view
+    """
+
+    def __init__(self, parent):
+        """
+        Constructor
+        """
+        super().__init__(parent)
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+            self.refresh()
+        return super().changeEvent(event)
\ No newline at end of file
diff --git a/src/sakia/tests/functional/transfer/__init__.py b/src/sakia/gui/navigation/graphs/wot/__init__.py
similarity index 100%
rename from src/sakia/tests/functional/transfer/__init__.py
rename to src/sakia/gui/navigation/graphs/wot/__init__.py
diff --git a/src/sakia/gui/navigation/graphs/wot/controller.py b/src/sakia/gui/navigation/graphs/wot/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7e7b11f4ca6df713895152888865a3c6618c4ba
--- /dev/null
+++ b/src/sakia/gui/navigation/graphs/wot/controller.py
@@ -0,0 +1,70 @@
+import asyncio
+
+from sakia.decorators import asyncify, once_at_a_time
+from sakia.gui.sub.search_user.controller import SearchUserController
+from .model import WotModel
+from .view import WotView
+from ..base.controller import BaseGraphController
+
+
+class WotController(BaseGraphController):
+    """
+    The homescreen view
+    """
+
+    def __init__(self, parent, view, model, password_asker=None):
+        """
+        Constructor of the homescreen component
+
+        :param sakia.gui.homescreen.view.HomeScreenView: the view
+        :param sakia.gui.homescreen.model.HomeScreenModel model: the model
+        """
+        super().__init__(parent, view, model, password_asker)
+        self.set_scene(view.scene())
+        self.reset()
+
+    @classmethod
+    def create(cls, parent, app, connection, blockchain_service, identities_service):
+        view = WotView(parent.view)
+        model = WotModel(None, app, connection, blockchain_service, identities_service)
+        wot = cls(parent, view, model)
+        model.setParent(wot)
+        search_user = SearchUserController.create(wot, app, currency=connection.currency)
+        wot.view.set_search_user(search_user.view)
+        search_user.identity_selected.connect(wot.center_on_identity)
+        search_user.view.button_reset.clicked.connect(wot.reset)
+        return wot
+
+    def center_on_identity(self, identity):
+        """
+        Draw community graph centered on the identity
+
+        :param sakia.core.registry.Identity identity: Center identity
+        """
+        self.draw_graph(identity)
+
+    @once_at_a_time
+    @asyncify
+    async def draw_graph(self, identity):
+        """
+        Draw community graph centered on the identity
+
+        :param sakia.core.registry.Identity identity: Center identity
+        """
+        self.view.busy.show()
+        await self.model.set_identity(identity)
+        self.refresh()
+        self.view.busy.hide()
+
+    def refresh(self):
+        """
+        Refresh graph scene to current metadata
+        """
+        nx_graph = self.model.get_nx_graph()
+        self.view.display_wot(nx_graph, self.model.identity)
+
+    def reset(self, checked=False):
+        """
+        Reset graph scene to wallet identity
+        """
+        self.draw_graph(None)
diff --git a/src/sakia/gui/views/edges/wot_edge.py b/src/sakia/gui/navigation/graphs/wot/edge.py
similarity index 97%
rename from src/sakia/gui/views/edges/wot_edge.py
rename to src/sakia/gui/navigation/graphs/wot/edge.py
index 50fcf150c949e89e3f2d468606fdc16c63eb9da7..29053a06f7522e2b0a107f02d11ef3d64f4c384c 100644
--- a/src/sakia/gui/views/edges/wot_edge.py
+++ b/src/sakia/gui/navigation/graphs/wot/edge.py
@@ -1,9 +1,11 @@
+import math
+
 from PyQt5.QtCore import Qt, QRectF, QLineF, QPointF, QSizeF, \
                         qFuzzyCompare
 from PyQt5.QtGui import QColor, QPen, QPolygonF
-import math
-from .base_edge import BaseEdge
-from ....core.graph.constants import EdgeStatus
+
+from sakia.data.graphs.constants import EdgeStatus
+from ..base.edge import BaseEdge
 
 
 class WotEdge(BaseEdge):
diff --git a/src/sakia/gui/views/wot.py b/src/sakia/gui/navigation/graphs/wot/graphics_view.py
similarity index 95%
rename from src/sakia/gui/views/wot.py
rename to src/sakia/gui/navigation/graphs/wot/graphics_view.py
index 056962d84469f4000daec002861fac40f9066ad9..95aaae1bde9224aad4539dc1b6d311de9bc1a0c7 100644
--- a/src/sakia/gui/views/wot.py
+++ b/src/sakia/gui/navigation/graphs/wot/graphics_view.py
@@ -1,10 +1,10 @@
 from PyQt5.QtCore import Qt
 from PyQt5.QtGui import QPainter, QWheelEvent
 from PyQt5.QtWidgets import QGraphicsView
-from .scenes import WotScene
+from .scene import WotScene
 
 
-class WotView(QGraphicsView):
+class WotGraphicsView(QGraphicsView):
     def __init__(self, parent=None):
         """
         Create View to display scene
diff --git a/src/sakia/gui/navigation/graphs/wot/model.py b/src/sakia/gui/navigation/graphs/wot/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..80ea9038d469e204c0e03bf8465c14c440a041b6
--- /dev/null
+++ b/src/sakia/gui/navigation/graphs/wot/model.py
@@ -0,0 +1,50 @@
+from sakia.data.graphs import WoTGraph
+from ..base.model import BaseGraphModel
+
+
+class WotModel(BaseGraphModel):
+    """
+    The model of Navigation component
+    """
+
+    def __init__(self, parent, app, connection, blockchain_service, identities_service):
+        """
+        Constructor of a model of WoT component
+
+        :param sakia.gui.identities.controller.IdentitiesController parent: the controller
+        :param sakia.app.Application app: the app
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        :param sakia.services.IdentitiesService identities_service: the identities service
+        """
+        super().__init__(parent, app, connection, blockchain_service, identities_service)
+        self.app = app
+        self.connection = connection
+        self.blockchain_service = blockchain_service
+        self.identities_service = identities_service
+        self.wot_graph = WoTGraph(self.app, self.blockchain_service, self.identities_service)
+        self.identity = None
+
+    async def set_identity(self, identity=None):
+        """
+        Change current identity
+        If identity is None, it defaults to account identity
+        :param sakia.core.registry.Identity identity: the new identity to show
+        :return:
+        """
+        connection_identity = self.identities_service.get_identity(self.connection.pubkey)
+        # create empty graph instance
+        if identity:
+            self.identity = identity
+            await self.wot_graph.initialize(self.identity, connection_identity)
+        else:
+            self.identity = connection_identity
+            # create empty graph instance
+            self.wot_graph.offline_init(connection_identity, connection_identity)
+
+    def get_nx_graph(self):
+        """
+        Get nx graph of current identity wot graph
+        :rtype: sakia.core.registry.Identity
+        """
+        return self.wot_graph.nx_graph
diff --git a/src/sakia/gui/views/nodes/wot_node.py b/src/sakia/gui/navigation/graphs/wot/node.py
similarity index 96%
rename from src/sakia/gui/views/nodes/wot_node.py
rename to src/sakia/gui/navigation/graphs/wot/node.py
index ef7ba7dc1e0c43b631b05c28d032dbf0228cd646..0d99b01eb9540b26cd70864637eb34a975bd7010 100644
--- a/src/sakia/gui/views/nodes/wot_node.py
+++ b/src/sakia/gui/navigation/graphs/wot/node.py
@@ -1,8 +1,9 @@
-from PyQt5.QtWidgets import QGraphicsSimpleTextItem
 from PyQt5.QtCore import Qt, QPointF
 from PyQt5.QtGui import QTransform, QColor, QPen, QBrush, QRadialGradient
-from ....core.graph.constants import NodeStatus
-from .base_node import BaseNode
+from PyQt5.QtWidgets import QGraphicsSimpleTextItem
+
+from sakia.data.graphs.constants import NodeStatus
+from ..base.node import BaseNode
 
 
 class WotNode(BaseNode):
diff --git a/src/sakia/gui/views/scenes/wot_scene.py b/src/sakia/gui/navigation/graphs/wot/scene.py
similarity index 98%
rename from src/sakia/gui/views/scenes/wot_scene.py
rename to src/sakia/gui/navigation/graphs/wot/scene.py
index 743a044b537cdee0acda72a0a1ffe3394aa5ead8..d8c67d4597faa3254d86a5b3b810104131627e28 100644
--- a/src/sakia/gui/views/scenes/wot_scene.py
+++ b/src/sakia/gui/navigation/graphs/wot/scene.py
@@ -2,10 +2,10 @@ import networkx
 from PyQt5.QtCore import QPoint, pyqtSignal
 from PyQt5.QtWidgets import QGraphicsScene
 
-from ..edges import WotEdge
-from ..nodes import WotNode
+from .edge import WotEdge
+from .node import WotNode
 
-from .base_scene import BaseScene
+from ..base.scene import BaseScene
 
 
 class WotScene(BaseScene):
diff --git a/src/sakia/gui/navigation/graphs/wot/view.py b/src/sakia/gui/navigation/graphs/wot/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..14c9fa19efcc51e6c0831b6c53cd5309196c41f9
--- /dev/null
+++ b/src/sakia/gui/navigation/graphs/wot/view.py
@@ -0,0 +1,48 @@
+from ..base.view import BaseGraphView
+from .wot_tab_uic import Ui_WotWidget
+from sakia.gui.widgets.busy import Busy
+from PyQt5.QtCore import QEvent
+
+
+class WotView(BaseGraphView, Ui_WotWidget):
+    """
+    Wot graph view
+    """
+
+    def __init__(self, parent):
+        """
+        Constructor
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        self.busy = Busy(self.graphics_view)
+        self.busy.hide()
+
+    def set_search_user(self, search_user):
+        """
+        Set the search user view in the gui
+        :param sakia.gui.search_user.view.SearchUserView search_user: the view
+        :return:
+        """
+        self.layout().insertWidget(0, search_user)
+
+    def scene(self):
+        """
+        Get the scene of the underlying graphics view
+        :return:
+        """
+        return self.graphics_view.scene()
+
+    def display_wot(self, nx_graph, identity):
+        """
+        Display given wot around given identity
+        :param nx_graph:
+        :param identity:
+        :return:
+        """
+        # draw graph in qt scene
+        self.graphics_view.scene().update_wot(nx_graph, identity)
+
+    def resizeEvent(self, event):
+        self.busy.resize(event.size())
+        super().resizeEvent(event)
diff --git a/res/ui/wot_tab.ui b/src/sakia/gui/navigation/graphs/wot/wot_tab.ui
similarity index 52%
rename from res/ui/wot_tab.ui
rename to src/sakia/gui/navigation/graphs/wot/wot_tab.ui
index d71654c11480961021b3679b84a89243a1b1e3b2..7bddf7cbfa3102c58d9b38710d1c30d85e78e956 100644
--- a/res/ui/wot_tab.ui
+++ b/src/sakia/gui/navigation/graphs/wot/wot_tab.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>WotTabWidget</class>
- <widget class="QWidget" name="WotTabWidget">
+ <class>WotWidget</class>
+ <widget class="QWidget" name="WotWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -13,34 +13,25 @@
   <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QGridLayout" name="gridLayout">
-   <item row="1" column="0" colspan="2">
-    <widget class="WotView" name="graphicsView">
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="WotGraphicsView" name="graphics_view">
      <property name="viewportUpdateMode">
       <enum>QGraphicsView::BoundingRectViewportUpdate</enum>
      </property>
     </widget>
    </item>
-   <item row="0" column="0" colspan="2">
-    <widget class="SearchUserWidget" name="search_user_widget" native="true"/>
-   </item>
   </layout>
  </widget>
  <customwidgets>
   <customwidget>
-   <class>WotView</class>
+   <class>WotGraphicsView</class>
    <extends>QGraphicsView</extends>
-   <header>sakia.gui.views.wot</header>
-  </customwidget>
-  <customwidget>
-   <class>SearchUserWidget</class>
-   <extends>QWidget</extends>
-   <header>sakia.gui.widgets.search_user</header>
-   <container>1</container>
+   <header>sakia.gui.navigation.graphs.wot.graphics_view</header>
   </customwidget>
  </customwidgets>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../../../../res/icons/icons.qrc"/>
  </resources>
  <connections/>
  <slots>
diff --git a/src/sakia/tests/mocks/__init__.py b/src/sakia/gui/navigation/homescreen/__init__.py
similarity index 100%
rename from src/sakia/tests/mocks/__init__.py
rename to src/sakia/gui/navigation/homescreen/__init__.py
diff --git a/src/sakia/gui/navigation/homescreen/controller.py b/src/sakia/gui/navigation/homescreen/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..d499a8cbefb74b9ce49b1e189e542229da0954a6
--- /dev/null
+++ b/src/sakia/gui/navigation/homescreen/controller.py
@@ -0,0 +1,35 @@
+from PyQt5.QtCore import QObject
+from .view import HomeScreenView
+from .model import HomeScreenModel
+
+
+class HomeScreenController(QObject):
+    """
+    The homescreen view
+    """
+
+    def __init__(self, parent, view, model):
+        """
+        Constructor of the homescreen component
+
+        :param sakia.gui.homescreen.view.HomeScreenView: the view
+        :param sakia.gui.homescreen.model.HomeScreenModel model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+
+    @classmethod
+    def create(cls, parent, app):
+        """
+        Instanciate a homescreen component
+        :param sakia.gui.component.controller.ComponentController parent:
+        :param sakia.core.Application app:
+        :return: a new Homescreen controller
+        :rtype: HomeScreenController
+        """
+        view = HomeScreenView(parent.view)
+        model = HomeScreenModel(None, app)
+        homescreen = cls(parent, view, model)
+        model.setParent(homescreen)
+        return homescreen
diff --git a/src/sakia/gui/navigation/homescreen/homescreen.ui b/src/sakia/gui/navigation/homescreen/homescreen.ui
new file mode 100644
index 0000000000000000000000000000000000000000..97f5d8bfb627f44bd79556a0ea732b3ceb179b92
--- /dev/null
+++ b/src/sakia/gui/navigation/homescreen/homescreen.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HomescreenWidget</class>
+ <widget class="QWidget" name="HomescreenWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>648</width>
+    <height>472</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout"/>
+ </widget>
+ <resources>
+  <include location="../../../../res/icons/icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/sakia/gui/navigation/homescreen/model.py b/src/sakia/gui/navigation/homescreen/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9dbae61066814536fbac80e12b5fcf2e15837cb
--- /dev/null
+++ b/src/sakia/gui/navigation/homescreen/model.py
@@ -0,0 +1,15 @@
+from PyQt5.QtCore import QObject
+
+
+class HomeScreenModel(QObject):
+    """
+    The model of HomeScreen component
+    """
+
+    def __init__(self, parent, app):
+        super().__init__(parent)
+        self.app = app
+
+    @property
+    def account(self):
+        return self.app.current_account
\ No newline at end of file
diff --git a/src/sakia/gui/navigation/homescreen/view.py b/src/sakia/gui/navigation/homescreen/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..828780f12b75a11de3d7fee306e38d774fda5949
--- /dev/null
+++ b/src/sakia/gui/navigation/homescreen/view.py
@@ -0,0 +1,15 @@
+from PyQt5.QtWidgets import QWidget
+from .homescreen_uic import Ui_HomescreenWidget
+
+
+class HomeScreenView(QWidget, Ui_HomescreenWidget):
+    """
+    Home screen view
+    """
+
+    def __init__(self, parent):
+        """
+        Constructor
+        """
+        super().__init__(parent)
+        self.setupUi(self)
diff --git a/src/sakia/tests/unit/__init__.py b/src/sakia/gui/navigation/identities/__init__.py
similarity index 100%
rename from src/sakia/tests/unit/__init__.py
rename to src/sakia/gui/navigation/identities/__init__.py
diff --git a/src/sakia/gui/navigation/identities/controller.py b/src/sakia/gui/navigation/identities/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..48a7d107ecff49f86b22cfa97b731aee68d64c04
--- /dev/null
+++ b/src/sakia/gui/navigation/identities/controller.py
@@ -0,0 +1,79 @@
+import logging
+
+from PyQt5.QtGui import QCursor
+from PyQt5.QtCore import QObject, pyqtSignal
+from sakia.errors import NoPeerAvailable
+
+from duniterpy.api import errors
+from sakia.decorators import once_at_a_time, asyncify
+from sakia.gui.widgets.context_menu import ContextMenu
+from .model import IdentitiesModel
+from .view import IdentitiesView
+
+
+class IdentitiesController(QObject):
+    """
+    The navigation panel
+    """
+    view_in_wot = pyqtSignal(object)
+
+    def __init__(self, parent, view, model, password_asker=None):
+        """
+        Constructor of the navigation component
+
+        :param sakia.gui.identities.view.IdentitiesView view: the view
+        :param sakia.gui.identities.model.IdentitiesModel model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+        self.password_asker = password_asker
+        self.view.search_by_text_requested.connect(self.search_text)
+        self.view.search_directly_connected_requested.connect(self.search_direct_connections)
+        self.view.table_identities.customContextMenuRequested.connect(self.identity_context_menu)
+        table_model = self.model.init_table_model()
+        self.view.set_table_identities_model(table_model)
+
+    @classmethod
+    def create(cls, parent, app, connection, blockchain_service, identities_service):
+        view = IdentitiesView(parent.view)
+        model = IdentitiesModel(None, app, connection, blockchain_service, identities_service)
+        identities = cls(parent, view, model)
+        model.setParent(identities)
+        return identities
+
+    def identity_context_menu(self, point):
+        index = self.view.table_identities.indexAt(point)
+        valid, identity = self.model.table_data(index)
+        if valid:
+            menu = ContextMenu.from_data(self.view, self.model.app, self.model.connection, (identity,))
+            menu.view_identity_in_wot.connect(self.view_in_wot)
+            menu.identity_information_loaded.connect(self.model.table_model.sourceModel().identity_loaded)
+
+            # Show the context menu.
+            menu.qmenu.popup(QCursor.pos())
+
+    @once_at_a_time
+    @asyncify
+    async def search_text(self, text):
+        """
+        Search identities using given text
+        :param str text: text to search
+        :return:
+        """
+        try:
+            identities = await self.model.lookup_identities(text)
+            self.model.refresh_identities(identities)
+        except errors.DuniterError as e:
+            if e.ucode == errors.BLOCK_NOT_FOUND:
+                logging.debug(str(e))
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+
+    def search_direct_connections(self):
+        """
+        Search identities directly connected to account
+        :return:
+        """
+        identities = self.model.linked_identities()
+        self.model.refresh_identities(identities)
diff --git a/res/ui/identities_tab.ui b/src/sakia/gui/navigation/identities/identities.ui
similarity index 96%
rename from res/ui/identities_tab.ui
rename to src/sakia/gui/navigation/identities/identities.ui
index 0dab4a2d41ed7e07fe2da897b2dc8bad20aa7496..7d21354e111e0506633e54f3c3577e9b0d840007 100644
--- a/res/ui/identities_tab.ui
+++ b/src/sakia/gui/navigation/identities/identities.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>IdentitiesTab</class>
- <widget class="QWidget" name="IdentitiesTab">
+ <class>IdentitiesWidget</class>
+ <widget class="QWidget" name="IdentitiesWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
diff --git a/src/sakia/gui/navigation/identities/model.py b/src/sakia/gui/navigation/identities/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..e91df361dcbf30100f396403fee2819427ebd3ae
--- /dev/null
+++ b/src/sakia/gui/navigation/identities/model.py
@@ -0,0 +1,80 @@
+import asyncio
+from PyQt5.QtCore import Qt, QObject
+from .table_model import IdentitiesFilterProxyModel, IdentitiesTableModel
+
+
+class IdentitiesModel(QObject):
+    """
+    The model of the identities component
+    """
+
+    def __init__(self, parent, app, connection, blockchain_service, identities_service):
+        """
+        Constructor of a model of Identities component
+
+        :param sakia.gui.identities.controller.IdentitiesController parent: the controller
+        :param sakia.app.Application app: the app
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        :param sakia.services.IdentitiesService identities_service: the identities service
+        """
+        super().__init__(parent)
+        self.app = app
+        self.connection = connection
+        self.blockchain_service = blockchain_service
+        self.identities_service = identities_service
+
+        self.table_model = None
+
+    def init_table_model(self):
+        """
+        Instanciate the table model of the view
+        """
+        identities_model = IdentitiesTableModel(self, self.blockchain_service, self.identities_service)
+        proxy = IdentitiesFilterProxyModel()
+        proxy.setSourceModel(identities_model)
+        self.table_model = proxy
+        return self.table_model
+
+    def table_data(self, index):
+        """
+        Get table data at given point
+        :param PyQt5.QtCore.QModelIndex index:
+        :return: a tuple containing information of the table
+        """
+        if index.isValid() and index.row() < self.table_model.rowCount():
+            source_index = self.table_model.mapToSource(index)
+            identity_col = self.table_model.sourceModel().columns_ids.index('identity')
+            identity_index = self.table_model.sourceModel().index(source_index.row(), identity_col)
+            identity = self.table_model.sourceModel().data(identity_index, Qt.DisplayRole)
+            return True, identity
+        return False, None
+
+    async def lookup_identities(self, text):
+        """
+        Lookup for identities
+        :param str text: text contained in the identities to lookup
+        """
+        return await self.identities_service.lookup(text)
+
+    def refresh_identities(self, identities):
+        """
+        Refresh the table with specified identities.
+        If no identities is passed, use the account connections.
+        """
+        self.table_model.sourceModel().refresh_identities(identities)
+
+    def linked_identities(self):
+
+        # create Identity from node metadata
+        connection_identity = self.identities_service.get_identity(self.connection.pubkey)
+        linked = []
+        certifier_list = self.identities_service.certifications_received(connection_identity.pubkey)
+        for certification in tuple(certifier_list):
+            linked.append(self.identities_service.get_identity(certification.certifier))
+
+        certified_list = self.identities_service.certifications_sent(connection_identity.pubkey)
+        for certification in tuple(certified_list):
+            linked.append(self.identities_service.get_identity(certification.certified))
+
+        return linked
diff --git a/src/sakia/models/identities.py b/src/sakia/gui/navigation/identities/table_model.py
similarity index 55%
rename from src/sakia/models/identities.py
rename to src/sakia/gui/navigation/identities/table_model.py
index b6fea90292dbf9c5b1df10e4f31576a538f0e830..0b6651f814132bac1668f12e9a01a7b838702d47 100644
--- a/src/sakia/models/identities.py
+++ b/src/sakia/gui/navigation/identities/table_model.py
@@ -1,13 +1,7 @@
-"""
-Created on 5 févr. 2014
-
-@author: inso
-"""
-
-from ..tools.exceptions import NoPeerAvailable, MembershipNotFoundError
+from sakia.errors import NoPeerAvailable
 from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt, \
-                        QDateTime, QModelIndex, QLocale, QEvent
-from PyQt5.QtGui import QColor, QIcon
+                        QDateTime, QModelIndex, QLocale, QT_TRANSLATE_NOOP
+from PyQt5.QtGui import QColor, QIcon, QFont
 import logging
 import asyncio
 
@@ -15,16 +9,10 @@ import asyncio
 class IdentitiesFilterProxyModel(QSortFilterProxyModel):
     def __init__(self, parent=None):
         super().__init__(parent)
-        self.community = None
-
-    def setSourceModel(self, sourceModel):
-        self.community = sourceModel.community
-        super().setSourceModel(sourceModel)
-
-    def change_community(self, community):
-        self.community = community
-        self.sourceModel().change_community(community)
 
+    def columnCount(self, parent):
+        return len(IdentitiesTableModel.columns_ids) - 1
+    
     def lessThan(self, left, right):
         """
         Sort table by given column number.
@@ -40,11 +28,12 @@ class IdentitiesFilterProxyModel(QSortFilterProxyModel):
         source_index = self.mapToSource(index)
         if source_index.isValid():
             source_data = self.sourceModel().data(source_index, role)
-            expiration_col = self.sourceModel().columns_ids.index('expiration')
+            expiration_col = IdentitiesTableModel.columns_ids.index('expiration')
             expiration_index = self.sourceModel().index(source_index.row(), expiration_col)
 
             STATUS_NOT_MEMBER = 0
             STATUS_MEMBER = 1
+            STATUS_UNKNOWN = 2
             STATUS_EXPIRE_SOON = 3
             status = STATUS_NOT_MEMBER
             expiration_data = self.sourceModel().data(expiration_index, Qt.DisplayRole)
@@ -53,7 +42,9 @@ class IdentitiesFilterProxyModel(QSortFilterProxyModel):
             warning_expiration_time = int(sig_validity / 3)
             #logging.debug("{0} > {1}".format(current_time, expiration_data))
 
-            if expiration_data is not None:
+            if expiration_data == 0:
+                status = STATUS_UNKNOWN
+            elif expiration_data is not None:
                 status = STATUS_MEMBER
                 if current_time > (expiration_data*1000):
                     status = STATUS_NOT_MEMBER
@@ -61,9 +52,9 @@ class IdentitiesFilterProxyModel(QSortFilterProxyModel):
                     status = STATUS_EXPIRE_SOON
 
             if role == Qt.DisplayRole:
-                if source_index.column() in (self.sourceModel().columns_ids.index('renewed'),
-                                             self.sourceModel().columns_ids.index('expiration')):
-                    if source_data is not None:
+                if source_index.column() in (IdentitiesTableModel.columns_ids.index('renewed'),
+                                             IdentitiesTableModel.columns_ids.index('expiration')):
+                    if source_data:
                         return QLocale.toString(
                             QLocale(),
                             QDateTime.fromTime_t(source_data).date(),
@@ -71,8 +62,8 @@ class IdentitiesFilterProxyModel(QSortFilterProxyModel):
                         )
                     else:
                         return ""
-                if source_index.column() == self.sourceModel().columns_ids.index('publication'):
-                    if source_data is not None:
+                if source_index.column() == IdentitiesTableModel.columns_ids.index('publication'):
+                    if source_data:
                         return QLocale.toString(
                             QLocale(),
                             QDateTime.fromTime_t(source_data),
@@ -80,17 +71,27 @@ class IdentitiesFilterProxyModel(QSortFilterProxyModel):
                         )
                     else:
                         return ""
-                if source_index.column() == self.sourceModel().columns_ids.index('pubkey'):
-                    return "pub:{0}".format(source_data[:5])
+                if source_index.column() == IdentitiesTableModel.columns_ids.index('pubkey'):
+                    return source_data
+                if source_index.column() == IdentitiesTableModel.columns_ids.index('block'):
+                    return str(source_data)[:20]
 
             if role == Qt.ForegroundRole:
                 if status == STATUS_EXPIRE_SOON:
                     return QColor("darkorange").darker(120)
                 elif status == STATUS_NOT_MEMBER:
                     return QColor(Qt.red)
+                elif status == STATUS_UNKNOWN:
+                    return QColor(Qt.black)
                 else:
                     return QColor(Qt.blue)
-            if role == Qt.DecorationRole and source_index.column() == self.sourceModel().columns_ids.index('uid'):
+
+            if role == Qt.FontRole and status == STATUS_UNKNOWN:
+                font = QFont()
+                font.setItalic(True)
+                return font
+
+            if role == Qt.DecorationRole and source_index.column() == IdentitiesTableModel.columns_ids.index('uid'):
                 if status == STATUS_NOT_MEMBER:
                     return QIcon(":/icons/not_member")
                 elif status == STATUS_MEMBER:
@@ -107,116 +108,97 @@ class IdentitiesTableModel(QAbstractTableModel):
     A Qt abstract item model to display communities in a tree
     """
 
-    def __init__(self, parent=None):
+    columns_titles = {'uid': lambda: QT_TRANSLATE_NOOP("IdentitiesTableModel", 'UID'),
+                           'pubkey': lambda: QT_TRANSLATE_NOOP("IdentitiesTableModel", 'Pubkey'),
+                           'renewed': lambda: QT_TRANSLATE_NOOP("IdentitiesTableModel", 'Renewed'),
+                           'expiration': lambda: QT_TRANSLATE_NOOP("IdentitiesTableModel", 'Expiration'),
+                           'publication': lambda: QT_TRANSLATE_NOOP("IdentitiesTableModel", 'Publication Date'),
+                           'block': lambda: QT_TRANSLATE_NOOP("IdentitiesTableModel", 'Publication Block'), }
+    columns_ids = ('uid', 'pubkey', 'renewed', 'expiration', 'publication', 'block', 'identity')
+
+    def __init__(self, parent, blockchain_service, identities_service):
         """
         Constructor
+        :param parent:
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        :param sakia.services.IdentitiesService identities_service: the identities service
         """
         super().__init__(parent)
-        self.community = None
-        self.columns_titles = {'uid': lambda: self.tr('UID'),
-                               'pubkey': lambda: self.tr('Pubkey'),
-                               'renewed': lambda: self.tr('Renewed'),
-                               'expiration': lambda: self.tr('Expiration'),
-                               'publication': lambda: self.tr('Publication Date'),
-                               'block': lambda: self.tr('Publication Block'),}
-        self.columns_ids = ('uid', 'pubkey', 'renewed', 'expiration', 'publication', 'block')
+        self.blockchain_service = blockchain_service
+        self.identities_service = identities_service
         self.identities_data = []
         self._sig_validity = 0
 
-    def change_community(self, community):
-        self.community = community
-
     def sig_validity(self):
         return self._sig_validity
-
+    
     @property
     def pubkeys(self):
         """
-        Ge
-    def resizeEvent(self, event):
-        self.busy.resize(event.size())
-        super().resizeEvent(event)t pubkeys of displayed identities
+        Get pubkeys of displayed identities
         """
         return [i[1] for i in self.identities_data]
 
-    async def identity_data(self, identity):
+    def identity_data(self, identity):
         """
         Return the identity in the form a tuple to display
-        :param sakia.core.registry.Identity identity: The identity to get data from
+        :param sakia.data.entities.Identity identity: The identity to get data from
         :return: The identity data in the form of a tuple
         :rtype: tuple
         """
-        try:
-            join_date = await identity.get_join_date(self.community)
-            expiration_date = await identity.get_expiration_date(self.community)
-        except MembershipNotFoundError:
-            join_date = None
-            expiration_date = None
-
-        if identity.sigdate:
-            sigdate_ts = await self.community.time(identity.sigdate.number)
-            sigdate_block = identity.sigdate.sha_hash[:7]
-        else:
-            sigdate_ts = None
-            sigdate_block = None
-
-        return identity.uid, identity.pubkey, join_date, expiration_date, sigdate_ts, sigdate_block
-
-    async def refresh_identities(self, identities):
+        join_date = identity.membership_timestamp
+        expiration_date = self.identities_service.expiration_date(identity)
+        sigdate_ts = identity.timestamp
+        sigdate_block = identity.blockstamp
+
+        return identity.uid, identity.pubkey, join_date, expiration_date, sigdate_ts, sigdate_block, identity
+
+    def refresh_identities(self, identities):
         """
         Change the identities to display
 
-        :param sakia.core.registry.IdentitiesRegistry identities: The new identities to display
+        :param list[sakia.data.entities.Identity] identities: The new identities to display
         """
         logging.debug("Refresh {0} identities".format(len(identities)))
         self.beginResetModel()
-        self.identities_data = []
-        self.endResetModel()
-        self.beginResetModel()
         identities_data = []
-        requests_coro = []
         for identity in identities:
-            coro = asyncio.ensure_future(self.identity_data(identity))
-            requests_coro.append(coro)
-
-        identities_data = await asyncio.gather(*requests_coro)
+            identities_data.append(self.identity_data(identity))
 
         if len(identities) > 0:
             try:
-                parameters = await self.community.parameters()
-                self._sig_validity = parameters['sigValidity']
+                parameters = self.blockchain_service.parameters()
+                self._sig_validity = parameters.sig_validity
             except NoPeerAvailable as e:
                 logging.debug(str(e))
                 self._sig_validity = 0
         self.identities_data = identities_data
         self.endResetModel()
 
+    def identity_loaded(self, identity):
+        for i, idty in enumerate(self.identities_data):
+            if idty[IdentitiesTableModel.columns_ids.index('identity')] == identity:
+                self.identities_data[i] = self.identity_data(identity)
+                self.dataChanged.emit(self.index(i, 0), self.index(i, len(IdentitiesTableModel.columns_ids)))
+                return
+
     def rowCount(self, parent):
         return len(self.identities_data)
 
     def columnCount(self, parent):
-        return len(self.columns_ids)
+        return len(IdentitiesTableModel.columns_ids)
 
     def headerData(self, section, orientation, role):
         if role == Qt.DisplayRole:
-            col_id = self.columns_ids[section]
-            return self.columns_titles[col_id]()
+            col_id = IdentitiesTableModel.columns_ids[section]
+            return IdentitiesTableModel.columns_titles[col_id]()
 
     def data(self, index, role):
-        if role == Qt.DisplayRole:
+        if index.isValid() and role == Qt.DisplayRole:
             row = index.row()
             col = index.column()
             identity_data = self.identities_data[row]
             return identity_data[col]
 
-    def identity_index(self, pubkey):
-        try:
-            row = self.pubkeys.index(pubkey)
-            index_start = self.index(row, 0)
-            index_end = self.index(row, len(self.columns_ids))
-            return (index_start, index_end)
-        except ValueError:
-            return (QModelIndex(), QModelIndex())
-
     def flags(self, index):
         return Qt.ItemIsSelectable | Qt.ItemIsEnabled
diff --git a/src/sakia/gui/navigation/identities/view.py b/src/sakia/gui/navigation/identities/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5011b16d8ee0a5c03d61773d436038eb353d8a8
--- /dev/null
+++ b/src/sakia/gui/navigation/identities/view.py
@@ -0,0 +1,73 @@
+from PyQt5.QtCore import pyqtSignal, QT_TRANSLATE_NOOP, Qt, QEvent
+from PyQt5.QtWidgets import QWidget, QAbstractItemView, QAction
+from .identities_uic import Ui_IdentitiesWidget
+
+
+class IdentitiesView(QWidget, Ui_IdentitiesWidget):
+    """
+    View of the Identities component
+    """
+    view_in_wot = pyqtSignal(object)
+    money_sent = pyqtSignal()
+    search_by_text_requested = pyqtSignal(str)
+    search_directly_connected_requested = pyqtSignal()
+
+    _direct_connections_text = QT_TRANSLATE_NOOP("IdentitiesView", "Search direct certifications")
+    _search_placeholder = QT_TRANSLATE_NOOP("IdentitiesView", "Research a pubkey, an uid...")
+
+    def __init__(self, parent):
+        super().__init__(parent)
+
+        self.direct_connections = QAction(self.tr(IdentitiesView._direct_connections_text), self)
+        self.direct_connections.triggered.connect(self.request_search_direct_connections)
+        self.setupUi(self)
+
+        self.table_identities.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.table_identities.sortByColumn(0, Qt.AscendingOrder)
+        self.table_identities.resizeColumnsToContents()
+        self.edit_textsearch.setPlaceholderText(self.tr(IdentitiesView._search_placeholder))
+        self.button_search.addAction(self.direct_connections)
+        self.button_search.clicked.connect(self.request_search_by_text)
+
+    def set_table_identities_model(self, model):
+        """
+        Set the model of the table view
+        :param PyQt5.QtCore.QAbstractItemModel model: the model of the table view
+        """
+        self.table_identities.setModel(model)
+        model.modelAboutToBeReset.connect(lambda: self.table_identities.setEnabled(False))
+        model.modelReset.connect(lambda: self.table_identities.setEnabled(True))
+
+    def request_search_by_text(self):
+        text = self.edit_textsearch.text()
+        if len(text) < 2:
+            return
+        self.edit_textsearch.clear()
+        self.edit_textsearch.setPlaceholderText(text)
+        self.search_by_text_requested.emit(text)
+
+    def request_search_direct_connections(self):
+        """
+        Search members of community and display found members
+        """
+        self.edit_textsearch.setPlaceholderText(self.tr(IdentitiesView._search_placeholder))
+        self.search_directly_connected_requested.emit()
+
+    def retranslateUi(self, widget):
+        self.direct_connections.setText(self.tr(IdentitiesView._direct_connections_text))
+        super().retranslateUi(self)
+
+    def resizeEvent(self, event):
+        self.busy.resize(event.size())
+        super().resizeEvent(event)
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+        return super().changeEvent(event)
+
diff --git a/src/sakia/tests/unit/core/graph/__init__.py b/src/sakia/gui/navigation/informations/__init__.py
similarity index 100%
rename from src/sakia/tests/unit/core/graph/__init__.py
rename to src/sakia/gui/navigation/informations/__init__.py
diff --git a/src/sakia/gui/navigation/informations/controller.py b/src/sakia/gui/navigation/informations/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..074699c162bba386ad12f271e4de6a62c618fda5
--- /dev/null
+++ b/src/sakia/gui/navigation/informations/controller.py
@@ -0,0 +1,98 @@
+import logging
+
+from PyQt5.QtCore import QObject
+from sakia.errors import NoPeerAvailable
+
+from duniterpy.api import errors
+from sakia.decorators import asyncify
+from .model import InformationsModel
+from .view import InformationsView
+
+
+class InformationsController(QObject):
+    """
+    The informations component
+    """
+
+    def __init__(self, parent, view, model):
+        """
+        Constructor of the informations component
+
+        :param sakia.gui.informations.view.InformationsView view: the view
+        :param sakia.gui.informations.model.InformationsModel model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+
+    @property
+    def informations_view(self):
+        """
+        :rtype: sakia.gui.informations.view.InformationsView
+        """
+        return self.view
+
+    @classmethod
+    def create(cls, parent, app, connection, blockchain_service, identities_service, sources_service):
+        """
+
+        :param parent:
+        :param sakia.app.Application app:
+        :param connection:
+        :param blockchain_service:
+        :param identities_service:
+        :param sources_service:
+        :return:
+        """
+        view = InformationsView(parent.view)
+        model = InformationsModel(None, app, connection, blockchain_service, identities_service, sources_service)
+        informations = cls(parent, view, model)
+        model.setParent(informations)
+        informations.init_view_text()
+        app.identity_changed.connect(informations.handle_identity_change)
+        app.new_transfer.connect(informations.refresh_localized_data)
+        app.new_dividend.connect(informations.refresh_localized_data)
+        app.referential_changed.connect(informations.refresh_localized_data)
+        app.sources_refreshed.connect(informations.refresh_localized_data)
+        return informations
+
+    @asyncify
+    async def init_view_text(self):
+        """
+        Initialization of text in informations view
+        """
+        referentials = self.model.referentials()
+        self.view.set_rules_text_no_dividend()
+        self.view.set_general_text_no_dividend()
+        self.view.set_text_referentials(referentials)
+        params = self.model.parameters()
+        if params:
+            self.view.set_money_text(params, self.model.short_currency())
+            self.view.set_wot_text(params)
+            self.refresh_localized_data()
+
+    def handle_identity_change(self, identity):
+        if identity.pubkey == self.model.connection.pubkey and identity.uid == self.model.connection.uid:
+            self.refresh_localized_data()
+
+    def refresh_localized_data(self):
+        """
+        Refresh localized data in view
+        """
+        localized_data = self.model.get_localized_data()
+        try:
+            simple_data = self.model.get_identity_data()
+            all_data = {**simple_data, **localized_data}
+            self.view.set_simple_informations(all_data, InformationsView.CommunityState.READY)
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+            self.view.set_simple_informations(all_data, InformationsView.CommunityState.OFFLINE)
+        except errors.DuniterError as e:
+            if e.ucode == errors.BLOCK_NOT_FOUND:
+                self.view.set_simple_informations(all_data, InformationsView.CommunityState.NOT_INIT)
+            else:
+                raise
+
+        self.view.set_general_text(localized_data)
+        self.view.set_rules_text(localized_data)
+
diff --git a/res/ui/informations_tab.ui b/src/sakia/gui/navigation/informations/informations.ui
similarity index 69%
rename from res/ui/informations_tab.ui
rename to src/sakia/gui/navigation/informations/informations.ui
index 46ed1d421ce25daa318873f2b5b548f42e23a40b..920da0a0356356bd02d576bc429b1841c01ac67e 100644
--- a/res/ui/informations_tab.ui
+++ b/src/sakia/gui/navigation/informations/informations.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>InformationsTabWidget</class>
- <widget class="QWidget" name="InformationsTabWidget">
+ <class>InformationsWidget</class>
+ <widget class="QWidget" name="InformationsWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -13,11 +13,8 @@
   <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="0">
-    <widget class="QScrollArea" name="scrollArea">
-     <property name="styleSheet">
-      <string notr="true">QGroupBox {
+  <property name="styleSheet">
+   <string notr="true">QGroupBox {
     border: 1px solid gray;
     border-radius: 9px;
     margin-top: 0.5em;
@@ -29,6 +26,12 @@ QGroupBox::title {
     padding: 0 3px 0 3px;
 	font-weight: bold;
 }</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="3" column="0">
+    <widget class="QScrollArea" name="scrollarea">
+     <property name="styleSheet">
+      <string notr="true"/>
      </property>
      <property name="widgetResizable">
       <bool>true</bool>
@@ -38,8 +41,8 @@ QGroupBox::title {
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>518</width>
-        <height>717</height>
+        <width>522</width>
+        <height>308</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_5">
@@ -148,10 +151,69 @@ QGroupBox::title {
      </widget>
     </widget>
    </item>
+   <item row="1" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="topMargin">
+      <number>6</number>
+     </property>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="button_details">
+       <property name="text">
+        <string>Details</string>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="0" column="0">
+    <widget class="QGroupBox" name="group_uid_state">
+     <property name="title">
+      <string>UID</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QLabel" name="label_simple">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
   </layout>
  </widget>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../../res/icons/icons.qrc"/>
  </resources>
  <connections/>
 </ui>
diff --git a/src/sakia/gui/navigation/informations/model.py b/src/sakia/gui/navigation/informations/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..729eade25b96b9c935f3bdb035835d0bf5b6826e
--- /dev/null
+++ b/src/sakia/gui/navigation/informations/model.py
@@ -0,0 +1,179 @@
+import logging
+import math
+
+from PyQt5.QtCore import QLocale, QDateTime, pyqtSignal, QObject
+from sakia.errors import NoPeerAvailable
+
+from sakia.money.currency import shortened
+from sakia.money import Referentials
+from duniterpy.api import errors
+
+
+class InformationsModel(QObject):
+    """
+    An component
+    """
+    localized_data_changed = pyqtSignal(dict)
+
+    def __init__(self, parent, app, connection, blockchain_service, identities_service, sources_service):
+        """
+        Constructor of an component
+
+        :param sakia.gui.informations.controller.InformationsController parent: the controller
+        :param sakia.app.Application app: the app
+        :param sakia.data.entities.Connection connection: the user connection of this node
+        :param sakia.services.BlockchainService blockchain_service: the service watching the blockchain state
+        :param sakia.services.IdentitiesService identities_service: the service watching the identities state
+        :param sakia.services.SourcesService sources_service: the service watching the sources states
+        """
+        super().__init__(parent)
+        self.app = app
+        self.connection = connection
+        self.blockchain_service = blockchain_service
+        self.identities_service = identities_service
+        self.sources_service = sources_service
+        self._logger = logging.getLogger('sakia')
+
+    def get_localized_data(self):
+        localized_data = {}
+        #  try to request money parameters
+        try:
+            params = self.blockchain_service.parameters()
+        except NoPeerAvailable as e:
+            logging.debug('community parameters error : ' + str(e))
+            return None
+
+        localized_data['growth'] = params.c
+        localized_data['days_per_dividend'] = params.dt / 86400
+
+        last_ud, last_ud_base = self.blockchain_service.last_ud()
+        members_count = self.blockchain_service.last_members_count()
+        previous_ud, previous_ud_base = self.blockchain_service.previous_ud()
+        previous_ud_time = self.blockchain_service.previous_ud_time()
+        previous_monetary_mass = self.blockchain_service.previous_monetary_mass()
+        previous_members_count = self.blockchain_service.previous_members_count()
+
+        localized_data['units'] = self.app.current_ref.instance(0,
+                                                                self.connection.currency,
+                                                                self.app, None).units
+        localized_data['diff_units'] = self.app.current_ref.instance(0,
+                                                                     self.connection.currency,
+                                                                     self.app, None).diff_units
+
+        if last_ud:
+            # display float values
+            localized_data['ud'] = self.app.current_ref.instance(last_ud * math.pow(10, last_ud_base),
+                                              self.connection.currency,
+                                              self.app).diff_localized(False, True)
+
+            localized_data['members_count'] = self.blockchain_service.current_members_count()
+
+            computed_dividend = self.blockchain_service.computed_dividend()
+            # display float values
+            localized_data['ud_plus_1'] = self.app.current_ref.instance(computed_dividend,
+                                              self.connection.currency, self.app).diff_localized(False, True)
+
+            localized_data['mass'] = self.app.current_ref.instance(self.blockchain_service.current_mass(),
+                                              self.connection.currency, self.app).diff_localized(False, True)
+
+            localized_data['ud_median_time'] = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(self.blockchain_service.last_ud_time()),
+                QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+            )
+
+            localized_data['next_ud_median_time'] = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(self.blockchain_service.last_ud_time() + params.dt),
+                QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+            )
+
+            if previous_ud:
+                mass_minus_1_per_member = (float(0) if previous_ud == 0 else
+                                           previous_monetary_mass / previous_members_count)
+                localized_data['mass_minus_1_per_member'] = self.app.current_ref.instance(mass_minus_1_per_member,
+                                                  self.connection.currency, self.app) \
+                                                .diff_localized(False, True)
+                localized_data['mass_minus_1'] = self.app.current_ref.instance(previous_monetary_mass,
+                                                  self.connection.currency, self.app) \
+                                                    .diff_localized(False, True)
+                # avoid divide by zero !
+                if members_count == 0 or previous_members_count == 0:
+                    localized_data['actual_growth'] = float(0)
+                else:
+                    localized_data['actual_growth'] = (last_ud * math.pow(10, last_ud_base)) / (
+                    previous_monetary_mass / members_count)
+
+                localized_data['ud_median_time_minus_1'] = QLocale.toString(
+                    QLocale(),
+                    QDateTime.fromTime_t(previous_ud_time),
+                    QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+                )
+        return localized_data
+
+    def get_identity_data(self):
+        amount = self.sources_service.amount(self.connection.pubkey)
+        localized_amount = self.app.current_ref.instance(amount,
+                                                         self.connection.currency,
+                                                         self.app).localized(False, True)
+        mstime_remaining_text = self.tr("Expired or never published")
+        outdistanced_text = self.tr("Outdistanced")
+        is_member = False
+        nb_certs = 0
+
+        if self.connection.uid:
+            try:
+                identity = self.identities_service.get_identity(self.connection.pubkey, self.connection.uid)
+                mstime_remaining = self.identities_service.ms_time_remaining(identity)
+                is_member = identity.member
+                nb_certs = len(self.identities_service.certifications_received(identity.pubkey))
+                if not identity.outdistanced:
+                    outdistanced_text = self.tr("In WoT range")
+
+                if mstime_remaining > 0:
+                    days, remainder = divmod(mstime_remaining, 3600 * 24)
+                    hours, remainder = divmod(remainder, 3600)
+                    minutes, seconds = divmod(remainder, 60)
+                    mstime_remaining_text = self.tr("Expires in ")
+                    if days > 0:
+                        mstime_remaining_text += "{days} days".format(days=days)
+                    else:
+                        mstime_remaining_text += "{hours} hours and {min} min.".format(hours=hours,
+                                                                                       min=minutes)
+            except errors.DuniterError as e:
+                if e.ucode == errors.NO_MEMBER_MATCHING_PUB_OR_UID:
+                    pass
+                else:
+                    self._logger.error(str(e))
+
+        return {
+            'amount': localized_amount,
+            'outdistanced': outdistanced_text,
+            'nb_certs': nb_certs,
+            'mstime': mstime_remaining_text,
+            'membership_state': is_member
+        }
+
+    def parameters(self):
+        """
+        Get community parameters
+        """
+        return self.blockchain_service.parameters()
+
+    def referentials(self):
+        """
+        Get referentials
+        :return: The list of instances of all referentials
+        :rtype: list
+        """
+        refs_instances = []
+        for ref_class in Referentials:
+             refs_instances.append(ref_class(0, self.connection.currency, self.app, None))
+        return refs_instances
+
+    def short_currency(self):
+        """
+        Get community currency
+        :return: the community in short currency format
+        """
+        return shortened(self.connection.currency)
\ No newline at end of file
diff --git a/src/sakia/gui/navigation/informations/view.py b/src/sakia/gui/navigation/informations/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc8bae79cc966ba1e3575eab97cdd122d42ba0f2
--- /dev/null
+++ b/src/sakia/gui/navigation/informations/view.py
@@ -0,0 +1,274 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtCore import QEvent
+from .informations_uic import Ui_InformationsWidget
+from enum import Enum
+
+
+class InformationsView(QWidget, Ui_InformationsWidget):
+    """
+    The view of navigation panel
+    """
+
+    class CommunityState(Enum):
+        NOT_INIT = 0
+        OFFLINE = 1
+        READY = 2
+
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.setupUi(self)
+        self.scrollarea.hide()
+        self.button_details.clicked.connect(self.handle_details_click)
+
+    def handle_details_click(self):
+        if self.button_details.isChecked():
+            self.scrollarea.show()
+        else:
+            self.scrollarea.hide()
+
+    def set_simple_informations(self, data, state):
+        if state in (InformationsView.CommunityState.NOT_INIT, InformationsView.CommunityState.OFFLINE):
+            self.label_simple.setText("""<html>
+                <body>
+                <p>
+                <span style=" font-size:16pt; font-weight:600;">{currency}</span>
+                </p>
+                <p>{message}</p>
+                </body>
+                </html>""".format(currency=data['currency'],
+                                  message=InformationsView.simple_message[state]))
+        else:
+            status_value = self.tr("Member") if data['membership_state'] else self.tr("Non-Member")
+            status_color = '#00AA00' if data['membership_state'] else self.tr('#FF0000')
+            description = """<html>
+                        <body>
+                        <p>
+                        <span style=" font-size:16pt; font-weight:600;">{currency}</span>
+                        </p>
+                        <p>{nb_members} {members_label}</p>
+                        <p><span style="font-weight:600;">{monetary_mass_label}</span> : {monetary_mass}</p>
+                        <p><span style="font-weight:600;">{status_label}</span> : <span style="color:{status_color};">{status}</span></p>
+                        <p><span style="font-weight:600;">{nb_certs_label}</span> : {nb_certs} ({outdistanced_text})</p>
+                        <p><span style="font-weight:600;">{mstime_remaining_label}</span> : {mstime_remaining}</p>
+                        <p><span style="font-weight:600;">{balance_label}</span> : {balance}</p>
+                        </body>
+                        </html>""".format(currency=data['units'],
+                                          nb_members=data['members_count'],
+                                          members_label=self.tr("members"),
+                                          monetary_mass_label=self.tr("Monetary mass"),
+                                          monetary_mass=data['mass'],
+                                          status_color=status_color,
+                                          status_label=self.tr("Status"),
+                                          status=status_value,
+                                          nb_certs_label=self.tr("Certs. received"),
+                                          nb_certs=data['nb_certs'],
+                                          outdistanced_text=data['outdistanced'],
+                                          mstime_remaining_label=self.tr("Membership"),
+                                          mstime_remaining=data['mstime'],
+                                          balance_label=self.tr("Balance"),
+                                          balance=data['amount'])
+            self.label_simple.setText(description)
+
+    def set_general_text_no_dividend(self):
+        """
+        Set the general text when there is no dividend
+        """
+        self.label_general.setText(self.tr('No Universal Dividend created yet.'))
+
+    def set_general_text(self, localized_data):
+        """
+        Fill the general text with given informations
+        :return:
+        """
+        # set infos in label
+        self.label_general.setText(
+            self.tr("""
+            <table cellpadding="5">
+            <tr><td align="right"><b>{:}</b></div></td><td>{:} {:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
+            <tr><td align="right"><b>{:2.2%} / {:} days</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            </table>
+            """).format(
+                localized_data.get('ud', '####'),
+                self.tr('Universal Dividend UD(t) in'),
+                localized_data['diff_units'],
+                localized_data['mass_minus_1'],
+                self.tr('Monetary Mass M(t-1) in'),
+                localized_data['units'],
+                localized_data.get('members_count', '####'),
+                self.tr('Members N(t)'),
+                localized_data.get('mass_minus_1_per_member', '####'),
+                self.tr('Monetary Mass per member M(t-1)/N(t) in'),
+                localized_data['diff_units'],
+                localized_data.get('actual_growth', 0),
+                localized_data.get('days_per_dividend', '####'),
+                self.tr('Actual growth c = UD(t)/[M(t-1)/N(t)]'),
+                localized_data.get('ud_median_time_minus_1', '####'),
+                self.tr('Penultimate UD date and time (t-1)'),
+                localized_data.get('ud_median_time', '####'),
+                self.tr('Last UD date and time (t)'),
+                localized_data.get('next_ud_median_time', '####'),
+                self.tr('Next UD date and time (t+1)')
+            )
+        )
+
+    def set_rules_text_no_dividend(self):
+        """
+        Set text when no dividends was generated yet
+        """
+        self.label_rules.setText(self.tr('No Universal Dividend created yet.'))
+
+    def set_rules_text(self, localized_data):
+        """
+        Set text in rules
+        :param dict localized_data:
+        :return:
+        """
+        # set infos in label
+        self.label_rules.setText(
+            self.tr("""
+            <table cellpadding="5">
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            </table>
+            """).format(
+                self.tr('{:2.0%} / {:} days').format(localized_data['growth'], localized_data['days_per_dividend']),
+                self.tr('Fundamental growth (c) / Delta time (dt)'),
+                self.tr('UD(t+1) = MAX { UD(t) ; c &#215; M(t) / N(t+1) }'),
+                self.tr('Universal Dividend (formula)'),
+                self.tr('{:} = MAX {{ {:} {:} ; {:2.0%} &#215; {:} {:} / {:} }}').format(
+                    localized_data.get('ud_plus_1', '####'),
+                    localized_data.get('ud', '####'),
+                    localized_data['diff_units'],
+                    localized_data.get('growth', '####'),
+                    localized_data.get('mass', '####'),
+                    localized_data['diff_units'],
+                    localized_data.get('members_count', '####')
+                ),
+                self.tr('Universal Dividend (computed)')
+            )
+        )
+
+    def set_text_referentials(self, referentials):
+        """
+        Set text from referentials
+        :param list referentials: list of referentials
+        """
+        # set infos in label
+        ref_template = """
+                <table cellpadding="5">
+                <tr><th>{:}</th><td>{:}</td></tr>
+                <tr><th>{:}</th><td>{:}</td></tr>
+                <tr><th>{:}</th><td>{:}</td></tr>
+                <tr><th>{:}</th><td>{:}</td></tr>
+                </table>
+                """
+        templates = []
+        for ref in referentials:
+            # print(ref_class.__class__.__name__)
+            # if ref_class.__class__.__name__ == 'RelativeToPast':
+            #     continue
+            templates.append(ref_template.format(self.tr('Name'), ref.translated_name(),
+                                                 self.tr('Units'), ref.units,
+                                                 self.tr('Formula'), ref.formula,
+                                                 self.tr('Description'), ref.description
+                                                 )
+                             )
+
+        self.label_referentials.setText('<hr>'.join(templates))
+
+    def set_money_text(self, params, currency):
+        """
+        Set text from money parameters
+        :param sakia.data.entities.BlockchainParameters params: Parameters of the currency
+        :param str currency: The currency
+        """
+
+        # set infos in label
+        self.label_money.setText(
+                self.tr("""
+            <table cellpadding="5">
+            <tr><td align="right"><b>{:2.0%} / {:} days</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:} {:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:2.0%}</b></td><td>{:}</td></tr>
+            </table>
+            """).format(
+                        params.c,
+                        params.dt / 86400,
+                        self.tr('Fundamental growth (c)'),
+                        params.ud0,
+                        self.tr('Initial Universal Dividend UD(0) in'),
+                        currency,
+                        params.dt / 86400,
+                        self.tr('Time period (dt) in days (86400 seconds) between two UD'),
+                        params.median_time_blocks,
+                        self.tr('Number of blocks used for calculating median time'),
+                        params.avg_gen_time,
+                        self.tr('The average time in seconds for writing 1 block (wished time)'),
+                        params.dt_diff_eval,
+                        self.tr('The number of blocks required to evaluate again PoWMin value'),
+                        params.percent_rot,
+                        self.tr('The percent of previous issuers to reach for personalized difficulty')
+                )
+        )
+
+    def set_wot_text(self, params):
+        """
+        Set wot text from currency parameters
+        :param sakia.data.entities.BlockchainParameters params: Parameters of the currency
+        :return:
+        """
+
+        # set infos in label
+        self.label_wot.setText(
+                self.tr("""
+            <table cellpadding="5">
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            </table>
+            """).format(
+                        params.sig_period / 86400,
+                        self.tr('Minimum delay between 2 certifications (in days)'),
+                        params.sig_validity / 86400,
+                        self.tr('Maximum age of a valid signature (in days)'),
+                        params.sig_qty,
+                        self.tr('Minimum quantity of signatures to be part of the WoT'),
+                        params.sig_stock,
+                        self.tr('Maximum quantity of active certifications made by member.'),
+                        params.sig_window,
+                        self.tr('Maximum delay a certification can wait before being expired for non-writing.'),
+                        params.xpercent,
+                        self.tr('Minimum percent of sentries to reach to match the distance rule'),
+                        params.ms_validity / 86400,
+                        self.tr('Maximum age of a valid membership (in days)'),
+                        params.step_max,
+                        self.tr('Maximum distance between each WoT member and a newcomer'),
+                )
+        )
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+            self.refresh()
+        return super().changeEvent(event)
diff --git a/src/sakia/gui/navigation/model.py b/src/sakia/gui/navigation/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f2387246f1f1d75816bc9936e6e5a350bb7df11
--- /dev/null
+++ b/src/sakia/gui/navigation/model.py
@@ -0,0 +1,157 @@
+from PyQt5.QtCore import QObject, pyqtSignal
+from PyQt5.QtWidgets import QApplication
+from sakia.models.generic_tree import GenericTreeModel
+from sakia.data.processors import ConnectionsProcessor
+
+
+class NavigationModel(QObject):
+    """
+    The model of Navigation component
+    """
+    navigation_changed = pyqtSignal(GenericTreeModel)
+
+    def __init__(self, parent, app):
+        """
+
+        :param sakia.gui.component.controller.ComponentController parent:
+        :param sakia.app.Application app:
+        """
+        super().__init__(parent)
+        self.app = app
+        self.navigation = []
+        self._current_data = None
+
+    def init_navigation_data(self):
+        currencies = ConnectionsProcessor.instanciate(self.app).currencies()
+        if currencies:
+            self.navigation = [
+                {
+                    'title': self.tr('Network'),
+                    'icon': ':/icons/network_icon',
+                    'component': "Network",
+                    'dependencies': {
+                        'network_service': self.app.network_services[currencies[0]],
+                    },
+                    'misc': {
+                    },
+                    'children': []
+                }
+            ]
+        else:
+            self.navigation = [
+                {
+                    'title': self.tr("No connection configured"),
+                    'component': "HomeScreen",
+                    'parameters': self.app.parameters,
+                    'dependencies': {},
+                    'misc': {},
+                    'children': []
+                }
+            ]
+
+        self._current_data = self.navigation[0]
+        for connection in self.app.db.connections_repo.get_all():
+            self.navigation[0]['children'].append(self.create_node(connection))
+        return self.navigation
+
+    def create_node(self, connection):
+        node = {
+            'title': connection.title(),
+            'component': "Informations",
+            'dependencies': {
+                'blockchain_service': self.app.blockchain_services[connection.currency],
+                'identities_service': self.app.identities_services[connection.currency],
+                'sources_service': self.app.sources_services[connection.currency],
+                'connection': connection,
+            },
+            'misc': {
+                'connection': connection
+            },
+            'children': [
+                {
+                    'title': self.tr('Transfers'),
+                    'icon': ':/icons/tx_icon',
+                    'component': "TxHistory",
+                    'dependencies': {
+                        'connection': connection,
+                        'identities_service': self.app.identities_services[connection.currency],
+                        'blockchain_service': self.app.blockchain_services[connection.currency],
+                        'transactions_service': self.app.transactions_services[connection.currency],
+                        "sources_service": self.app.sources_services[connection.currency]
+                    },
+                    'misc': {
+                        'connection': connection
+                    }
+                }
+            ]
+        }
+        if connection.uid:
+            node["children"] += [{
+                'title': self.tr('Identities'),
+                'icon': ':/icons/members_icon',
+                'component': "Identities",
+                'dependencies': {
+                    'connection': connection,
+                    'blockchain_service': self.app.blockchain_services[connection.currency],
+                    'identities_service': self.app.identities_services[connection.currency],
+                },
+                'misc': {
+                    'connection': connection
+                }
+            },
+            {
+                'title': self.tr('Web of Trust'),
+                'icon': ':/icons/wot_icon',
+                'component': "Wot",
+                'dependencies': {
+                    'connection': connection,
+                    'blockchain_service': self.app.blockchain_services[connection.currency],
+                    'identities_service': self.app.identities_services[connection.currency],
+                },
+                'misc': {
+                    'connection': connection
+                }
+            }]
+        return node
+
+    def generic_tree(self):
+        return GenericTreeModel.create("Navigation", self.navigation)
+
+    def add_connection(self, connection):
+        raw_node = self.create_node(connection)
+        self.navigation[0]["children"].append(raw_node)
+        return raw_node
+
+    def set_current_data(self, raw_data):
+        self._current_data = raw_data
+
+    def current_data(self, key):
+        return self._current_data.get(key, None)
+
+    def current_connection(self):
+        if self._current_data:
+            return self._current_data['misc'].get('connection', None)
+        else:
+            return None
+
+    def generate_revokation(self, connection, password):
+        return self.app.documents_service.generate_revokation(connection, password)
+
+    def identity_published(self, connection):
+        identities_services = self.app.identities_services[connection.currency]
+        return identities_services.get_identity(connection.pubkey, connection.uid).written_on != 0
+
+    def identity_is_member(self, connection):
+        identities_services = self.app.identities_services[connection.currency]
+        return identities_services.get_identity(connection.pubkey, connection.uid).member
+
+    async def remove_connection(self, connection):
+        await self.app.remove_connection(connection)
+
+    async def send_leave(self, connection, password):
+        return await self.app.documents_service.send_membership(connection, password, "OUT")
+
+    @staticmethod
+    def copy_pubkey_to_clipboard(connection):
+        clipboard = QApplication.clipboard()
+        clipboard.setText(connection.pubkey)
\ No newline at end of file
diff --git a/src/sakia/gui/navigation/navigation.ui b/src/sakia/gui/navigation/navigation.ui
new file mode 100644
index 0000000000000000000000000000000000000000..74af0ce9b1d4976fa1a730e53d8c580594a83953
--- /dev/null
+++ b/src/sakia/gui/navigation/navigation.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Navigation</class>
+ <widget class="QFrame" name="Navigation">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>685</width>
+    <height>477</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Frame</string>
+  </property>
+  <property name="frameShape">
+   <enum>QFrame::StyledPanel</enum>
+  </property>
+  <property name="frameShadow">
+   <enum>QFrame::Raised</enum>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QTreeView" name="tree_view">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>150</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="showDropIndicator" stdset="0">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>true</bool>
+     </property>
+     <property name="headerHidden">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QStackedWidget" name="stacked_widget">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/sakia/tests/unit/core/money/__init__.py b/src/sakia/gui/navigation/network/__init__.py
similarity index 100%
rename from src/sakia/tests/unit/core/money/__init__.py
rename to src/sakia/gui/navigation/network/__init__.py
diff --git a/src/sakia/gui/navigation/network/controller.py b/src/sakia/gui/navigation/network/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..f610d90f02cbb7d662bfc6fc28cf57086693600d
--- /dev/null
+++ b/src/sakia/gui/navigation/network/controller.py
@@ -0,0 +1,89 @@
+from .model import NetworkModel
+from .view import NetworkView
+from PyQt5.QtWidgets import QAction, QMenu
+from PyQt5.QtGui import QCursor, QDesktopServices
+from PyQt5.QtCore import pyqtSlot, QUrl, QObject
+from duniterpy.api import bma
+from sakia.data.processors import ConnectionsProcessor
+
+
+class NetworkController(QObject):
+    """
+    The network panel
+    """
+
+    def __init__(self, parent, view, model):
+        """
+        Constructor of the navigation component
+
+        :param sakia.gui.network.view.NetworkView: the view
+        :param sakia.gui.network.model.NetworkModel model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+        table_model = self.model.init_network_table_model()
+        self.view.set_network_table_model(table_model)
+        self.view.manual_refresh_clicked.connect(self.refresh_nodes_manually)
+
+    @classmethod
+    def create(cls, parent, app, network_service):
+        """
+
+        :param PyQt5.QObject parent:
+        :param sakia.app.Application app:
+        :param sakia.services.NetworkService network_service:
+        :return:
+        """
+        view = NetworkView(parent.view,)
+        model = NetworkModel(None, app, network_service)
+        txhistory = cls(parent, view, model)
+        model.setParent(txhistory)
+        return txhistory
+    
+    def refresh_nodes_manually(self):
+        self.model.refresh_nodes_once()
+
+    def node_context_menu(self, point):
+        index = self.view.table_network.indexAt(point)
+        valid, node, is_root = self.model.table_model_data(index)
+        if valid:
+            self.view.show_menu(point, is_root)
+            menu = QMenu()
+            if is_root:
+                unset_root = QAction(self.tr("Unset root node"), self)
+                unset_root.triggered.connect(self.unset_root_node)
+                unset_root.setData(node)
+                if len(self.community.network.root_nodes) > 1:
+                    menu.addAction(unset_root)
+            else:
+                set_root = QAction(self.tr("Set as root node"), self)
+                set_root.triggered.connect(self.set_root_node)
+                set_root.setData(node)
+                menu.addAction(set_root)
+
+            if self.app.preferences['expert_mode']:
+                open_in_browser = QAction(self.tr("Open in browser"), self)
+                open_in_browser.triggered.connect(self.open_node_in_browser)
+                open_in_browser.setData(node)
+                menu.addAction(open_in_browser)
+
+            # Show the context menu.
+            menu.exec_(QCursor.pos())
+
+    @pyqtSlot()
+    def set_root_node(self):
+        node = self.sender().data()
+        self.model.add_root_node(node)
+
+    @pyqtSlot()
+    def unset_root_node(self):
+        node = self.sender().data()
+        self.model.unset_root_node(node)
+
+    @pyqtSlot()
+    def open_node_in_browser(self):
+        node = self.sender().data()
+        peering = bma.network.Peering(node.endpoint.conn_handler())
+        url = QUrl(peering.reverse_url("http", "/peering"))
+        QDesktopServices.openUrl(url)
\ No newline at end of file
diff --git a/src/sakia/gui/navigation/network/model.py b/src/sakia/gui/navigation/network/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e8c810601f49d478a4eb9cf1856115a2984fb92
--- /dev/null
+++ b/src/sakia/gui/navigation/network/model.py
@@ -0,0 +1,68 @@
+from .table_model import NetworkTableModel, NetworkFilterProxyModel
+from PyQt5.QtCore import QModelIndex, Qt, QObject
+
+
+class NetworkModel(QObject):
+    """
+    A network model
+    """
+
+    def __init__(self, parent, app, network_service):
+        """
+        Constructor of an network model
+
+        :param sakia.gui.network.controller.NetworkController parent: the controller
+        :param sakia.app.Application app: the app
+        :param sakia.services.NetworkService network_service: the service handling network state
+        """
+        super().__init__(parent)
+        self.app = app
+        self.network_service = network_service
+        self.table_model = None
+
+    def init_network_table_model(self):
+        model = NetworkTableModel(self.network_service)
+        proxy = NetworkFilterProxyModel()
+        proxy.setSourceModel(model)
+        self.table_model = proxy
+        model.refresh_nodes()
+        return self.table_model
+
+    def refresh_nodes_once(self):
+        """
+        Start the refresh of the nodes
+        :return:
+        """
+        self.network_service.refresh_once()
+
+    def table_model_data(self, index):
+        """
+        Get data at given index
+        :param PyQt5.QtCore.QModelIndex index:
+        :return:
+        """
+        if index.isValid() and index.row() < self.table_model.rowCount(QModelIndex()):
+            source_index = self.table_model.mapToSource(index)
+            is_root_col = self.table_model.sourceModel().columns_types.index('is_root')
+            is_root_index = self.table_model.sourceModel().index(source_index.row(), is_root_col)
+            is_root = self.table_model.sourceModel().data(is_root_index, Qt.DisplayRole)
+            node = self.community.network.nodes()[source_index.row()]
+            return True, node, is_root
+        return False, None, None
+
+    def add_root_node(self, node):
+        """
+        The node set as root
+        :param sakia.data.entities.Node node: the node
+        """
+        node.root = True
+        self.network_service.commit_node(node)
+
+    def unset_root_node(self, node):
+        """
+        The node set as root
+        :param sakia.data.entities.Node node: the node
+        """
+        node.root = False
+        self.network_service.commit_node(node)
+
diff --git a/res/ui/network_tab.ui b/src/sakia/gui/navigation/network/network.ui
similarity index 82%
rename from res/ui/network_tab.ui
rename to src/sakia/gui/navigation/network/network.ui
index 25edc66568e46a69ea74f9ce6314559b6fa5075b..c36278aa0ca3ec6518b57fe9550007c86fb40d39 100644
--- a/res/ui/network_tab.ui
+++ b/src/sakia/gui/navigation/network/network.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>NetworkTabWidget</class>
- <widget class="QWidget" name="NetworkTabWidget">
+ <class>NetworkWidget</class>
+ <widget class="QWidget" name="NetworkWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -40,7 +40,7 @@
           <string/>
          </property>
          <property name="icon">
-          <iconset resource="../icons/icons.qrc">
+          <iconset resource="../../../../res/icons/icons.qrc">
            <normaloff>:/icons/refresh_icon</normaloff>:/icons/refresh_icon</iconset>
          </property>
          <property name="iconSize">
@@ -86,29 +86,13 @@
   </layout>
  </widget>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../../res/icons/icons.qrc"/>
  </resources>
  <connections>
-  <connection>
-   <sender>table_network</sender>
-   <signal>customContextMenuRequested(QPoint)</signal>
-   <receiver>NetworkTabWidget</receiver>
-   <slot>node_context_menu()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>199</x>
-     <y>149</y>
-    </hint>
-   </hints>
-  </connection>
   <connection>
    <sender>button_manual_refresh</sender>
    <signal>clicked()</signal>
-   <receiver>NetworkTabWidget</receiver>
+   <receiver>NetworkWidget</receiver>
    <slot>manual_nodes_refresh()</slot>
    <hints>
     <hint type="sourcelabel">
diff --git a/src/sakia/models/network.py b/src/sakia/gui/navigation/network/table_model.py
similarity index 74%
rename from src/sakia/models/network.py
rename to src/sakia/gui/navigation/network/table_model.py
index 07c6c20b3cc1f967a1196e488df65cec6b296d4b..570313a0ee9b3fd932d5bc75d3ba0a884d74dec5 100644
--- a/src/sakia/models/network.py
+++ b/src/sakia/gui/navigation/network/table_model.py
@@ -4,39 +4,19 @@ Created on 5 févr. 2014
 @author: inso
 """
 
-import logging
-import asyncio
 
 from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, QDateTime, QLocale
 from PyQt5.QtGui import QColor, QFont, QIcon
-
-from ..tools.exceptions import NoPeerAvailable
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from sakia.core.net.node import Node
-
+from sakia.data.entities import Node
+from duniterpy.documents import BMAEndpoint, SecuredBMAEndpoint
 
 class NetworkFilterProxyModel(QSortFilterProxyModel):
     def __init__(self, parent=None):
         super().__init__(parent)
-        self.community = None
 
     def columnCount(self, parent):
         return self.sourceModel().columnCount(None) - 2
 
-    def change_community(self, community):
-        """
-        Change current community and returns refresh task
-        :param sakia.core.Community community:
-        :return: the refresh task
-        :rtype: asyncio.Task
-        """
-        self.community = community
-        return self.sourceModel().change_community(community)
-
-    def setSourceModel(self, sourceModel):
-        self.community = sourceModel.community
-        super().setSourceModel(sourceModel)
-
     def lessThan(self, left, right):
         """
         Sort table by given column number.
@@ -124,12 +104,14 @@ class NetworkTableModel(QAbstractTableModel):
     A Qt abstract item model to display
     """
 
-    def __init__(self, community, parent=None):
+    def __init__(self, network_service, parent=None):
         """
-        Constructor
+        The table showing nodes
+        :param sakia.services.NetworkService network_service:
+        :param parent:
         """
         super().__init__(parent)
-        self.community = community
+        self.network_service = network_service
         self.columns_types = (
             'address',
             'port',
@@ -163,58 +145,43 @@ class NetworkTableModel(QAbstractTableModel):
             Node.CORRUPTED: lambda: self.tr('Corrupted')
         }
         self.nodes_data = []
+        self.network_service.nodes_changed.connect(self.refresh_nodes)
 
-    def change_community(self, community):
-        """
-        Change current community displayed in network and refresh the nodes
-        :param sakia.core.Community community: the new community
-        :return: the refresh task
-        :rtype: asyncio.Task
-        """
-        cancel_once_task(self, self.refresh_nodes)
-        self.community = community
-        return self.refresh_nodes()
-
-    async def data_node(self, node: Node) -> tuple:
+    def data_node(self, node: Node) -> tuple:
         """
         Return node data tuple
         :param ..core.net.node.Node node: Network node
         :return:
         """
-        try:
-            members_pubkey = await self.community.members_pubkeys()
-            is_member = node.pubkey in members_pubkey
-        except NoPeerAvailable as e:
-            logging.error(e)
-            is_member = None
-
-        address = ""
-        if node.endpoint.server:
-            address = node.endpoint.server
-        elif node.endpoint.ipv4:
-            address = node.endpoint.ipv4
-        elif node.endpoint.ipv6:
-            address = node.endpoint.ipv6
-        port = node.endpoint.port
-
-        is_root = self.community.network.is_root_node(node)
-        if node.block:
-            number, block_hash, block_time = node.block['number'], node.block['hash'], node.block['medianTime']
+
+        addresses = []
+        ports = []
+        for e in node.endpoints:
+            if type(e) in (BMAEndpoint, SecuredBMAEndpoint):
+                if e.server:
+                    addresses.append(e.server)
+                elif e.ipv4:
+                    addresses.append(e.ipv4)
+                elif e.ipv6:
+                    addresses.append(e.ipv6)
+                ports.append(str(e.port))
+        address = "\n".join(addresses)
+        port = "\n".join(ports)
+
+        if node.current_buid:
+            number, block_hash, block_time = node.current_buid.number, node.current_buid.sha_hash, node.current_ts
         else:
             number, block_hash, block_time = "", "", ""
         return (address, port, number, block_hash, block_time, node.uid,
-                is_member, node.pubkey, node.software, node.version, is_root, node.state)
+                node.member, node.pubkey, node.software, node.version, node.root, node.state)
 
-    @once_at_a_time
-    @asyncify
-    async def refresh_nodes(self):
+    def refresh_nodes(self):
         self.beginResetModel()
         self.nodes_data = []
         nodes_data = []
-        if self.community:
-            for node in self.community.network.nodes:
-                data = await self.data_node(node)
-                nodes_data.append(data)
+        for node in self.network_service.nodes():
+            data = self.data_node(node)
+            nodes_data.append(data)
         self.nodes_data = nodes_data
         self.endResetModel()
 
diff --git a/src/sakia/gui/navigation/network/view.py b/src/sakia/gui/navigation/network/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..f956145b553784133261178c1ef03db03d1f8268
--- /dev/null
+++ b/src/sakia/gui/navigation/network/view.py
@@ -0,0 +1,47 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtCore import Qt, QEvent, pyqtSignal
+from .network_uic import Ui_NetworkWidget
+import asyncio
+
+
+class NetworkView(QWidget, Ui_NetworkWidget):
+    """
+    The view of Network component
+    """
+    manual_refresh_clicked = pyqtSignal()
+
+    def __init__(self, parent):
+        """
+
+        :param sakia.gui.network.controller parent:
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+    def set_network_table_model(self, model):
+        """
+        Set the table view model
+        :param PyQt5.QtCore.QAbstractItemModel model: the model of the table view
+        """
+        self.table_network.setModel(model)
+        self.table_network.sortByColumn(2, Qt.DescendingOrder)
+        self.table_network.resizeColumnsToContents()
+        model.modelAboutToBeReset.connect(lambda: self.table_network.setEnabled(False))
+        model.modelReset.connect(lambda: self.table_network.setEnabled(True))
+        model.modelReset.connect(self.table_network.resizeColumnsToContents)
+
+    def manual_nodes_refresh(self):
+        self.button_manual_refresh.setEnabled(False)
+        asyncio.get_event_loop().call_later(15, lambda: self.button_manual_refresh.setEnabled(True))
+        self.manual_refresh_clicked.emit()
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+            self.refresh_nodes()
+        return super().changeEvent(event)
diff --git a/src/sakia/tests/unit/gui/__init__.py b/src/sakia/gui/navigation/txhistory/__init__.py
similarity index 100%
rename from src/sakia/tests/unit/gui/__init__.py
rename to src/sakia/gui/navigation/txhistory/__init__.py
diff --git a/src/sakia/gui/navigation/txhistory/controller.py b/src/sakia/gui/navigation/txhistory/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..314ba91c972eb7323fa4882c2bdcd890fe5e308a
--- /dev/null
+++ b/src/sakia/gui/navigation/txhistory/controller.py
@@ -0,0 +1,99 @@
+import logging
+
+from PyQt5.QtCore import QTime, pyqtSignal, QObject
+from PyQt5.QtGui import QCursor
+
+from sakia.decorators import asyncify
+from sakia.gui.widgets import toast
+from sakia.gui.widgets.context_menu import ContextMenu
+from sakia.data.entities import Identity
+from .model import TxHistoryModel
+from .view import TxHistoryView
+
+
+class TxHistoryController(QObject):
+    """
+    Transfer history component controller
+    """
+    view_in_wot = pyqtSignal(object)
+
+    def __init__(self, view, model):
+        super().__init__()
+        self.view = view
+        self.model = model
+        self._logger = logging.getLogger('sakia')
+        ts_from, ts_to = self.view.get_time_frame()
+        model = self.model.init_history_table_model(ts_from, ts_to)
+        self.view.set_table_history_model(model)
+
+        self.view.date_from.dateChanged.connect(self.dates_changed)
+        self.view.date_to.dateChanged.connect(self.dates_changed)
+        self.view.table_history.customContextMenuRequested['QPoint'].connect(self.history_context_menu)
+        self.refresh()
+
+    @classmethod
+    def create(cls, parent, app, connection,
+               identities_service, blockchain_service, transactions_service, sources_service):
+
+        view = TxHistoryView(parent.view)
+        model = TxHistoryModel(None, app, connection, blockchain_service, identities_service,
+                               transactions_service, sources_service)
+        txhistory = cls(view, model)
+        model.setParent(txhistory)
+        app.referential_changed.connect(txhistory.refresh_balance)
+        app.sources_refreshed.connect(txhistory.refresh_balance)
+        return txhistory
+
+    def refresh_minimum_maximum(self):
+        """
+        Refresh minimum and maximum datetime
+        """
+        minimum, maximum = self.model.minimum_maximum_datetime()
+        self.view.set_minimum_maximum_datetime(minimum, maximum)
+
+    def refresh(self):
+        self.refresh_minimum_maximum()
+        self.refresh_balance()
+
+    @asyncify
+    async def notification_reception(self, received_list):
+        if len(received_list) > 0:
+            localized_amount = await self.model.received_amount(received_list)
+            text = self.tr("Received {amount} from {number} transfers").format(amount=localized_amount,
+                                                                           number=len(received_list))
+            if self.model.notifications():
+                toast.display(self.tr("New transactions received"), text)
+
+    def refresh_balance(self):
+        localized_amount = self.model.localized_balance()
+        self.view.set_balance(localized_amount)
+
+    def history_context_menu(self, point):
+        index = self.view.table_history.indexAt(point)
+        valid, identity, transfer = self.model.table_data(index)
+        if valid:
+            if identity is None:
+                if transfer.issuer != self.model.connection.pubkey:
+                    pubkey = transfer.issuer
+                else:
+                    pubkey = transfer.receiver
+                identity = Identity(currency=transfer.currency, pubkey=pubkey)
+            menu = ContextMenu.from_data(self.view, self.model.app, self.model.connection, (identity, transfer))
+            menu.view_identity_in_wot.connect(self.view_in_wot)
+
+            # Show the context menu.
+            menu.qmenu.popup(QCursor.pos())
+
+    def dates_changed(self):
+        self._logger.debug("Changed dates")
+        if self.view.table_history.model():
+            qdate_from = self.view.date_from
+            qdate_from.setTime(QTime(0, 0, 0))
+            qdate_to = self.view.date_to
+            qdate_to.setTime(QTime(0, 0, 0))
+            ts_from = qdate_from.dateTime().toTime_t()
+            ts_to = qdate_to.dateTime().toTime_t()
+
+            self.view.table_history.model().set_period(ts_from, ts_to)
+
+            self.refresh_balance()
diff --git a/src/sakia/gui/navigation/txhistory/model.py b/src/sakia/gui/navigation/txhistory/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..f856c80690968df1ae3a320354210420f6819602
--- /dev/null
+++ b/src/sakia/gui/navigation/txhistory/model.py
@@ -0,0 +1,124 @@
+from PyQt5.QtCore import QObject
+from .table_model import HistoryTableModel, TxFilterProxyModel
+from PyQt5.QtCore import Qt, QDateTime, QTime, pyqtSignal, QModelIndex
+from sakia.errors import NoPeerAvailable
+from duniterpy.api import errors
+
+import logging
+
+
+class TxHistoryModel(QObject):
+    """
+    The model of Navigation component
+    """
+    def __init__(self, parent, app, connection, blockchain_service, identities_service,
+                 transactions_service, sources_service):
+        """
+
+        :param sakia.gui.txhistory.TxHistoryParent parent: the parent controller
+        :param sakia.app.Application app: the app
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        :param sakia.services.IdentitiesService identities_service: the identities service
+        :param sakia.services.TransactionsService transactions_service: the identities service
+        :param sakia.services.SourcesService sources_service: the sources service
+        """
+        super().__init__(parent)
+        self.app = app
+        self.connection = connection
+        self.blockchain_service = blockchain_service
+        self.identities_service = identities_service
+        self.transactions_service = transactions_service
+        self.sources_service = sources_service
+        self._model = None
+        self._proxy = None
+
+    def init_history_table_model(self, ts_from, ts_to):
+        """
+        Generates a history table model
+        :param int ts_from: date from where to filter tx
+        :param int ts_to: date to where to filter tx
+        :return:
+        """
+        self._model = HistoryTableModel(self, self.app, self.connection,
+                                        self.identities_service, self.transactions_service)
+        self._proxy = TxFilterProxyModel(self, ts_from, ts_to, self.blockchain_service)
+        self._proxy.setSourceModel(self._model)
+        self._proxy.setDynamicSortFilter(True)
+        self._proxy.setSortRole(Qt.DisplayRole)
+        self._model.init_transfers()
+        self.app.new_transfer.connect(self._model.add_transfer)
+        self.app.new_dividend.connect(self._model.add_dividend)
+        self.app.transaction_state_changed.connect(self._model.change_transfer)
+        self.app.referential_changed.connect(self._model.modelReset)
+
+        return self._proxy
+
+    def table_data(self, index):
+        """
+        Gets available table data at given index
+        :param index:
+        :return: tuple containing (Identity, Transfer)
+        """
+        if index.isValid() and index.row() < self.table_model.rowCount(QModelIndex()):
+            source_index = self.table_model.mapToSource(index)
+
+            pubkey_col = self.table_model.sourceModel().columns_types.index('pubkey')
+            pubkey_index = self.table_model.sourceModel().index(source_index.row(), pubkey_col)
+            pubkey = self.table_model.sourceModel().data(pubkey_index, Qt.DisplayRole)
+
+            identity = self.identities_service.get_identity(pubkey)
+
+            transfer = self.table_model.sourceModel().transfers()[source_index.row()]
+            return True, identity, transfer
+        return False, None, None
+
+    def minimum_maximum_datetime(self):
+        """
+        Get minimum and maximum datetime
+        :rtype: Tuple[PyQt5.QtCore.QDateTime, PyQt5.QtCore.QDateTime]
+        :return: minimum and maximum datetime
+        """
+        minimum_datetime = QDateTime()
+        minimum_datetime.setTime_t(0)
+        tomorrow_datetime = QDateTime().currentDateTime().addDays(1)
+        return minimum_datetime, tomorrow_datetime
+
+    async def received_amount(self, received_list):
+        """
+        Converts a list of transactions to an amount
+        :param list received_list:
+        :return: the amount, localized
+        """
+        amount = 0
+        for r in received_list:
+            amount += r.metadata['amount']
+        localized_amount = await self.app.current_ref.instance(amount,
+                                                               self.connection.currency,
+                                                               self.app).localized(True, True)
+        return localized_amount
+
+    def localized_balance(self):
+        """
+        Get the localized amount of the given tx history
+        :return: the localized amount of given account in given community
+        :rtype: int
+        """
+        try:
+            amount = self.sources_service.amount(self.connection.pubkey)
+            localized_amount = self.app.current_ref.instance(amount,
+                                                             self.connection.currency,
+                                                             self.app).localized(False, True)
+            return localized_amount
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+        except errors.DuniterError as e:
+            logging.debug(str(e))
+        return self.tr("Loading...")
+
+    @property
+    def table_model(self):
+        return self._proxy
+
+    def notifications(self):
+        return self.app.parameters.notifications
\ No newline at end of file
diff --git a/src/sakia/gui/navigation/txhistory/table_model.py b/src/sakia/gui/navigation/txhistory/table_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..40d588d8f3fe9ae5f3f811bf3b1602634bcfe1b6
--- /dev/null
+++ b/src/sakia/gui/navigation/txhistory/table_model.py
@@ -0,0 +1,347 @@
+import datetime
+import logging
+
+from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \
+    QDateTime, QLocale, QModelIndex
+from PyQt5.QtGui import QFont, QColor
+from sakia.data.entities import Transaction
+from sakia.constants import MAX_CONFIRMATIONS
+from sakia.data.processors import BlockchainProcessor
+
+
+class TxFilterProxyModel(QSortFilterProxyModel):
+    def __init__(self, parent, ts_from, ts_to, blockchain_service):
+        """
+        History of all transactions
+        :param PyQt5.QtWidgets.QWidget parent: parent widget
+        :param int ts_from: the min timestamp of latest tx
+        :param in ts_to: the max timestamp of most recent tx
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        """
+        super().__init__(parent)
+        self.app = None
+        self.ts_from = ts_from
+        self.ts_to = ts_to
+        self.blockchain_service = blockchain_service
+
+    def set_period(self, ts_from, ts_to):
+        """
+        Filter table by given timestamps
+        """
+        logging.debug("Filtering from {0} to {1}".format(
+            datetime.datetime.fromtimestamp(ts_from).isoformat(' '),
+            datetime.datetime.fromtimestamp(ts_to).isoformat(' '))
+        )
+        self.beginResetModel()
+        self.ts_from = ts_from
+        self.ts_to = ts_to
+        self.endResetModel()
+
+    def filterAcceptsRow(self, sourceRow, sourceParent):
+        def in_period(date_ts):
+            return date_ts in range(self.ts_from, self.ts_to)
+
+        source_model = self.sourceModel()
+        date_col = source_model.columns_types.index('date')
+        source_index = source_model.index(sourceRow, date_col)
+        date = source_model.data(source_index, Qt.DisplayRole)
+
+        return in_period(date)
+
+    def columnCount(self, parent):
+        return self.sourceModel().columnCount(None) - 5
+
+    def setSourceModel(self, source_model):
+        self.app = source_model.app
+        super().setSourceModel(source_model)
+
+    def lessThan(self, left, right):
+        """
+        Sort table by given column number.
+        """
+        source_model = self.sourceModel()
+        left_data = source_model.data(left, Qt.DisplayRole)
+        right_data = source_model.data(right, Qt.DisplayRole)
+        if left_data == "":
+            return self.sortOrder() == Qt.DescendingOrder
+        elif right_data == "":
+            return self.sortOrder() == Qt.AscendingOrder
+        if left_data == right_data:
+            txid_col = source_model.columns_types.index('txid')
+            txid_left = source_model.index(left.row(), txid_col)
+            txid_right = source_model.index(right.row(), txid_col)
+            return txid_left < txid_right
+
+        return left_data < right_data
+
+    def data(self, index, role):
+        source_index = self.mapToSource(index)
+        model = self.sourceModel()
+        source_data = model.data(source_index, role)
+        state_col = model.columns_types.index('state')
+        state_index = model.index(source_index.row(), state_col)
+        state_data = model.data(state_index, Qt.DisplayRole)
+
+        block_col = model.columns_types.index('block_number')
+        block_index = model.index(source_index.row(), block_col)
+        block_data = model.data(block_index, Qt.DisplayRole)
+
+        if state_data == Transaction.VALIDATED:
+            current_confirmations = self.blockchain_service.current_buid().number - block_data
+        else:
+            current_confirmations = 0
+
+        if role == Qt.DisplayRole:
+            if source_index.column() == model.columns_types.index('uid'):
+                return source_data
+            if source_index.column() == model.columns_types.index('date'):
+                return QLocale.toString(
+                    QLocale(),
+                    QDateTime.fromTime_t(source_data).date(),
+                    QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
+                )
+            if source_index.column() == model.columns_types.index('amount'):
+                amount = self.app.current_ref.instance(source_data, model.connection.currency,
+                                                       self.app, block_data).diff_localized(False, True)
+                return amount
+
+        if role == Qt.FontRole:
+            font = QFont()
+            if state_data == Transaction.AWAITING or \
+                    (state_data == Transaction.VALIDATED and current_confirmations < MAX_CONFIRMATIONS):
+                font.setItalic(True)
+            elif state_data == Transaction.REFUSED:
+                font.setItalic(True)
+            elif state_data == Transaction.TO_SEND:
+                font.setBold(True)
+            else:
+                font.setItalic(False)
+            return font
+
+        if role == Qt.ForegroundRole:
+            if state_data == Transaction.REFUSED:
+                return QColor(Qt.darkGray)
+            elif state_data == Transaction.TO_SEND:
+                return QColor(Qt.blue)
+            if source_index.column() == model.columns_types.index('amount'):
+                if source_data < 0:
+                    return QColor(Qt.darkRed)
+                elif state_data == HistoryTableModel.DIVIDEND:
+                    return QColor(Qt.darkBlue)
+
+        if role == Qt.TextAlignmentRole:
+            if self.sourceModel().columns_types.index('amount'):
+                return Qt.AlignRight | Qt.AlignVCenter
+            if source_index.column() == model.columns_types.index('date'):
+                return Qt.AlignCenter
+
+        if role == Qt.ToolTipRole:
+            if source_index.column() == model.columns_types.index('date'):
+                return QDateTime.fromTime_t(source_data).toString(Qt.SystemLocaleLongDate)
+
+            if state_data == Transaction.VALIDATED or state_data == Transaction.AWAITING:
+                if current_confirmations >= MAX_CONFIRMATIONS:
+                    return None
+                elif self.app.parameters.expert_mode:
+                    return self.tr("{0} / {1} confirmations").format(current_confirmations, MAX_CONFIRMATIONS)
+                else:
+                    confirmation = current_confirmations / MAX_CONFIRMATIONS * 100
+                    confirmation = 100 if confirmation > 100 else confirmation
+                    return self.tr("Confirming... {0} %").format(QLocale().toString(float(confirmation), 'f', 0))
+
+            return None
+        return source_data
+
+
+class HistoryTableModel(QAbstractTableModel):
+    """
+    A Qt abstract item model to display communities in a tree
+    """
+
+    DIVIDEND = 32
+
+    def __init__(self, parent, app, connection, identities_service, transactions_service):
+        """
+        History of all transactions
+        :param PyQt5.QtWidgets.QWidget parent: parent widget
+        :param sakia.app.Application app: the main application
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.services.IdentitiesService identities_service: the identities service
+        :param sakia.services.TransactionsService transactions_service: the transactions service
+        """
+        super().__init__(parent)
+        self.app = app
+        self.connection = connection
+        self.blockchain_processor = BlockchainProcessor.instanciate(app)
+        self.identities_service = identities_service
+        self.transactions_service = transactions_service
+        self.transfers_data = []
+
+        self.columns_types = (
+            'date',
+            'uid',
+            'amount',
+            'comment',
+            'state',
+            'txid',
+            'pubkey',
+            'block_number',
+            'txhash'
+        )
+
+        self.column_headers = (
+            lambda: self.tr('Date'),
+            lambda: self.tr('UID/Public key'),
+            lambda: self.tr('Amount'),
+            lambda: self.tr('Comment')
+        )
+
+    def transfers(self):
+        """
+        Transfer
+        :rtype: List[sakia.data.entities.Transfer]
+        """
+        return self.transactions_service.transfers(self.connection.pubkey)
+
+    def dividends(self):
+        """
+        Transfer
+        :rtype: List[sakia.data.entities.Dividend]
+        """
+        return self.transactions_service.dividends(self.connection.pubkey)
+
+    def add_transfer(self, transfer):
+        self.beginInsertRows(QModelIndex(), len(self.transfers_data), len(self.transfers_data))
+        if transfer.issuer == self.connection.pubkey:
+            self.transfers_data.append(self.data_sent(transfer))
+        else:
+            self.transfers_data.append(self.data_received(transfer))
+        self.endInsertRows()
+
+    def add_dividend(self, dividend):
+        self.beginInsertRows(QModelIndex(), 0, 0)
+        self.transfers_data.append(self.data_dividend(dividend))
+        self.endInsertRows()
+
+    def change_transfer(self, transfer):
+        for i, data in enumerate(self.transfers_data):
+            if data[self.columns_types.index('txhash')] == transfer.sha_hash:
+                if transfer.state == Transaction.DROPPED:
+                    self.beginRemoveRows(QModelIndex(), i, i)
+                    self.transfers_data.pop(i)
+                    self.endRemoveRows()
+                elif transfer.issuer == self.connection.pubkey:
+                    self.transfers_data[i] = self.data_sent(transfer)
+                    self.dataChanged.emit(self.index(i, 0), self.index(i, len(self.columns_types)))
+                else:
+                    self.transfers_data[i] = self.data_received(transfer)
+                    self.dataChanged.emit(self.index(i, 0), self.index(i, len(self.columns_types)))
+                return
+
+    def data_received(self, transfer):
+        """
+        Converts a transaction to table data
+        :param sakia.data.entities.Transaction transfer: the transaction
+        :return: data as tuple
+        """
+        block_number = transfer.written_block
+
+        amount = transfer.amount * 10**transfer.amount_base
+
+        identity = self.identities_service.get_identity(transfer.issuer)
+        if identity:
+            sender = identity.uid
+        else:
+            sender = transfer.issuer
+
+        date_ts = transfer.timestamp
+        txid = transfer.txid
+
+        return (date_ts, sender, amount,
+                transfer.comment, transfer.state, txid,
+                transfer.issuer, block_number, transfer.sha_hash)
+
+    def data_sent(self, transfer):
+        """
+        Converts a transaction to table data
+        :param sakia.data.entities.Transaction transfer: the transaction
+        :return: data as tuple
+        """
+        block_number = transfer.written_block
+
+        amount = transfer.amount * 10**transfer.amount_base * -1
+        identity = self.identities_service.get_identity(transfer.receiver)
+        if identity:
+            receiver = identity.uid
+        else:
+            receiver = transfer.receiver
+
+        date_ts = transfer.timestamp
+        txid = transfer.txid
+        return (date_ts, receiver, amount, transfer.comment, transfer.state, txid,
+                transfer.receiver, block_number, transfer.sha_hash)
+
+    def data_dividend(self, dividend):
+        """
+        Converts a transaction to table data
+        :param sakia.data.entities.Dividend dividend: the dividend
+        :return: data as tuple
+        """
+        block_number = dividend.block_number
+
+        amount = dividend.amount * 10**dividend.base
+        identity = self.identities_service.get_identity(dividend.pubkey)
+        if identity:
+            receiver = identity.uid
+        else:
+            receiver = dividend.pubkey
+
+        date_ts = dividend.timestamp
+        return (date_ts, receiver, amount, "", HistoryTableModel.DIVIDEND, 0,
+                receiver, block_number, "")
+
+    def init_transfers(self):
+        self.beginResetModel()
+        self.transfers_data = []
+        transfers = self.transfers()
+        for transfer in transfers:
+            if transfer.state != Transaction.DROPPED:
+                if transfer.issuer == self.connection.pubkey:
+                    self.transfers_data.append(self.data_sent(transfer))
+                else:
+                    self.transfers_data.append(self.data_received(transfer))
+        dividends = self.dividends()
+        for dividend in dividends:
+            self.transfers_data.append(self.data_dividend(dividend))
+        self.endResetModel()
+
+    def rowCount(self, parent):
+        return len(self.transfers_data)
+
+    def columnCount(self, parent):
+        return len(self.columns_types)
+
+    def headerData(self, section, orientation, role):
+        if role == Qt.DisplayRole:
+            if self.columns_types[section] == 'amount':
+                dividend, base = self.blockchain_processor.last_ud(self.transactions_service.currency)
+                header = '{:}'.format(self.column_headers[section]())
+                if self.app.current_ref.base_str(base):
+                    header += "\n({:})".format(self.app.current_ref.base_str(base))
+                return header
+
+            return self.column_headers[section]()
+
+    def data(self, index, role):
+        row = index.row()
+        col = index.column()
+
+        if not index.isValid():
+            return QVariant()
+
+        if role in (Qt.DisplayRole, Qt.ForegroundRole, Qt.ToolTipRole):
+            return self.transfers_data[row][col]
+
+    def flags(self, index):
+        return Qt.ItemIsSelectable | Qt.ItemIsEnabled
+
diff --git a/res/ui/transactions_tab.ui b/src/sakia/gui/navigation/txhistory/txhistory.ui
similarity index 88%
rename from res/ui/transactions_tab.ui
rename to src/sakia/gui/navigation/txhistory/txhistory.ui
index 737ecfa4aa93d0e094da40eed917ae4b71e16457..838345c54cfeb45b29caea9b3b1dc9f9d1f905d1 100644
--- a/res/ui/transactions_tab.ui
+++ b/src/sakia/gui/navigation/txhistory/txhistory.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>transactionsTabWidget</class>
- <widget class="QWidget" name="transactionsTabWidget">
+ <class>TxHistoryWidget</class>
+ <widget class="QWidget" name="TxHistoryWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -72,16 +72,6 @@
        </item>
       </layout>
      </item>
-     <item>
-      <widget class="QProgressBar" name="progressbar">
-       <property name="maximum">
-        <number>0</number>
-       </property>
-       <property name="value">
-        <number>-1</number>
-       </property>
-      </widget>
-     </item>
      <item>
       <widget class="QTableView" name="table_history">
        <property name="contextMenuPolicy">
@@ -117,7 +107,7 @@
   </customwidget>
  </customwidgets>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../../../res/icons/icons.qrc"/>
  </resources>
  <connections/>
 </ui>
diff --git a/src/sakia/gui/navigation/txhistory/view.py b/src/sakia/gui/navigation/txhistory/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a67135394c1e8addc65620c1f778ba907a4e6d1
--- /dev/null
+++ b/src/sakia/gui/navigation/txhistory/view.py
@@ -0,0 +1,67 @@
+from PyQt5.QtWidgets import QWidget, QAbstractItemView, QHeaderView
+from PyQt5.QtCore import QDateTime, QEvent, Qt
+from .txhistory_uic import Ui_TxHistoryWidget
+
+
+class TxHistoryView(QWidget, Ui_TxHistoryWidget):
+    """
+    The view of TxHistory component
+    """
+
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.setupUi(self)
+
+    def get_time_frame(self):
+        """
+        Get the time frame of date filters
+        :return: the timestamps of the date filters
+        """
+        return self.date_from.dateTime().toTime_t(), self.date_to.dateTime().toTime_t()
+
+    def set_table_history_model(self, model):
+        """
+        Define the table history model
+        :param QAbstractItemModel model:
+        :return:
+        """
+        self.table_history.setModel(model)
+        self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.table_history.setSortingEnabled(True)
+        self.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
+        model.modelReset.connect(self.table_history.resizeColumnsToContents)
+
+    def set_minimum_maximum_datetime(self, minimum, maximum):
+        """
+        Configure minimum and maximum datetime in date filter
+        :param PyQt5.QtCore.QDateTime minimum: the minimum
+        :param PyQt5.QtCore.QDateTime maximum: the maximum
+        """
+        self.date_from.setMinimumDateTime(minimum)
+        self.date_from.setDateTime(minimum)
+        self.date_from.setMaximumDateTime(QDateTime().currentDateTime())
+
+        self.date_to.setMinimumDateTime(minimum)
+        self.date_to.setDateTime(maximum)
+        self.date_to.setMaximumDateTime(maximum)
+
+    def set_balance(self, balance):
+        """
+        Display given balance
+        :param balance: the given balance to display
+        :return:
+        """
+        # set infos in label
+        self.label_balance.setText(
+            "{:}".format(balance)
+        )
+
+    def changeEvent(self, event):
+        """
+        Intercepte LanguageChange event to translate UI
+        :param QEvent QEvent: Event
+        :return:
+        """
+        if event.type() == QEvent.LanguageChange:
+            self.retranslateUi(self)
+        super().changeEvent(event)
\ No newline at end of file
diff --git a/src/sakia/gui/navigation/view.py b/src/sakia/gui/navigation/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c5acc029ba1ffda077d4f10c3fe6e8e33dfe1cb
--- /dev/null
+++ b/src/sakia/gui/navigation/view.py
@@ -0,0 +1,55 @@
+from PyQt5.QtWidgets import QFrame, QSizePolicy
+from PyQt5.QtCore import pyqtSignal
+from .navigation_uic import Ui_Navigation
+from sakia.models.generic_tree import GenericTreeModel
+
+
+class NavigationView(QFrame, Ui_Navigation):
+    """
+    The view of navigation panel
+    """
+    current_view_changed = pyqtSignal(dict)
+
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.setupUi(self)
+        self.tree_view.clicked.connect(self.handle_click)
+        self.tree_view.setItemsExpandable(False)
+
+    def set_model(self, model):
+        """
+        Change the navigation pane
+        :param sakia.gui.navigation.model.NavigationModel
+        """
+        self.tree_view.setModel(model.generic_tree())
+        self.tree_view.expandAll()
+
+    def add_widget(self, widget):
+        """
+        Add a widget to the stacked_widget
+        :param PyQt5.QtWidgets.QWidget widget: the new widget
+        """
+        self.stacked_widget.addWidget(widget)
+        return widget
+
+    def handle_click(self, index):
+        """
+        Click on the navigation pane
+        :param PyQt5.QtCore.QModelIndex index: the index
+        :return:
+        """
+        if index.isValid():
+            raw_data = self.tree_view.model().data(index, GenericTreeModel.ROLE_RAW_DATA)
+            if 'widget' in raw_data:
+                widget = raw_data['widget']
+                if self.stacked_widget.indexOf(widget) != -1:
+                    self.stacked_widget.setCurrentWidget(widget)
+                    self.current_view_changed.emit(raw_data)
+
+    def add_connection(self, raw_data):
+        self.tree_view.model().insert_node(raw_data)
+        self.tree_view.expandAll()
+
+    def remove_connection(self, raw_data):
+        self.tree_view.model().remove_node(raw_data)
+        self.tree_view.expandAll()
diff --git a/src/sakia/gui/network_tab.py b/src/sakia/gui/network_tab.py
deleted file mode 100644
index ace7c59e9a5febad3462debcbb5a8735c9ebcc3a..0000000000000000000000000000000000000000
--- a/src/sakia/gui/network_tab.py
+++ /dev/null
@@ -1,125 +0,0 @@
-"""
-Created on 20 févr. 2015
-
-@author: inso
-"""
-
-import logging
-import asyncio
-
-from PyQt5.QtGui import QCursor, QDesktopServices
-from PyQt5.QtWidgets import QWidget, QMenu, QAction
-from PyQt5.QtCore import Qt, QModelIndex, pyqtSlot, QUrl, QEvent
-from ..models.network import NetworkTableModel, NetworkFilterProxyModel
-from duniterpy.api import bma
-from ..gen_resources.network_tab_uic import Ui_NetworkTabWidget
-
-
-class NetworkTabWidget(QWidget, Ui_NetworkTabWidget):
-    """
-    classdocs
-    """
-
-    def __init__(self, app):
-        """
-        Constructore of a network tab.
-
-        :param sakia.core.Application app: The application
-        :return: A new network tab.
-        :rtype: NetworkTabWidget
-        """
-        super().__init__()
-        self.app = app
-        self.community = None
-
-        self.setupUi(self)
-        model = NetworkTableModel(self.community)
-        proxy = NetworkFilterProxyModel()
-        proxy.setSourceModel(model)
-        self.table_network.setModel(proxy)
-        self.table_network.sortByColumn(2, Qt.DescendingOrder)
-        self.table_network.resizeColumnsToContents()
-        model.modelAboutToBeReset.connect(lambda: self.table_network.setEnabled(False))
-        model.modelReset.connect(lambda: self.table_network.setEnabled(True))
-
-    def change_community(self, community):
-        if self.community:
-            self.community.network.nodes_changed.disconnect(self.refresh_nodes)
-        if community:
-            community.network.nodes_changed.connect(self.refresh_nodes)
-
-        self.community = community
-        refresh_task = self.table_network.model().change_community(community)
-        refresh_task.add_done_callback(lambda fut: self.table_network.resizeColumnsToContents())
-
-    @pyqtSlot()
-    def refresh_nodes(self):
-        logging.debug("Refresh nodes")
-        refresh_task = self.table_network.model().sourceModel().refresh_nodes()
-        refresh_task.add_done_callback(lambda fut: self.table_network.resizeColumnsToContents())
-
-    def node_context_menu(self, point):
-        index = self.table_network.indexAt(point)
-        model = self.table_network.model()
-        if index.isValid() and index.row() < model.rowCount(QModelIndex()):
-            source_index = model.mapToSource(index)
-            is_root_col = model.sourceModel().columns_types.index('is_root')
-            is_root_index = model.sourceModel().index(source_index.row(), is_root_col)
-            is_root = model.sourceModel().data(is_root_index, Qt.DisplayRole)
-
-            menu = QMenu()
-            if is_root:
-                unset_root = QAction(self.tr("Unset root node"), self)
-                unset_root.triggered.connect(self.unset_root_node)
-                unset_root.setData(self.community.network.root_node_index(source_index.row()))
-                if len(self.community.network.root_nodes) > 1:
-                    menu.addAction(unset_root)
-            else:
-                set_root = QAction(self.tr("Set as root node"), self)
-                set_root.triggered.connect(self.set_root_node)
-                set_root.setData(self.community.network.nodes[source_index.row()])
-                menu.addAction(set_root)
-
-            if self.app.preferences['expert_mode']:
-                open_in_browser = QAction(self.tr("Open in browser"), self)
-                open_in_browser.triggered.connect(self.open_node_in_browser)
-                open_in_browser.setData(self.community.network.nodes[source_index.row()])
-                menu.addAction(open_in_browser)
-
-            # Show the context menu.
-            menu.exec_(QCursor.pos())
-
-    @pyqtSlot()
-    def set_root_node(self):
-        node = self.sender().data()
-        self.community.network.add_root_node(node)
-        self.table_network.model().sourceModel().refresh_nodes()
-
-    @pyqtSlot()
-    def unset_root_node(self):
-        index = self.sender().data()
-        self.community.network.remove_root_node(index)
-        self.table_network.model().sourceModel().refresh_nodes()
-
-    @pyqtSlot()
-    def open_node_in_browser(self):
-        node = self.sender().data()
-        peering = bma.network.Peering(node.endpoint.conn_handler())
-        url = QUrl(peering.reverse_url("http", "/peering"))
-        QDesktopServices.openUrl(url)
-
-    def manual_nodes_refresh(self):
-        self.community.network.refresh_once()
-        self.button_manual_refresh.setEnabled(False)
-        asyncio.get_event_loop().call_later(15, lambda: self.button_manual_refresh.setEnabled(True))
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-            self.refresh_nodes()
-        return super(NetworkTabWidget, self).changeEvent(event)
diff --git a/src/sakia/gui/node_manager.py b/src/sakia/gui/node_manager.py
deleted file mode 100644
index 7dd8432442563ffc58754f7c52ccc8a2102f0ace..0000000000000000000000000000000000000000
--- a/src/sakia/gui/node_manager.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import aiohttp
-
-from PyQt5.QtCore import QObject, QEvent, QUrl
-from PyQt5.QtWidgets import QDialog
-
-#from ..gen_resources.node_manager_uic import Ui_NodeManager
-from .widgets.dialogs import QAsyncMessageBox
-from ..tools.decorators import asyncify
-
-
-class NodeManager(QObject):
-    """
-    A widget showing informations about a member
-    """
-
-    def __init__(self, widget, ui):
-        """
-        Init MemberDialog
-
-        :param PyQt5.QtWidget widget: The class of the widget
-        :param sakia.gen_resources.member_uic.Ui_DialogMember ui: the class of the ui applyed to the widget
-        :return:
-        """
-        super().__init__()
-        self.widget = widget
-        self.ui = ui
-        self.ui.setupUi(self.widget)
-        self.widget.installEventFilter(self)
-
-    @classmethod
-    def create(cls, parent):
-        raise TypeError("Not implemented ( https://github.com/duniter/sakia/issues/399 )")
-        #dialog = cls(QDialog(parent), Ui_NodeManager())
-        #return dialog
-
-    @asyncify
-    async def open_home_page(self):
-        try:
-            with aiohttp.ClientSession() as session:
-                response = await session.get("http://127.0.0.1:9220")
-                if response.status == 200:
-                    self.ui.web_view.load(QUrl("http://127.0.0.1:9220"))
-                    self.ui.web_view.show()
-                    self.widget.show()
-                else:
-                    await QAsyncMessageBox.critical(self.widget, "Local node manager",
-                                            "Could not access to local node ui.")
-        except aiohttp.ClientError:
-            await QAsyncMessageBox.critical(self.widget, "Local node manager",
-                                      "Could not connect to node. Please make sure it's running.")
-
-    def eventFilter(self, source, event):
-        if event.type() == QEvent.Resize:
-            self.widget.resizeEvent(event)
-        return self.widget.eventFilter(source, event)
-
-    def exec(self):
-        self.widget.exec()
diff --git a/src/sakia/gui/password_asker.py b/src/sakia/gui/password_asker.py
index be1b97c3a64a2e4e09ff76e638f8797f94aeb707..865bf2baf7efe7b6741b76c709650491dc54ce34 100644
--- a/src/sakia/gui/password_asker.py
+++ b/src/sakia/gui/password_asker.py
@@ -7,10 +7,11 @@ Created on 24 dec. 2014
 import logging
 import re
 import asyncio
+from duniterpy.key import SigningKey
 from PyQt5.QtCore import QEvent
 from PyQt5.QtWidgets import QDialog, QMessageBox
 
-from ..gen_resources.password_asker_uic import Ui_PasswordAskerDialog
+from .password_asker_uic import Ui_PasswordAskerDialog
 
 
 class PasswordAskerDialog(QDialog, Ui_PasswordAskerDialog):
@@ -19,47 +20,44 @@ class PasswordAskerDialog(QDialog, Ui_PasswordAskerDialog):
     A dialog to get password.
     """
 
-    def __init__(self, account):
+    def __init__(self, connection):
         """
         Constructor
+
+        :param sakia.data.entities.Connection connection: a given connection
         """
         super().__init__()
         self.setupUi(self)
-        self.account = account
         self.password = ""
+        self.connection = connection
         self.remember = False
 
-    def change_account(self, account):
-        self.remember = False
-        self.password = ""
-        self.account = account
-
     def async_exec(self):
         future = asyncio.Future()
-        if not self.remember:
+        if not self.connection.password:
             def future_show():
                 pwd = self.password
-                if not self.remember:
-                    self.password = ""
+                if self.remember:
+                    self.connection.password = self.password
                 self.finished.disconnect(future_show)
                 future.set_result(pwd)
             self.open()
             self.finished.connect(future_show)
         else:
             self.setResult(QDialog.Accepted)
-            future.set_result(self.password)
+            future.set_result(self.connection.password)
         return future
 
-    def exec_(self):
-        if not self.remember:
+    def exec(self):
+        if not self.connection.password:
             super().exec_()
             pwd = self.password
-            if not self.remember:
-                self.password = ""
+            if self.remember:
+                self.connection.password = self.password
             return pwd
         else:
             self.setResult(QDialog.Accepted)
-            return self.password
+            return self.connection.password
 
     def accept(self):
         password = self.edit_password.text()
@@ -70,7 +68,7 @@ class PasswordAskerDialog(QDialog, Ui_PasswordAskerDialog):
                                 QMessageBox.Ok)
             return False
 
-        if not self.account.check_password(password):
+        if SigningKey(self.connection.salt, password, self.connection.scrypt_params).pubkey != self.connection.pubkey:
             QMessageBox.warning(self, self.tr("Failed to get private key"),
                                 self.tr("Wrong password typed. Cannot open the private key"),
                                 QMessageBox.Ok)
diff --git a/res/ui/password_asker.ui b/src/sakia/gui/password_asker.ui
similarity index 100%
rename from res/ui/password_asker.ui
rename to src/sakia/gui/password_asker.ui
diff --git a/src/sakia/gui/preferences.py b/src/sakia/gui/preferences.py
index d497cf817233f7b55e6821d868e4572f2165086d..bc8368b738207d0955a334e8790f695d6d653fe1 100644
--- a/src/sakia/gui/preferences.py
+++ b/src/sakia/gui/preferences.py
@@ -7,8 +7,9 @@ Created on 11 mai 2015
 from PyQt5.QtCore import QCoreApplication
 from PyQt5.QtWidgets import QDialog
 
-from ..core import money
-from ..gen_resources.preferences_uic import Ui_PreferencesDialog
+from sakia import money
+from sakia.data.entities import UserParameters
+from .preferences_uic import Ui_PreferencesDialog
 
 
 class PreferencesDialog(QDialog, Ui_PreferencesDialog):
@@ -20,62 +21,53 @@ class PreferencesDialog(QDialog, Ui_PreferencesDialog):
     def __init__(self, app):
         """
         Constructor
+
+        :param sakia.app.Application app:
         """
         super().__init__()
         self.setupUi(self)
         self.app = app
-        self.combo_account.addItem("")
-        for account_name in self.app.accounts.keys():
-            self.combo_account.addItem(account_name)
-        self.combo_account.setCurrentText(self.app.preferences['account'])
         for ref in money.Referentials:
             self.combo_referential.addItem(QCoreApplication.translate('Account', ref.translated_name()))
-        self.combo_referential.setCurrentIndex(self.app.preferences['ref'])
-        for lang in ('en_GB', 'fr_FR', 'de_DE', 'es_ES', 'it_IT', 'pl_PL', 'pt_BR', 'ru_RU', 'cs_CZ'):
+        self.combo_referential.setCurrentIndex(self.app.parameters.referential)
+        for lang in ('en_US', 'fr_FR', 'de_DE', 'es_ES', 'it_IT', 'pl_PL', 'pt_BR', 'ru_RU'):
             self.combo_language.addItem(lang)
-        self.combo_language.setCurrentText(self.app.preferences.get('lang', 'en_US'))
-        self.checkbox_expertmode.setChecked(self.app.preferences.get('expert_mode', False))
-        self.checkbox_maximize.setChecked(self.app.preferences.get('maximized', False))
-        self.checkbox_notifications.setChecked(self.app.preferences.get('notifications', True))
-        self.checkbox_international_system.setChecked(self.app.preferences.get('international_system_of_units', True))
-        self.spinbox_digits_comma.setValue(self.app.preferences.get('digits_after_comma', 2))
+        self.combo_language.setCurrentText(self.app.parameters.lang)
+        self.checkbox_expertmode.setChecked(self.app.parameters.expert_mode)
+        self.checkbox_maximize.setChecked(self.app.parameters.maximized)
+        self.checkbox_notifications.setChecked(self.app.parameters.notifications)
+        self.spinbox_digits_comma.setValue(self.app.parameters.digits_after_comma)
         self.spinbox_digits_comma.setMaximum(12)
         self.spinbox_digits_comma.setMinimum(1)
         self.button_app.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(0))
         self.button_display.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(1))
         self.button_network.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(2))
 
-        self.checkbox_proxy.setChecked(self.app.preferences.get('enable_proxy', False))
+        self.checkbox_proxy.setChecked(self.app.parameters.enable_proxy)
         self.spinbox_proxy_port.setEnabled(self.checkbox_proxy.isChecked())
         self.edit_proxy_address.setEnabled(self.checkbox_proxy.isChecked())
         self.checkbox_proxy.stateChanged.connect(self.handle_proxy_change)
 
         self.spinbox_proxy_port.setMinimum(0)
         self.spinbox_proxy_port.setMaximum(55636)
-        self.spinbox_proxy_port.setValue(self.app.preferences.get('proxy_port', 8080))
-        self.edit_proxy_address.setText(self.app.preferences.get('proxy_address', ""))
-
-        self.checkbox_forgetfulness.setChecked(self.app.preferences.get('forgetfulness', True))
+        self.spinbox_proxy_port.setValue(self.app.parameters.proxy_port)
+        self.edit_proxy_address.setText(self.app.parameters.proxy_address)
 
     def handle_proxy_change(self):
         self.spinbox_proxy_port.setEnabled(self.checkbox_proxy.isChecked())
         self.edit_proxy_address.setEnabled(self.checkbox_proxy.isChecked())
 
     def accept(self):
-        pref = {'account': self.combo_account.currentText(),
-                'lang': self.combo_language.currentText(),
-                'ref': self.combo_referential.currentIndex(),
-                'expert_mode': self.checkbox_expertmode.isChecked(),
-                'maximized': self.checkbox_maximize.isChecked(),
-                'digits_after_comma': self.spinbox_digits_comma.value(),
-                'notifications': self.checkbox_notifications.isChecked(),
-                'enable_proxy': self.checkbox_proxy.isChecked(),
-                'proxy_address': self.edit_proxy_address.text(),
-                'proxy_port': self.spinbox_proxy_port.value(),
-                'international_system_of_units': self.checkbox_international_system.isChecked(),
-                'auto_refresh': self.checkbox_auto_refresh.isChecked(),
-                'forgetfulness': self.checkbox_forgetfulness.isChecked()}
-        self.app.save_preferences(pref)
+        parameters = UserParameters(lang=self.combo_language.currentText(),
+                                    referential=self.combo_referential.currentIndex(),
+                                    expert_mode=self.checkbox_expertmode.isChecked(),
+                                    maximized=self.checkbox_maximize.isChecked(),
+                                    digits_after_comma=self.spinbox_digits_comma.value(),
+                                    notifications=self.checkbox_notifications.isChecked(),
+                                    enable_proxy=self.checkbox_proxy.isChecked(),
+                                    proxy_address=self.edit_proxy_address.text(),
+                                    proxy_port=self.spinbox_proxy_port.value())
+        self.app.save_parameters(parameters)
       # change UI translation
         self.app.switch_language()
         super().accept()
diff --git a/res/ui/preferences.ui b/src/sakia/gui/preferences.ui
similarity index 79%
rename from res/ui/preferences.ui
rename to src/sakia/gui/preferences.ui
index ccc3312a7605ce316f1e91d60e5fa5021188fe49..6db3496fdd4d6337087a425ab6f8596cca7d606a 100644
--- a/res/ui/preferences.ui
+++ b/src/sakia/gui/preferences.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>469</width>
-    <height>328</height>
+    <height>330</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -30,7 +30,7 @@
           <string/>
          </property>
          <property name="icon">
-          <iconset resource="../icons/icons.qrc">
+          <iconset resource="../../../res/icons/icons.qrc">
            <normaloff>:/icons/settings_app_icon</normaloff>:/icons/settings_app_icon</iconset>
          </property>
          <property name="iconSize">
@@ -40,7 +40,7 @@
           </size>
          </property>
          <property name="flat">
-          <bool>true</bool>
+          <bool>false</bool>
          </property>
         </widget>
        </item>
@@ -50,7 +50,7 @@
           <string/>
          </property>
          <property name="icon">
-          <iconset resource="../icons/icons.qrc">
+          <iconset resource="../../../res/icons/icons.qrc">
            <normaloff>:/icons/settings_display_icon</normaloff>:/icons/settings_display_icon</iconset>
          </property>
          <property name="iconSize">
@@ -60,7 +60,7 @@
           </size>
          </property>
          <property name="flat">
-          <bool>true</bool>
+          <bool>false</bool>
          </property>
         </widget>
        </item>
@@ -70,7 +70,7 @@
           <string/>
          </property>
          <property name="icon">
-          <iconset resource="../icons/icons.qrc">
+          <iconset resource="../../../res/icons/icons.qrc">
            <normaloff>:/icons/settings_network_icon</normaloff>:/icons/settings_network_icon</iconset>
          </property>
          <property name="iconSize">
@@ -80,7 +80,7 @@
           </size>
          </property>
          <property name="flat">
-          <bool>true</bool>
+          <bool>false</bool>
          </property>
         </widget>
        </item>
@@ -96,7 +96,7 @@
      <item>
       <widget class="QStackedWidget" name="stackedWidget">
        <property name="currentIndex">
-        <number>0</number>
+        <number>1</number>
        </property>
        <widget class="QWidget" name="page">
         <layout class="QVBoxLayout" name="verticalLayout_7">
@@ -107,20 +107,6 @@
            </property>
           </widget>
          </item>
-         <item>
-          <layout class="QHBoxLayout" name="horizontalLayout">
-           <item>
-            <widget class="QLabel" name="label">
-             <property name="text">
-              <string>Default account</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QComboBox" name="combo_account"/>
-           </item>
-          </layout>
-         </item>
          <item>
           <layout class="QHBoxLayout" name="horizontalLayout_3">
            <item>
@@ -279,60 +265,6 @@
            </item>
           </layout>
          </item>
-         <item>
-          <layout class="QHBoxLayout" name="horizontalLayout_10">
-           <property name="topMargin">
-            <number>6</number>
-           </property>
-           <item>
-            <spacer name="horizontalSpacer_5">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>40</width>
-               <height>20</height>
-              </size>
-             </property>
-            </spacer>
-           </item>
-           <item>
-            <widget class="QCheckBox" name="checkbox_international_system">
-             <property name="text">
-              <string>Use International System of Units</string>
-             </property>
-            </widget>
-           </item>
-          </layout>
-         </item>
-         <item>
-          <layout class="QHBoxLayout" name="horizontalLayout_12">
-           <property name="topMargin">
-            <number>6</number>
-           </property>
-           <item>
-            <spacer name="horizontalSpacer_4">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>40</width>
-               <height>20</height>
-              </size>
-             </property>
-            </spacer>
-           </item>
-           <item>
-            <widget class="QCheckBox" name="checkbox_forgetfulness">
-             <property name="text">
-              <string>Enable forgetfulness</string>
-             </property>
-            </widget>
-           </item>
-          </layout>
-         </item>
          <item>
           <spacer name="verticalSpacer_2">
            <property name="orientation">
@@ -395,20 +327,6 @@
            </property>
           </widget>
          </item>
-         <item>
-          <layout class="QHBoxLayout" name="horizontalLayout_11">
-           <property name="topMargin">
-            <number>6</number>
-           </property>
-           <item>
-            <widget class="QCheckBox" name="checkbox_auto_refresh">
-             <property name="text">
-              <string>Automatically refresh identities informations</string>
-             </property>
-            </widget>
-           </item>
-          </layout>
-         </item>
          <item>
           <spacer name="verticalSpacer_3">
            <property name="orientation">
@@ -441,7 +359,7 @@
   </layout>
  </widget>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../res/icons/icons.qrc"/>
  </resources>
  <connections>
   <connection>
diff --git a/src/sakia/gui/process_cfg_account.py b/src/sakia/gui/process_cfg_account.py
deleted file mode 100644
index 0eede3a6d870693c2359dba1db7d2d5f5eb2e4d9..0000000000000000000000000000000000000000
--- a/src/sakia/gui/process_cfg_account.py
+++ /dev/null
@@ -1,322 +0,0 @@
-"""
-Created on 6 mars 2014
-
-@author: inso
-"""
-import logging
-import asyncio
-from math import sqrt, ceil, log, frexp
-from duniterpy.key import SigningKey, ScryptParams
-from ..gen_resources.account_cfg_uic import Ui_AccountConfigurationDialog
-from ..gui.process_cfg_community import ProcessConfigureCommunity
-from ..gui.password_asker import PasswordAskerDialog, detect_non_printable
-from ..gui.widgets.dialogs import QAsyncMessageBox
-from ..models.communities import CommunitiesListModel
-from ..tools.exceptions import KeyAlreadyUsed, Error, NoPeerAvailable
-from ..tools.decorators import asyncify
-
-from PyQt5.QtWidgets import QDialog, QMessageBox
-from PyQt5.QtCore import QRegExp, pyqtSlot
-from PyQt5.QtGui import QRegExpValidator
-
-
-class Step():
-    def __init__(self, config_dialog, previous_step=None, next_step=None):
-        self.previous_step = previous_step
-        self.next_step = next_step
-        self.config_dialog = config_dialog
-
-
-class StepPageInit(Step):
-    """
-    First step when adding a community
-    """
-
-    def __init__(self, config_dialog):
-        super().__init__(config_dialog)
-
-    def is_valid(self):
-        if len(self.config_dialog.edit_account_name.text()) > 2:
-            return True
-        else:
-            return False
-
-    def process_next(self):
-        if self.config_dialog.account is None:
-            name = self.config_dialog.edit_account_name.text()
-            self.config_dialog.account = self.config_dialog.app.create_account(name)
-        else:
-            name = self.config_dialog.edit_account_name.text()
-            self.config_dialog.account.name = name
-
-    def display_page(self):
-        if self.config_dialog.account is not None:
-            self.config_dialog.edit_account_name.setText(self.config_dialog.account.name)
-            model = CommunitiesListModel(self.config_dialog.account)
-            self.config_dialog.list_communities.setModel(model)
-            self.config_dialog.password_asker = PasswordAskerDialog(self.config_dialog.account)
-
-        self.config_dialog.button_previous.setEnabled(False)
-        self.config_dialog.button_next.setEnabled(False)
-
-
-class StepPageKey(Step):
-    """
-    First step when adding a community
-    """
-
-    def __init__(self, config_dialog):
-        super().__init__(config_dialog)
-
-    def is_valid(self):
-        if self.config_dialog.app.preferences['expert_mode']:
-            return True
-
-        if len(self.config_dialog.edit_salt.text()) < 6:
-            self.config_dialog.label_info.setText(self.config_dialog.tr("Forbidden : salt is too short"))
-            return False
-
-        if len(self.config_dialog.edit_password.text()) < 6:
-            self.config_dialog.label_info.setText(self.config_dialog.tr("Forbidden : password is too short"))
-            return False
-
-        if detect_non_printable(self.config_dialog.edit_salt.text()):
-            self.config_dialog.label_info.setText(self.config_dialog.tr("Forbidden : Invalid characters in salt field"))
-            return False
-
-        if detect_non_printable(self.config_dialog.edit_password.text()):
-            self.config_dialog.label_info.setText(
-                self.config_dialog.tr("Forbidden : Invalid characters in password field"))
-            return False
-
-        if self.config_dialog.edit_password.text() != \
-                self.config_dialog.edit_password_repeat.text():
-            self.config_dialog.label_info.setText(self.config_dialog.tr("Error : passwords are different"))
-            return False
-
-        self.config_dialog.label_info.setText("")
-        return True
-
-    def process_next(self):
-        salt = self.config_dialog.edit_salt.text()
-        password = self.config_dialog.edit_password.text()
-        self.config_dialog.account.set_scrypt_infos(salt, password, self.config_dialog.scrypt_params)
-        self.config_dialog.password_asker = PasswordAskerDialog(self.config_dialog.account)
-        model = CommunitiesListModel(self.config_dialog.account)
-        self.config_dialog.list_communities.setModel(model)
-
-    def display_page(self):
-        self.config_dialog.button_previous.setEnabled(False)
-        self.config_dialog.button_next.setEnabled(False)
-
-
-class StepPageCommunities(Step):
-    """
-    First step when adding a community
-    """
-
-    def __init__(self, config_dialog):
-        super().__init__(config_dialog)
-
-    def is_valid(self):
-        return True
-
-    def process_next(self):
-        password = self.config_dialog.password_asker.exec_()
-        if self.config_dialog.password_asker.result() == QDialog.Rejected:
-            return
-
-        self.config_dialog.app.add_account(self.config_dialog.account)
-        if len(self.config_dialog.app.accounts) == 1:
-            self.config_dialog.app.preferences['account'] = self.config_dialog.account.name
-        self.config_dialog.app.save(self.config_dialog.account)
-        self.config_dialog.app.change_current_account(self.config_dialog.account)
-
-    def display_page(self):
-        logging.debug("Communities DISPLAY")
-        self.config_dialog.button_previous.setEnabled(False)
-        self.config_dialog.button_next.setText("Ok")
-        list_model = CommunitiesListModel(self.config_dialog.account)
-        self.config_dialog.list_communities.setModel(list_model)
-
-
-class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog):
-    """
-    classdocs
-    """
-
-    def __init__(self, app, account):
-        """
-        Constructor
-        """
-        # Set up the user interface from Designer.
-        super().__init__()
-        self.setupUi(self)
-        regexp = QRegExp('[A-Za-z0-9_-]*')
-        validator = QRegExpValidator(regexp)
-        self.edit_account_name.setValidator(validator)
-        self.account = account
-        self.password_asker = None
-        self.app = app
-        step_init = StepPageInit(self)
-        step_key = StepPageKey(self)
-        step_communities = StepPageCommunities(self)
-        step_init.next_step = step_key
-        step_key.next_step = step_communities
-        self.combo_scrypt.currentIndexChanged.connect(self.handle_combo_change)
-        self.scrypt_params = ScryptParams(4096, 16, 1)
-        self.spin_N.setMaximum(2 ** 20)
-        self.spin_N.setValue(self.scrypt_params.N)
-        self.spin_N.valueChanged.connect(self.handle_N_change)
-        self.spin_r.setMaximum(128)
-        self.spin_r.setValue(self.scrypt_params.r)
-        self.spin_r.valueChanged.connect(self.handle_r_change)
-        self.spin_p.setMaximum(128)
-        self.spin_p.setValue(self.scrypt_params.p)
-        self.spin_p.valueChanged.connect(self.handle_p_change)
-        self.step = step_init
-        self.step.display_page()
-        if self.account is None:
-            self.setWindowTitle(self.tr("New account"))
-            self.button_delete.hide()
-        else:
-            self.label_action.setText("Edit account uid")
-            self.edit_account_name.setPlaceholderText(self.account.name)
-            self.stacked_pages.removeWidget(self.stacked_pages.widget(1))
-            step_init.next_step = step_communities
-            self.button_next.setEnabled(True)
-            self.stacked_pages.currentWidget()
-
-            self.setWindowTitle(self.tr("Configure {0}".format(self.account.name)))
-
-    def handle_combo_change(self, index):
-        strengths = [
-            (2**12, 16, 1),
-            (2**14, 32, 2),
-            (2**16, 32, 4),
-            (2**18, 64, 8),
-        ]
-        self.spin_N.setValue(strengths[index][0])
-        self.spin_r.setValue(strengths[index][1])
-        self.spin_p.setValue(strengths[index][2])
-
-    def handle_N_change(self, value):
-        spinbox = self.sender()
-        self.scrypt_params.N = self.compute_power_of_2(spinbox, value, self.scrypt_params.N)
-
-    def handle_r_change(self, value):
-        spinbox = self.sender()
-        self.scrypt_params.r = self.compute_power_of_2(spinbox, value, self.scrypt_params.r)
-
-    def handle_p_change(self, value):
-        spinbox = self.sender()
-        self.scrypt_params.p = self.compute_power_of_2(spinbox, value, self.scrypt_params.p)
-
-    def compute_power_of_2(self, spinbox, value, param):
-        if value > 1:
-            if value > param:
-                value = pow(2, ceil(log(value) / log(2)))
-            else:
-                value -= 1
-                value = 2 ** int(log(value, 2))
-        else:
-            value = 1
-
-        spinbox.blockSignals(True)
-        spinbox.setValue(value)
-        spinbox.blockSignals(False)
-        return value
-
-    def open_process_add_community(self):
-        logging.debug("Opening configure community dialog")
-        logging.debug(self.password_asker)
-        dialog = ProcessConfigureCommunity(self.app,
-                                           self.account, None,
-                                           self.password_asker)
-        dialog.accepted.connect(self.action_add_community)
-        dialog.exec_()
-
-    def action_add_community(self):
-        logging.debug("Action add community : done")
-        self.list_communities.setModel(CommunitiesListModel(self.account))
-        self.button_next.setEnabled(True)
-        self.button_next.setText(self.tr("Ok"))
-
-    def action_remove_community(self):
-        for index in self.list_communities.selectedIndexes():
-            self.account.communities.pop(index.row())
-
-        self.list_communities.setModel(CommunitiesListModel(self.account))
-
-    def action_edit_community(self):
-        self.list_communities.setModel(CommunitiesListModel(self.account))
-
-    def action_edit_account_key(self):
-        self.button_generate.setEnabled(self.step.is_valid())
-        self.button_next.setEnabled(self.step.is_valid())
-
-    def action_show_pubkey(self):
-        salt = self.edit_salt.text()
-        password = self.edit_password.text()
-        N = self.spin_N.value()
-        r = self.spin_r.value()
-        p = self.spin_p.value()
-        pubkey = SigningKey(salt, password, ScryptParams(N, r, p)).pubkey
-        self.label_info.setText(pubkey)
-
-    def action_edit_account_parameters(self):
-        if self.step.is_valid():
-            self.button_next.setEnabled(True)
-        else:
-            self.button_next.setEnabled(False)
-
-    def open_process_edit_community(self, index):
-        community = self.account.communities[index.row()]
-        dialog = ProcessConfigureCommunity(self.app, self.account, community, self.password_asker)
-
-        dialog.accepted.connect(self.action_edit_community)
-        dialog.exec_()
-
-    @asyncify
-    async def action_delete_account(self, checked=False):
-        reply = await QAsyncMessageBox.question(self, self.tr("Warning"),
-                                     self.tr("""This action will delete your account ({0}) locally.
-Please note your key parameters (salt and password) if you wish to recover it later.
-Your account won't be removed from the networks it joined.
-Are you sure ?""").format(self.app.current_account.name))
-        if reply == QMessageBox.Yes:
-            account = self.app.current_account
-            await self.app.delete_account(account)
-            self.app.save(account)
-            self.accept()
-
-    def next(self):
-        if self.step.is_valid():
-            try:
-                self.step.process_next()
-                if self.step.next_step is not None:
-                    self.step = self.step.next_step
-                    next_index = self.stacked_pages.currentIndex() + 1
-                    self.stacked_pages.setCurrentIndex(next_index)
-                    self.step.display_page()
-                else:
-                    self.accept()
-            except Error as e:
-                QMessageBox.critical(self, self.tr("Error"),
-                                     str(e), QMessageBox.Ok)
-
-    def previous(self):
-        if self.step.previous_step is not None:
-            self.step = self.step.previous_step
-            previous_index = self.stacked_pages.currentIndex() - 1
-            self.stacked_pages.setCurrentIndex(previous_index)
-            self.step.display_page()
-
-    def async_exec(self):
-        future = asyncio.Future()
-        self.finished.connect(lambda r: future.set_result(r))
-        self.open()
-        return future
-
-    def accept(self):
-        super().accept()
diff --git a/src/sakia/gui/process_cfg_community.py b/src/sakia/gui/process_cfg_community.py
deleted file mode 100644
index 5717579fb5dac384cc8501a94bd48e9f9578c64e..0000000000000000000000000000000000000000
--- a/src/sakia/gui/process_cfg_community.py
+++ /dev/null
@@ -1,321 +0,0 @@
-"""
-Created on 8 mars 2014
-
-@author: inso
-"""
-
-import logging
-import asyncio
-
-import aiohttp
-
-from duniterpy.api import errors
-from duniterpy.documents import MalformedDocumentError
-from PyQt5.QtWidgets import QDialog, QMenu, QApplication
-from PyQt5.QtGui import QCursor
-from PyQt5.QtCore import pyqtSignal, QObject
-
-from ..gen_resources.community_cfg_uic import Ui_CommunityConfigurationDialog
-from ..models.peering import PeeringTreeModel
-from ..core import Community
-from ..core.net import Node
-from .widgets import toast
-from .widgets.dialogs import QAsyncMessageBox
-from ..tools.decorators import asyncify
-from ..tools.exceptions import NoPeerAvailable
-
-
-class Step(QObject):
-    def __init__(self, config_dialog, previous_step=None, next_step=None):
-        super().__init__()
-        self.previous_step = previous_step
-        self.next_step = next_step
-        self.config_dialog = config_dialog
-
-
-class StepPageInit(Step):
-    """
-    First step when adding a community
-    """
-    def __init__(self, config_dialog):
-        super().__init__(config_dialog)
-        self.node = None
-        logging.debug("Init")
-        self.config_dialog.button_connect.clicked.connect(self.check_connect)
-        self.config_dialog.button_register.clicked.connect(self.check_register)
-        self.config_dialog.button_guest.clicked.connect(self.check_guest)
-
-    @property
-    def app(self):
-        return self.config_dialog.app
-
-    @property
-    def account(self):
-        return self.config_dialog.account
-
-    @property
-    def community(self):
-        return self.config_dialog.community
-
-    @property
-    def password_asker(self):
-        return self.config_dialog.password_asker
-
-    @asyncify
-    async def check_guest(self, checked=False):
-        server = self.config_dialog.lineedit_server.text()
-        port = self.config_dialog.spinbox_port.value()
-        logging.debug("Is valid ? ")
-        self.config_dialog.label_error.setText(self.tr("connecting..."))
-        try:
-            self.node = await Node.from_address(None, server, port, session=aiohttp.ClientSession())
-            community = Community.create(self.node)
-            self.config_dialog.button_connect.setEnabled(False)
-            self.config_dialog.button_register.setEnabled(False)
-            self.config_dialog.community = community
-            self.config_dialog.next()
-        except aiohttp.errors.DisconnectedError as e:
-            self.config_dialog.label_error.setText(str(e))
-        except aiohttp.errors.ClientError as e:
-            self.config_dialog.label_error.setText(str(e))
-        except (MalformedDocumentError, ValueError) as e:
-            self.config_dialog.label_error.setText(str(e))
-        except aiohttp.errors.TimeoutError:
-            self.config_dialog.label_error.setText(self.tr("Could not connect. Check hostname, ip address or port"))
-
-    @asyncify
-    async def check_connect(self, checked=False):
-        server = self.config_dialog.lineedit_server.text()
-        port = self.config_dialog.spinbox_port.value()
-        logging.debug("Is valid ? ")
-        self.config_dialog.label_error.setText(self.tr("connecting..."))
-        try:
-            self.node = await Node.from_address(None, server, port, session=aiohttp.ClientSession())
-            community = Community.create(self.node)
-            self.config_dialog.button_connect.setEnabled(False)
-            self.config_dialog.button_register.setEnabled(False)
-            registered = await self.account.check_registered(community)
-            self.config_dialog.button_connect.setEnabled(True)
-            self.config_dialog.button_register.setEnabled(True)
-            if registered[0] is False and registered[2] is None:
-                self.config_dialog.label_error.setText(self.tr("Could not find your identity on the network."))
-            elif registered[0] is False and registered[2]:
-                self.config_dialog.label_error.setText(self.tr("""Your pubkey or UID is different on the network.
-Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
-            else:
-                self.config_dialog.community = community
-                self.config_dialog.next()
-        except aiohttp.errors.DisconnectedError as e:
-            self.config_dialog.label_error.setText(str(e))
-        except aiohttp.errors.ClientError as e:
-            self.config_dialog.label_error.setText(str(e))
-        except (MalformedDocumentError, ValueError) as e:
-            self.config_dialog.label_error.setText(str(e))
-        except NoPeerAvailable:
-            self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry"))
-        except aiohttp.errors.TimeoutError:
-            self.config_dialog.label_error.setText(self.tr("Could not connect. Check hostname, ip address or port"))
-
-    @asyncify
-    async def check_register(self, checked=False):
-        server = self.config_dialog.lineedit_server.text()
-        port = self.config_dialog.spinbox_port.value()
-        logging.debug("Is valid ? ")
-        self.config_dialog.label_error.setText(self.tr("connecting..."))
-        try:
-            session = aiohttp.ClientSession()
-            self.node = await Node.from_address(None, server, port, session=session)
-            community = Community.create(self.node)
-            self.config_dialog.button_connect.setEnabled(False)
-            self.config_dialog.button_register.setEnabled(False)
-            registered = await self.account.check_registered(community)
-            self.config_dialog.button_connect.setEnabled(True)
-            self.config_dialog.button_register.setEnabled(True)
-            if registered[0] is False and registered[2] is None:
-                password = await self.password_asker.async_exec()
-                if self.password_asker.result() == QDialog.Rejected:
-                    return
-                self.config_dialog.label_error.setText(self.tr("Broadcasting identity..."))
-                result = await self.account.send_selfcert(password, community)
-                if result[0]:
-                    if self.app.preferences['notifications']:
-                        toast.display(self.tr("UID broadcast"), self.tr("Identity broadcasted to the network"))
-                    QApplication.restoreOverrideCursor()
-                    self.config_dialog.next()
-                else:
-                    self.config_dialog.label_error.setText(self.tr("Error") + " " + \
-                                                           self.tr("{0}".format(result[1])))
-                    if self.app.preferences['notifications']:
-                        toast.display(self.tr("Error"), self.tr("{0}".format(result[1])))
-                QApplication.restoreOverrideCursor()
-                self.config_dialog.community = community
-            elif registered[0] is False and registered[2]:
-                self.config_dialog.label_error.setText(self.tr("""Your pubkey or UID was already found on the network.
-Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
-            else:
-                self.config_dialog.label_error.setText(self.tr("Your account already exists on the network"))
-        except (MalformedDocumentError, ValueError, errors.DuniterError,
-                aiohttp.errors.ClientError, aiohttp.errors.DisconnectedError) as e:
-            session.close()
-            self.config_dialog.label_error.setText(str(e))
-        except NoPeerAvailable:
-            self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry"))
-        except aiohttp.errors.TimeoutError:
-            self.config_dialog.label_error.setText(self.tr("Could not connect. Check hostname, ip address or port"))
-
-    def is_valid(self):
-        return self.node is not None
-
-    def process_next(self):
-        """
-        We create the community
-        """
-        account = self.config_dialog.account
-        logging.debug("Account : {0}".format(account))
-        self.config_dialog.community = Community.create(self.node)
-
-    def display_page(self):
-        self.config_dialog.button_next.hide()
-        self.config_dialog.button_previous.hide()
-
-
-class StepPageAddpeers(Step):
-    """
-    The step where the user add peers
-    """
-    def __init__(self, config_dialog):
-        super().__init__(config_dialog)
-
-    def is_valid(self):
-        return True
-
-    def process_next(self):
-        pass
-
-    def display_page(self):
-        self.config_dialog.button_next.show()
-        self.config_dialog.button_previous.show()
-        # We add already known peers to the displayed list
-        self.config_dialog.nodes = self.config_dialog.community.network.root_nodes
-        tree_model = PeeringTreeModel(self.config_dialog.community)
-
-        self.config_dialog.tree_peers.setModel(tree_model)
-        self.config_dialog.button_previous.setEnabled(False)
-        self.config_dialog.button_next.setText(self.config_dialog.tr("Ok"))
-
-
-class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog):
-    """
-    Dialog to configure or add a community
-    """
-    community_added = pyqtSignal()
-
-    def __init__(self, app, account, community, password_asker):
-        """
-        Constructor
-
-        :param sakia.core.Application app: The application
-        :param sakia.core.Account account: The configured account
-        :param sakia.core.Community community: The configured community
-        :param sakia.gui.password_asker.Password_Asker password_asker: The password asker
-        """
-        super().__init__()
-        self.setupUi(self)
-        self.app = app
-        self.community = community
-        self.account = account
-        self.password_asker = password_asker
-        self.step = None
-        self.nodes = []
-
-        self.community_added.connect(self.add_community_and_close)
-        self._step_init = StepPageInit(self)
-        step_add_peers = StepPageAddpeers(self)
-
-        self._step_init.next_step = step_add_peers
-
-        if self.community is not None:
-            self.stacked_pages.removeWidget(self.page_node)
-            self.step = step_add_peers
-            self.setWindowTitle(self.tr("Configure community {0}").format(self.community.currency))
-        else:
-            self.step = self._step_init
-            self.setWindowTitle(self.tr("Add a community"))
-
-        self.step.display_page()
-
-    def next(self):
-        if self.step.next_step is not None:
-            if self.step.is_valid():
-                self.step.process_next()
-                self.step = self.step.next_step
-                next_index = self.stacked_pages.currentIndex() + 1
-                self.stacked_pages.setCurrentIndex(next_index)
-                self.step.display_page()
-        else:
-            self.add_community_and_close()
-
-    def previous(self):
-        if self.step.previous_step is not None:
-            self.step = self.step.previous_step
-            previous_index = self.stacked_pages.currentIndex() - 1
-            self.stacked_pages.setCurrentIndex(previous_index)
-            self.step.display_page()
-
-    async def start_add_node(self):
-        """
-        Add node slot
-        """
-        server = self.lineedit_add_address.text()
-        port = self.spinbox_add_port.value()
-
-        try:
-            node = await Node.from_address(self.community.currency, server, port, session=self.community.network.session)
-            self.community.add_node(node)
-        except Exception as e:
-            await QAsyncMessageBox.critical(self, self.tr("Error"),
-                                 str(e))
-        self.tree_peers.setModel(PeeringTreeModel(self.community))
-
-    def add_node(self):
-        asyncio.ensure_future(self.start_add_node())
-
-    def remove_node(self):
-        """
-        Remove node slot
-        """
-        logging.debug("Remove node")
-        index = self.sender().data()
-        self.community.remove_node(index)
-        self.tree_peers.setModel(PeeringTreeModel(self.community))
-
-    @property
-    def nb_steps(self):
-        s = self.step
-        nb_steps = 1
-        while s.next_step != None:
-            s = s.next_step
-            nb_steps = nb_steps + 1
-        return nb_steps
-
-    def showContextMenu(self, point):
-        if self.stacked_pages.currentIndex() == self.nb_steps - 1:
-            menu = QMenu()
-            index = self.tree_peers.indexAt(point)
-            action = menu.addAction(self.tr("Delete"), self.remove_node)
-            action.setData(index.row())
-            if self.community is not None:
-                if len(self.nodes) == 1:
-                    action.setEnabled(False)
-            menu.exec_(QCursor.pos())
-
-    def async_exec(self):
-        future = asyncio.Future()
-        self.finished.connect(lambda r: future.set_result(r))
-        self.open()
-        return future
-
-    def add_community_and_close(self):
-        if self.community not in self.account.communities:
-            self.account.add_community(self.community)
-        self.accept()
diff --git a/src/sakia/gui/revocation.py b/src/sakia/gui/revocation.py
deleted file mode 100644
index 5de30bf1ef1e64d9a3601ad49fb4340e345da408..0000000000000000000000000000000000000000
--- a/src/sakia/gui/revocation.py
+++ /dev/null
@@ -1,208 +0,0 @@
-import asyncio
-import aiohttp
-import logging
-from duniterpy.api import errors
-from duniterpy.api import bma
-from duniterpy.documents import MalformedDocumentError
-from duniterpy.documents.certification import Revocation
-from sakia.core.net import Node
-from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox
-from PyQt5.QtCore import QObject
-
-from .widgets.dialogs import QAsyncMessageBox
-from ..tools.decorators import asyncify
-from ..gen_resources.revocation_uic import Ui_RevocationDialog
-
-
-class RevocationDialog(QObject):
-    """
-    A dialog to revoke an identity
-    """
-
-    def __init__(self, app, account, widget, ui):
-        """
-        Constructor if a certification dialog
-
-        :param sakia.core.Application app:
-        :param sakia.core.Account account:
-        :param PyQt5.QtWidgets widget: the widget of the dialog
-        :param sakia.gen_resources.revocation_uic.Ui_RevocationDialog ui: the view of the certification dialog
-        :return:
-        """
-        super().__init__()
-        self.widget = widget
-        self.ui = ui
-        self.ui.setupUi(self.widget)
-        self.app = app
-        self.account = account
-        self.revocation_document = None
-        self.revoked_selfcert = None
-        self._steps = (
-            {
-                'page': self.ui.page_load_file,
-                'init': self.init_dialog,
-                'next': self.revocation_selected
-            },
-            {
-                'page': self.ui.page_destination,
-                'init': self.init_publication_page,
-                'next': self.publish
-            }
-        )
-        self._current_step = 0
-        self.handle_next_step(init=True)
-        self.ui.button_next.clicked.connect(lambda checked: self.handle_next_step(False))
-
-    def handle_next_step(self, init=False):
-        if self._current_step < len(self._steps) - 1:
-            if not init:
-                self.ui.button_next.clicked.disconnect(self._steps[self._current_step]['next'])
-                self._current_step += 1
-            self._steps[self._current_step]['init']()
-            self.ui.stackedWidget.setCurrentWidget(self._steps[self._current_step]['page'])
-            self.ui.button_next.clicked.connect(self._steps[self._current_step]['next'])
-
-    def init_dialog(self):
-        self.ui.button_next.setEnabled(False)
-        self.ui.button_load.clicked.connect(self.load_from_file)
-
-        self.ui.radio_address.toggled.connect(lambda c: self.publication_mode_changed("address"))
-        self.ui.radio_community.toggled.connect(lambda c: self.publication_mode_changed("community"))
-        self.ui.edit_address.textChanged.connect(self.refresh)
-        self.ui.spinbox_port.valueChanged.connect(self.refresh)
-        self.ui.combo_community.currentIndexChanged.connect(self.refresh)
-
-    def publication_mode_changed(self, radio):
-        self.ui.edit_address.setEnabled(radio == "address")
-        self.ui.spinbox_port.setEnabled(radio == "address")
-        self.ui.combo_community.setEnabled(radio == "community")
-        self.refresh()
-
-    def load_from_file(self):
-        selected_files = QFileDialog.getOpenFileName(self.widget,
-                                          self.tr("Load a revocation file"),
-                                          "",
-                                          self.tr("All text files (*.txt)"))
-        selected_file = selected_files[0]
-        try:
-            with open(selected_file, 'r') as file:
-                file_content = file.read()
-                self.revocation_document = Revocation.from_signed_raw(file_content)
-                self.revoked_selfcert = Revocation.extract_self_cert(file_content)
-                self.refresh()
-                self.ui.button_next.setEnabled(True)
-        except FileNotFoundError:
-            pass
-        except MalformedDocumentError:
-            QMessageBox.critical(self.widget, self.tr("Error loading document"),
-                                        self.tr("Loaded document is not a revocation document"),
-                                 QMessageBox.Ok)
-            self.ui.button_next.setEnabled(False)
-
-    def revocation_selected(self):
-        pass
-
-    def init_publication_page(self):
-        self.ui.combo_community.clear()
-        if self.account:
-            for community in self.account.communities:
-                self.ui.combo_community.addItem(community.currency)
-            self.ui.radio_community.setChecked(True)
-        else:
-            self.ui.radio_address.setChecked(True)
-            self.ui.radio_community.setEnabled(False)
-
-    def publish(self):
-        self.ui.button_next.setEnabled(False)
-        answer = QMessageBox.warning(self.widget, self.tr("Revocation"),
-                                      self.tr("""<h4>The publication of this document will remove your identity from the network.</h4>
-<li>
-    <li> <b>This identity won't be able to join the targeted community anymore.</b> </li>
-    <li> <b>This identity won't be able to generate Universal Dividends anymore.</b> </li>
-    <li> <b>This identity won't be able to certify individuals anymore.</b> </li>
-</li>
-Please think twice before publishing this document.
-"""), QMessageBox.Ok | QMessageBox.Cancel)
-        if answer == QMessageBox.Ok:
-            self.accept()
-        else:
-            self.ui.button_next.setEnabled(True)
-
-    @asyncify
-    async def accept(self):
-        try:
-            session = aiohttp.ClientSession()
-            if self.ui.radio_community.isChecked():
-                community = self.account.communities[self.ui.combo_community.currentIndex()]
-                await community.bma_access.broadcast(bma.wot.Revoke, {},
-                       {
-                           'revocation': self.revocation_document.signed_raw(self.revoked_selfcert)
-                       })
-            elif self.ui.radio_address.isChecked():
-                    server = self.ui.edit_address.text()
-                    port = self.ui.spinbox_port.value()
-                    node = await Node.from_address(None, server, port, session=session)
-                    conn_handler = node.endpoint.conn_handler()
-                    await bma.wot.Revoke(conn_handler).post(session,
-                                                revocation=self.revocation_document.signed_raw(self.revoked_selfcert))
-        except (MalformedDocumentError, ValueError, errors.DuniterError,
-            aiohttp.errors.ClientError, aiohttp.errors.DisconnectedError,
-                aiohttp.errors.TimeoutError) as e:
-            await QAsyncMessageBox.critical(self.widget, self.tr("Error broadcasting document"),
-                                        str(e))
-        else:
-            await QAsyncMessageBox.information(self.widget, self.tr("Revocation broadcast"),
-                                               self.tr("The document was successfully broadcasted."))
-            self.widget.accept()
-        finally:
-            session.close()
-
-    @classmethod
-    def open_dialog(cls, app, account):
-        """
-        Certify and identity
-        :param sakia.core.Application app: the application
-        :param sakia.core.Account account: the account certifying the identity
-        :return:
-        """
-        dialog = cls(app, account, QDialog(), Ui_RevocationDialog())
-        dialog.refresh()
-        return dialog.exec()
-
-    def refresh(self):
-        if self.revoked_selfcert:
-            text = self.tr("""
-<div>Identity revoked : {uid} (public key : {pubkey}...)</div>
-<div>Identity signed on block : {timestamp}</div>
-    """.format(uid=self.revoked_selfcert.uid,
-               pubkey=self.revoked_selfcert.pubkey[:12],
-               timestamp=self.revoked_selfcert.timestamp))
-
-            self.ui.label_revocation_content.setText(text)
-
-            if self.ui.radio_community.isChecked():
-                target = self.tr("All nodes of community {name}".format(name=self.ui.combo_community.currentText()))
-            elif self.ui.radio_address.isChecked():
-                target = self.tr("Address {address}:{port}".format(address=self.ui.edit_address.text(),
-                                                                   port=self.ui.spinbox_port.value()))
-            else:
-                target = ""
-            self.ui.label_revocation_info.setText("""
-<h4>Revocation document</h4>
-<div>{text}</div>
-<h4>Publication address</h4>
-<div>{target}</div>
-""".format(text=text,
-           target=target))
-        else:
-            self.ui.label_revocation_content.setText("")
-
-    def async_exec(self):
-        future = asyncio.Future()
-        self.widget.finished.connect(lambda r: future.set_result(r))
-        self.widget.open()
-        self.refresh()
-        return future
-
-    def exec(self):
-        self.widget.exec()
diff --git a/src/sakia/tests/unit/gui/views/__init__.py b/src/sakia/gui/sub/__init__.py
similarity index 100%
rename from src/sakia/tests/unit/gui/views/__init__.py
rename to src/sakia/gui/sub/__init__.py
diff --git a/src/sakia/gui/sub/search_user/controller.py b/src/sakia/gui/sub/search_user/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..787c2aed85d7ad806e1f08e5e8376b2842e8f9ab
--- /dev/null
+++ b/src/sakia/gui/sub/search_user/controller.py
@@ -0,0 +1,64 @@
+from PyQt5.QtCore import pyqtSignal, QObject
+
+from sakia.data.entities import Identity
+from sakia.decorators import asyncify
+from .model import SearchUserModel
+from .view import SearchUserView
+
+
+class SearchUserController(QObject):
+    """
+    The navigation panel
+    """
+    search_started = pyqtSignal()
+    search_ended = pyqtSignal()
+    identity_selected = pyqtSignal(Identity)
+
+    def __init__(self, parent, view, model):
+        """
+        :param sakia.gui.agent.controller.AgentController parent: the parent
+        :param sakia.gui.search_user.view.SearchUserView view:
+        :param sakia.gui.search_user.model.SearchUserModel model:
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+        self.view.search_requested.connect(self.search)
+        self.view.node_selected.connect(self.select_node)
+
+    @classmethod
+    def create(cls, parent, app, currency):
+        view = SearchUserView(parent.view)
+        model = SearchUserModel(parent, app, currency)
+        search_user = cls(parent, view, model)
+        model.setParent(search_user)
+        return search_user
+
+    @asyncify
+    async def search(self, text):
+        """
+        Search for a user
+        :param text:
+        :return:
+        """
+        if len(text) > 2:
+            await self.model.find_user(text)
+        user_nodes = self.model.user_nodes()
+        self.view.set_search_result(text, user_nodes)
+
+    def current_identity(self):
+        """
+
+        :rtype: sakia.core.registry.Identity
+        """
+        return self.model.identity()
+
+    def select_node(self, index):
+        """
+        Select node in graph when item is selected in combobox
+        """
+        self.model.select_identity(index)
+        self.identity_selected.emit(self.model.identity())
+
+    def set_currency(self, currency):
+        self.model.currency = currency
diff --git a/src/sakia/gui/sub/search_user/model.py b/src/sakia/gui/sub/search_user/model.py
new file mode 100644
index 0000000000000000000000000000000000000000..222aeaa1a38dda2e5dacb773278ba84f00bcb530
--- /dev/null
+++ b/src/sakia/gui/sub/search_user/model.py
@@ -0,0 +1,69 @@
+from PyQt5.QtCore import QObject
+from duniterpy.api import errors
+from sakia.errors import NoPeerAvailable
+from sakia.data.processors import IdentitiesProcessor
+
+import logging
+
+
+class SearchUserModel(QObject):
+    """
+    The model of Navigation component
+    """
+
+    def __init__(self, parent, app, currency):
+        """
+
+        :param sakia.gui.search_user.controller.NetworkController parent: the controller
+        :param sakia.app.Application app: the app
+        :param str currency: the currency network to look for users
+        """
+        super().__init__(parent)
+        self.app = app
+        self.identities_processor = IdentitiesProcessor.instanciate(app)
+        self.currency = currency
+        self._nodes = list()
+        self._current_identity = None
+
+    def identity(self):
+        """
+        Get current identity selected
+        :rtype: sakia.core.registry.Identity
+        """
+        return self._current_identity
+
+    def user_nodes(self):
+        """
+        Gets user nodes
+        :return:
+        """
+        return [n.uid for n in self._nodes]
+
+    async def find_user(self, text):
+        """
+        Search for a user
+        :param text:
+        :return:
+        """
+        try:
+            self._nodes = await self.identities_processor.lookup(self.currency, text)
+        except errors.DuniterError as e:
+            if e.ucode == errors.NO_MATCHING_IDENTITY:
+                self._nodes = list()
+            else:
+                logging.debug(str(e))
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+        except BaseException as e:
+            logging.debug(str(e))
+
+    def select_identity(self, index):
+        """
+        Select an identity from a node index
+        :param index:
+        :return:
+        """
+        if index < 0 or index >= len(self._nodes):
+            self._current_identity = None
+            return False
+        self._current_identity = self._nodes[index]
\ No newline at end of file
diff --git a/res/ui/search_user_view.ui b/src/sakia/gui/sub/search_user/search_user.ui
similarity index 100%
rename from res/ui/search_user_view.ui
rename to src/sakia/gui/sub/search_user/search_user.ui
diff --git a/src/sakia/gui/sub/search_user/view.py b/src/sakia/gui/sub/search_user/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c13fc892b1c5a8da5e83d93a9587306a2914cb4
--- /dev/null
+++ b/src/sakia/gui/sub/search_user/view.py
@@ -0,0 +1,64 @@
+from PyQt5.QtWidgets import QWidget, QComboBox
+from PyQt5.QtCore import QT_TRANSLATE_NOOP, pyqtSignal, Qt
+from .search_user_uic import Ui_SearchUserWidget
+
+
+class SearchUserView(QWidget, Ui_SearchUserWidget):
+    """
+    The model of Navigation component
+    """
+    _search_placeholder = QT_TRANSLATE_NOOP("SearchUserWidget", "Research a pubkey, an uid...")
+    search_requested = pyqtSignal(str)
+    reset_requested = pyqtSignal()
+    node_selected = pyqtSignal(int)
+
+    def __init__(self, parent):
+        # construct from qtDesigner
+        super().__init__(parent)
+        self.setupUi(self)
+        # Default text when combo lineEdit is empty
+        self.combobox_search.lineEdit().setPlaceholderText(self.tr(SearchUserView._search_placeholder))
+        #  add combobox events
+        self.combobox_search.lineEdit().returnPressed.connect(self.search)
+        self.button_reset.clicked.connect(self.reset_requested)
+        # To fix a recall of the same item with different case,
+        # the edited text is not added in the item list
+        self.combobox_search.setInsertPolicy(QComboBox.NoInsert)
+        self.combobox_search.activated.connect(self.node_selected)
+
+    def search(self):
+        """
+        Search nodes when return is pressed in combobox lineEdit
+        """
+        text = self.combobox_search.lineEdit().text()
+        self.combobox_search.lineEdit().clear()
+        self.combobox_search.lineEdit().setPlaceholderText(self.tr("Looking for {0}...".format(text)))
+        self.search_requested.emit(text)
+
+    def set_search_result(self, text, nodes):
+        """
+        Set the list of users displayed in the combo box
+        :param str text: the text of the search
+        :param list[str] nodes: the list of users found
+        """
+        self.blockSignals(True)
+        self.combobox_search.clear()
+        if len(nodes) > 0:
+            self.combobox_search.lineEdit().setText(text)
+            for uid in nodes:
+                self.combobox_search.addItem(uid)
+        self.blockSignals(False)
+        self.combobox_search.showPopup()
+
+    def retranslateUi(self, widget):
+        """
+        Retranslate missing widgets from generated code
+        """
+        self.combobox_search.lineEdit().setPlaceholderText(self.tr(SearchUserView._search_placeholder))
+        super().retranslateUi(self)
+
+    def keyPressEvent(self, event):
+        if event.key() == Qt.Key_Return:
+            return
+
+        super().keyPressEvent(event)
diff --git a/src/sakia/tests/unit/tools/__init__.py b/src/sakia/gui/sub/user_information/__init__.py
similarity index 100%
rename from src/sakia/tests/unit/tools/__init__.py
rename to src/sakia/gui/sub/user_information/__init__.py
diff --git a/src/sakia/gui/sub/user_information/controller.py b/src/sakia/gui/sub/user_information/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ffb47cce0d62e4c23fcd5e4d68d79b52065b1e7
--- /dev/null
+++ b/src/sakia/gui/sub/user_information/controller.py
@@ -0,0 +1,86 @@
+from PyQt5.QtWidgets import QDialog, QTabWidget, QVBoxLayout
+from PyQt5.QtCore import QObject, pyqtSignal
+from sakia.decorators import asyncify
+from sakia.gui.widgets.dialogs import dialog_async_exec
+from .model import UserInformationModel
+from .view import UserInformationView
+
+
+class UserInformationController(QObject):
+    """
+    The homescreen view
+    """
+    identity_loaded = pyqtSignal()
+
+    def __init__(self, parent, view, model):
+        """
+        Constructor of the homescreen component
+
+        :param sakia.gui.homescreen.view.HomeScreenView: the view
+        :param sakia.gui.homescreen.model.HomeScreenModel model: the model
+        """
+        super().__init__(parent)
+        self.view = view
+        self.model = model
+
+    @classmethod
+    def create(cls, parent, app, currency, identity):
+        view = UserInformationView(parent.view if parent else None)
+        model = UserInformationModel(None, app, currency, identity)
+        homescreen = cls(parent, view, model)
+        model.setParent(homescreen)
+        return homescreen
+
+    @classmethod
+    def show_identity(cls, parent, app, currency, identity):
+        dialog = QDialog()
+        dialog.setWindowTitle("Informations")
+        user_info = cls.create(parent, app, currency, identity)
+        user_info.view.setParent(dialog)
+        user_info.refresh()
+        dialog.exec()
+
+    @classmethod
+    @asyncify
+    async def search_and_show_pubkey(cls, parent, app, currency, pubkey):
+        dialog = QDialog(parent)
+        dialog.setWindowTitle("Informations")
+        layout = QVBoxLayout(dialog)
+        tabwidget = QTabWidget(dialog)
+        layout.addWidget(tabwidget)
+
+        identities = await app.identities_services[currency].lookup(pubkey)
+        for i in identities:
+            user_info = cls.create(parent, app, currency, i)
+            user_info.refresh()
+            tabwidget.addTab(user_info.view, i.uid)
+        return await dialog_async_exec(dialog)
+
+    @asyncify
+    async def refresh(self):
+        if self.model.identity:
+            self.view.show_busy()
+            await self.model.load_identity(self.model.identity)
+            self.view.display_uid(self.model.identity.uid, self.model.identity.member)
+            self.view.display_identity_timestamps(self.model.identity.pubkey, self.model.identity.timestamp,
+                                                  self.model.identity.membership_timestamp,
+                                                  self.model.mstime_remaining(), await self.model.nb_certs())
+            self.view.hide_busy()
+            self.identity_loaded.emit()
+
+    @asyncify
+    async def search_identity(self, identity):
+        await self.model.load_identity(identity)
+        self.refresh()
+
+    def change_identity(self, identity):
+        """
+        Set identity
+        :param sakia.core.registry.Identity identity:
+        """
+        self.model.identity = identity
+        self.refresh()
+
+    def set_currency(self, currency):
+        self.model.set_currency(currency)
+        self.refresh()
\ 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
new file mode 100644
index 0000000000000000000000000000000000000000..7751122233e71f4540ecf8c447bfdfe2f75959d1
--- /dev/null
+++ b/src/sakia/gui/sub/user_information/model.py
@@ -0,0 +1,50 @@
+from PyQt5.QtCore import QObject
+from sakia.data.processors import CertificationsProcessor, BlockchainProcessor
+
+
+class UserInformationModel(QObject):
+    """
+    The model of HomeScreen component
+    """
+
+    def __init__(self, parent, app, currency, identity):
+        """
+
+        :param sakia.gui.user_information.controller.UserInformationController parent:
+        :param sakia.core.Application app: the app
+        :param str currency: the currency currently requested
+        :param sakia.data.entities.Identity identity: the identity
+        :param sakia.services.IdentitiesService identities_service: the identities service of current currency
+        """
+        super().__init__(parent)
+        self._certifications_processor = CertificationsProcessor.instanciate(app)
+        self._blockchain_processor = BlockchainProcessor.instanciate(app)
+        self.app = app
+        self.currency = currency
+        self.identity = identity
+        if identity:
+            self.certs_sent = self._certifications_processor.certifications_sent(currency, identity.pubkey)
+            self.certs_received = self._certifications_processor.certifications_received(currency, identity.pubkey)
+        if currency:
+            self.identities_service = self.app.identities_services[self.currency]
+        else:
+            self.identities_service = None
+
+    async def load_identity(self, identity):
+        """
+        Ask network service to request identity informations
+        """
+        self.identity = identity
+        self.identity = await self.identities_service.load_memberships(self.identity)
+        self.identity = await self.identities_service.load_requirements(self.identity)
+
+    def set_currency(self, currency):
+        self.currency = currency
+        self.identities_service = self.app.identities_services[self.currency]
+
+    async def nb_certs(self):
+        certs = await self.identities_service.load_certifiers_of(self.identity)
+        return len(certs)
+
+    def mstime_remaining(self):
+        return self.identities_service.ms_time_remaining(self.identity)
diff --git a/res/ui/member.ui b/src/sakia/gui/sub/user_information/user_information.ui
similarity index 86%
rename from res/ui/member.ui
rename to src/sakia/gui/sub/user_information/user_information.ui
index 23bb9d1769f46c84eca8ca91a172ae5612de9dce..b7aa85477487eaa90d1ad37f283857503996d91a 100644
--- a/res/ui/member.ui
+++ b/src/sakia/gui/sub/user_information/user_information.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>MemberView</class>
- <widget class="QWidget" name="MemberView">
+ <class>UserInformationWidget</class>
+ <widget class="QWidget" name="UserInformationWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -15,7 +15,7 @@
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
-    <widget class="QGroupBox" name="groupbox_member">
+    <widget class="QGroupBox" name="groupbox_user">
      <property name="styleSheet">
       <string notr="true">QGroupBox {
     border: 1px solid gray;
@@ -31,7 +31,7 @@ QGroupBox::title {
 }</string>
      </property>
      <property name="title">
-      <string>Member</string>
+      <string>User</string>
      </property>
      <layout class="QGridLayout" name="gridLayout">
       <item row="0" column="1">
@@ -52,7 +52,7 @@ QGroupBox::title {
          <string/>
         </property>
         <property name="pixmap">
-         <pixmap resource="../icons/icons.qrc">:/icons/member_icon</pixmap>
+         <pixmap resource="../../../../res/icons/icons.qrc">:/icons/member_icon</pixmap>
         </property>
         <property name="scaledContents">
          <bool>true</bool>
@@ -95,7 +95,8 @@ QGroupBox::title {
   </layout>
  </widget>
  <resources>
-  <include location="../icons/icons.qrc"/>
+  <include location="../../../../res/icons/icons.qrc"/>
+  <include location="../../../../res/icons/icons.qrc"/>
  </resources>
  <connections/>
 </ui>
diff --git a/src/sakia/gui/sub/user_information/view.py b/src/sakia/gui/sub/user_information/view.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a0ccbc2756a36fc17fd065946be25062c05b826
--- /dev/null
+++ b/src/sakia/gui/sub/user_information/view.py
@@ -0,0 +1,107 @@
+from PyQt5.QtCore import QLocale, QDateTime
+from PyQt5.QtWidgets import QWidget
+from .user_information_uic import Ui_UserInformationWidget
+from sakia.gui.widgets.busy import Busy
+
+
+class UserInformationView(QWidget, Ui_UserInformationWidget):
+    """
+    User information view
+    """
+
+    def __init__(self, parent):
+        """
+        Constructor
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        self.busy = Busy(self)
+        self.busy.hide()
+
+    def display_identity_timestamps(self, pubkey, publish_time, join_date,
+                                    mstime_remaining, nb_certs):
+        """
+        Display identity timestamps in localized format
+        :param str pubkey:
+        :param int publish_time:
+        :param int join_date:
+        :return:
+        """
+        if join_date:
+            localized_join_date = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(join_date),
+                QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+            )
+        else:
+            localized_join_date = "###"
+
+        if publish_time:
+            localized_publish_date = QLocale.toString(
+                QLocale(),
+                QDateTime.fromTime_t(publish_time),
+                QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat)
+            )
+        else:
+            localized_publish_date = "###"
+
+        if mstime_remaining:
+            days, remainder = divmod(mstime_remaining, 3600 * 24)
+            hours, remainder = divmod(remainder, 3600)
+            minutes, seconds = divmod(remainder, 60)
+            if days > 0:
+                localized_mstime_remaining = "{days} days".format(days=days)
+            else:
+                localized_mstime_remaining = "{hours} hours and {min} min.".format(hours=hours,
+                                                                               min=minutes)
+        else:
+            localized_mstime_remaining = "###"
+
+
+        text = self.tr("""
+            <table cellpadding="5">
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr>
+            """).format(
+            self.tr('Public key'),
+            pubkey,
+            self.tr('UID Published on'),
+            localized_publish_date,
+            self.tr('Join date'),
+            localized_join_date,
+            self.tr("Expires in"),
+            localized_mstime_remaining,
+            self.tr("Certs. received"),
+            nb_certs
+        )
+
+        # close html text
+        text += "</table>"
+
+        # set text in label
+        self.label_properties.setText(text)
+
+    def display_uid(self, uid, member):
+        """
+        Display the uid in the label
+        :param str uid:
+        """
+        status_label = self.tr("Member") if member else self.tr("Non-Member")
+        status_color = '#00AA00' if member else self.tr('#FF0000')
+        text = "<b>{uid}</b> <p style='color: {status_color};'>({status_label})</p>".format(
+            uid=uid, status_color=status_color, status_label=status_label
+        )
+        self.label_uid.setText(text)
+
+    def show_busy(self):
+        self.busy.show()
+
+    def hide_busy(self):
+        self.busy.hide()
+
+    def resizeEvent(self, event):
+        self.busy.resize(event.size())
+        super().resizeEvent(event)
\ No newline at end of file
diff --git a/src/sakia/gui/transactions_tab.py b/src/sakia/gui/transactions_tab.py
deleted file mode 100644
index a498c54944e80d7fa78313ad712d9e67dd26783c..0000000000000000000000000000000000000000
--- a/src/sakia/gui/transactions_tab.py
+++ /dev/null
@@ -1,222 +0,0 @@
-import logging
-
-from duniterpy.api import errors
-from PyQt5.QtWidgets import QWidget, QAbstractItemView, QHeaderView
-from PyQt5.QtCore import Qt, QObject, QDateTime, QTime, QModelIndex, pyqtSignal, pyqtSlot, QEvent
-from PyQt5.QtGui import QCursor
-
-from ..gen_resources.transactions_tab_uic import Ui_transactionsTabWidget
-from ..models.txhistory import HistoryTableModel, TxFilterProxyModel
-from ..tools.exceptions import NoPeerAvailable
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from .widgets.context_menu import ContextMenu
-from .widgets import toast
-
-
-class TransactionsTabWidget(QObject):
-    """
-    classdocs
-    """
-    view_in_wot = pyqtSignal(object)
-
-    def __init__(self, app, account=None, community=None, password_asker=None,
-                 widget=QWidget, view=Ui_transactionsTabWidget):
-        """
-        Init
-
-        :param sakia.core.app.Application app: Application instance
-        :param sakia.core.Account account: The account displayed in the widget
-        :param sakia.core.Community community: The community displayed in the widget
-        :param sakia.gui.Password_Asker: password_asker: The widget to ask for passwords
-        :param class widget: The class of the PyQt5 widget used for this tab
-        :param class view: The class of the UI View for this tab
-        """
-
-        super().__init__()
-        self.widget = widget()
-        self.ui = view()
-        self.ui.setupUi(self.widget)
-        self.app = app
-        self.account = account
-        self.community = community
-        self.password_asker = password_asker
-        self.ui.busy_balance.hide()
-
-        ts_from = self.ui.date_from.dateTime().toTime_t()
-        ts_to = self.ui.date_to.dateTime().toTime_t()
-        model = HistoryTableModel(self.app, self.account, self.community)
-        proxy = TxFilterProxyModel(ts_from, ts_to)
-        proxy.setSourceModel(model)
-        proxy.setDynamicSortFilter(True)
-        proxy.setSortRole(Qt.DisplayRole)
-
-        self.ui.table_history.setModel(proxy)
-        self.ui.table_history.setSelectionBehavior(QAbstractItemView.SelectRows)
-        self.ui.table_history.setSortingEnabled(True)
-        self.ui.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
-        self.ui.table_history.resizeColumnsToContents()
-
-        self.ui.table_history.customContextMenuRequested['QPoint'].connect(self.history_context_menu)
-        self.ui.date_from.dateChanged['QDate'].connect(self.dates_changed)
-        self.ui.date_to.dateChanged['QDate'].connect(self.dates_changed)
-
-        model.modelAboutToBeReset.connect(lambda: self.ui.table_history.setEnabled(False))
-        model.modelReset.connect(lambda: self.ui.table_history.setEnabled(True))
-        self.app.refresh_transfers.connect(self.refresh)
-
-        self.ui.progressbar.hide()
-        self.refresh()
-
-    def cancel_once_tasks(self):
-        cancel_once_task(self, self.refresh_minimum_maximum)
-        cancel_once_task(self, self.refresh_balance)
-        cancel_once_task(self, self.history_context_menu)
-
-    def change_account(self, account, password_asker):
-        self.cancel_once_tasks()
-        self.account = account
-        self.password_asker = password_asker
-        self.ui.table_history.model().sourceModel().change_account(account)
-        if account:
-            self.connect_progress()
-
-    def change_community(self, community):
-        self.cancel_once_tasks()
-        self.community = community
-        self.ui.progressbar.hide()
-        self.ui.table_history.model().sourceModel().change_community(self.community)
-        self.refresh()
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_minimum_maximum(self):
-        try:
-            block = await self.community.get_block(1)
-            minimum_datetime = QDateTime()
-            minimum_datetime.setTime_t(block['medianTime'])
-            minimum_datetime.setTime(QTime(0, 0))
-
-            self.ui.date_from.setMinimumDateTime(minimum_datetime)
-            self.ui.date_from.setDateTime(minimum_datetime)
-            self.ui.date_from.setMaximumDateTime(QDateTime().currentDateTime())
-
-            self.ui.date_to.setMinimumDateTime(minimum_datetime)
-            tomorrow_datetime = QDateTime().currentDateTime().addDays(1)
-            self.ui.date_to.setDateTime(tomorrow_datetime)
-            self.ui.date_to.setMaximumDateTime(tomorrow_datetime)
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        except errors.DuniterError as e:
-            logging.debug(str(e))
-
-    def refresh(self):
-        if self.community:
-            refresh_task = self.ui.table_history.model().sourceModel().refresh_transfers()
-            refresh_task.add_done_callback(lambda fut: self.ui.table_history.resizeColumnsToContents())
-            self.refresh_minimum_maximum()
-            self.refresh_balance()
-
-    def connect_progress(self):
-        def progressing(community, value, maximum):
-            if community == self.community:
-                self.ui.progressbar.show()
-                self.ui.progressbar.setValue(value)
-                self.ui.progressbar.setMaximum(maximum)
-        self.account.loading_progressed.connect(progressing)
-        self.account.loading_finished.connect(self.stop_progress)
-
-    def stop_progress(self, community, received_list):
-        if community == self.community:
-            self.ui.progressbar.hide()
-            self.ui.table_history.model().sourceModel().refresh_transfers()
-            self.ui.table_history.resizeColumnsToContents()
-            self.notification_reception(received_list)
-
-    @asyncify
-    async def notification_reception(self, received_list):
-        if len(received_list) > 0:
-            amount = 0
-            for r in received_list:
-                amount += r.metadata['amount']
-            localized_amount = await self.app.current_account.current_ref.instance(amount, self.community, self.app)\
-                                            .localized(units=True,
-                                    international_system=self.app.preferences['international_system_of_units'])
-            text = self.tr("Received {amount} from {number} transfers").format(amount=localized_amount ,
-                                                                            number=len(received_list))
-            if self.app.preferences['notifications']:
-                toast.display(self.tr("New transactions received"), text)
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_balance(self):
-        self.ui.busy_balance.show()
-        try:
-            amount = await self.app.current_account.amount(self.community)
-            localized_amount = await self.app.current_account.current_ref.instance(amount, self.community,
-                                                                           self.app).localized(units=True,
-                                        international_system=self.app.preferences['international_system_of_units'])
-
-            # set infos in label
-            self.ui.label_balance.setText(
-                self.tr("{:}")
-                .format(
-                    localized_amount
-                )
-            )
-        except NoPeerAvailable as e:
-            logging.debug(str(e))
-        except errors.DuniterError as e:
-            logging.debug(str(e))
-        self.ui.busy_balance.hide()
-
-    @once_at_a_time
-    @asyncify
-    async def history_context_menu(self, point):
-        index = self.ui.table_history.indexAt(point)
-        model = self.ui.table_history.model()
-        if index.isValid() and index.row() < model.rowCount(QModelIndex()):
-            source_index = model.mapToSource(index)
-
-            pubkey_col = model.sourceModel().columns_types.index('pubkey')
-            pubkey_index = model.sourceModel().index(source_index.row(),
-                                                    pubkey_col)
-            pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole)
-
-            identity = await self.app.identities_registry.future_find(pubkey, self.community)
-
-            transfer = model.sourceModel().transfers()[source_index.row()]
-            menu = ContextMenu.from_data(self.widget, self.app, self.account, self.community, self.password_asker,
-                                         (identity, transfer))
-            menu.view_identity_in_wot.connect(self.view_in_wot)
-
-            # Show the context menu.
-            menu.qmenu.popup(QCursor.pos())
-
-    def dates_changed(self):
-        logging.debug("Changed dates")
-        if self.ui.table_history.model():
-            qdate_from = self.ui.date_from
-            qdate_from.setTime(QTime(0, 0, 0))
-            qdate_to = self.ui.date_to
-            qdate_to.setTime(QTime(0, 0, 0))
-            ts_from = qdate_from.dateTime().toTime_t()
-            ts_to = qdate_to.dateTime().toTime_t()
-
-            self.ui.table_history.model().set_period(ts_from, ts_to)
-
-            self.refresh_balance()
-
-    def resizeEvent(self, event):
-        self.ui.busy_balance.resize(event.size())
-        super().resizeEvent(event)
-
-    def changeEvent(self, event):
-        """
-        Intercepte LanguageChange event to translate UI
-        :param QEvent QEvent: Event
-        :return:
-        """
-        if event.type() == QEvent.LanguageChange:
-            self.retranslateUi(self)
-            self.refresh()
-        return super(TransactionsTabWidget, self).changeEvent(event)
diff --git a/src/sakia/gui/transfer.py b/src/sakia/gui/transfer.py
deleted file mode 100644
index 3fb340db67de93cf6b19c331b4f965c6c2e834c9..0000000000000000000000000000000000000000
--- a/src/sakia/gui/transfer.py
+++ /dev/null
@@ -1,261 +0,0 @@
-"""
-Created on 2 févr. 2014
-
-@author: inso
-"""
-import asyncio
-import logging
-
-from PyQt5.QtWidgets import QDialog, QApplication, QDialogButtonBox
-from PyQt5.QtCore import QRegExp, Qt, QObject
-
-from PyQt5.QtGui import QRegExpValidator
-
-from ..gen_resources.transfer_uic import Ui_TransferMoneyDialog
-from .widgets import toast
-from .widgets.dialogs import QAsyncMessageBox, QMessageBox
-from ..tools.decorators import asyncify
-
-
-class TransferMoneyDialog(QObject):
-
-    """
-    classdocs
-    """
-
-    def __init__(self, app, account, password_asker, community, transfer, widget=QDialog, view=Ui_TransferMoneyDialog):
-        """
-        Constructor
-        :param sakia.core.Application app: The application
-        :param sakia.core.Account account: The account
-        :param sakia.gui.password_asker.Password_Asker password_asker: The password asker
-        :param sakia.core.Community community:
-        :param sakia.core.Transfer transfer:
-        :param class widget:
-        :param class view:
-        :return:
-        """
-        super().__init__()
-        self.widget = widget()
-        self.ui = view()
-        self.ui.setupUi(self.widget)
-
-        self.app = app
-        self.account = account
-        self.password_asker = password_asker
-        self.recipient_trusts = []
-        self.transfer = transfer
-        self.wallet = None
-        self.community = community if community else self.account.communities[0]
-        self.wallet = self.account.wallets[0]
-
-        self.ui.radio_contact.toggled.connect(lambda c, radio="contact": self.recipient_mode_changed(radio))
-        self.ui.radio_pubkey.toggled.connect(lambda c, radio="pubkey": self.recipient_mode_changed(radio))
-        self.ui.radio_search.toggled.connect(lambda c, radio="search": self.recipient_mode_changed(radio))
-        self.ui.button_box.accepted.connect(self.accept)
-        self.ui.button_box.rejected.connect(self.widget.reject)
-        self.ui.combo_wallets.currentIndexChanged.connect(self.change_displayed_wallet)
-        self.ui.combo_community.currentIndexChanged.connect(self.change_current_community)
-        self.ui.spinbox_relative.valueChanged.connect(self.relative_amount_changed)
-        self.ui.spinbox_amount.valueChanged.connect(self.amount_changed)
-        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.search_started.connect(lambda: self.ui.button_box.setEnabled(False))
-        self.ui.search_user.search_completed.connect(lambda: self.ui.button_box.setEnabled(True))
-
-        regexp = QRegExp('^([ a-zA-Z0-9-_:/;*?\[\]\(\)\\\?!^+=@&~#{}|<>%.]{0,255})$')
-        validator = QRegExpValidator(regexp)
-        self.ui.edit_message.setValidator(validator)
-
-        for community in self.account.communities:
-            self.ui.combo_community.addItem(community.currency)
-
-        for wallet in self.account.wallets:
-            self.ui.combo_wallets.addItem(wallet.name)
-
-        for contact_name in sorted([c['name'] for c in account.contacts], key=str.lower):
-            self.ui.combo_contact.addItem(contact_name)
-
-        if len(self.account.contacts) == 0:
-            self.ui.combo_contact.setEnabled(False)
-            self.ui.radio_contact.setEnabled(False)
-            self.ui.radio_pubkey.setChecked(True)
-
-        self.ui.combo_community.setCurrentText(self.community.name)
-
-        if self.transfer:
-            account = self.transfer.metadata['issuer']
-            wallet_index = [w.pubkey for w in app.current_account.wallets].index(account)
-            self.ui.combo_wallets.setCurrentIndex(wallet_index)
-            self.ui.edit_pubkey.setText(transfer.metadata['receiver'])
-            self.ui.radio_pubkey.setChecked(True)
-            self.ui.edit_message.setText(transfer.metadata['comment'])
-
-    @classmethod
-    async def send_money_to_identity(cls, app, account, password_asker, community, identity):
-        dialog = cls(app, account, password_asker, community, None)
-        dialog.ui.edit_pubkey.setText(identity.pubkey)
-        dialog.ui.radio_pubkey.setChecked(True)
-        return await dialog.async_exec()
-
-    @classmethod
-    async def send_transfer_again(cls, app, account, password_asker, community, transfer):
-        dialog = cls(app, account, password_asker, community, transfer)
-        dividend = await community.dividend()
-        relative = transfer.metadata['amount'] / dividend
-        dialog.ui.spinbox_amount.setMaximum(transfer.metadata['amount'])
-        dialog.ui.spinbox_relative.setMaximum(relative)
-        dialog.ui.spinbox_amount.setValue(transfer.metadata['amount'])
-
-        return await dialog.async_exec()
-
-    @asyncify
-    async def accept(self):
-        logging.debug("Accept transfer action...")
-        self.ui.button_box.setEnabled(False)
-        comment = self.ui.edit_message.text()
-
-        logging.debug("checking recipient mode...")
-        if self.ui.radio_contact.isChecked():
-            for contact in self.account.contacts:
-                if contact['name'] == self.ui.combo_contact.currentText():
-                    recipient = contact['pubkey']
-                    break
-        elif self.ui.radio_search.isChecked():
-            if self.ui.search_user.current_identity():
-                recipient = self.ui.search_user.current_identity().pubkey
-            else:
-                return
-        else:
-            recipient = self.ui.edit_pubkey.text()
-        amount = self.ui.spinbox_amount.value()
-
-        logging.debug("checking amount...")
-        if not amount:
-            await QAsyncMessageBox.critical(self.widget, self.tr("Money transfer"),
-                                 self.tr("No amount. Please give the transfert amount"),
-                                 QMessageBox.Ok)
-            self.ui.button_box.setEnabled(True)
-            return
-        logging.debug("Showing password dialog...")
-        password = await self.password_asker.async_exec()
-        if self.password_asker.result() == QDialog.Rejected:
-            self.ui.button_box.setEnabled(True)
-            return
-
-        logging.debug("Setting cursor...")
-        QApplication.setOverrideCursor(Qt.WaitCursor)
-
-        logging.debug("Send money...")
-        result = await self.wallet.send_money(self.account.salt, password, self.account.scrypt_params,
-                                              self.community, recipient, amount, comment)
-        if result[0]:
-            logging.debug("Checking result to display...")
-            if self.app.preferences['notifications']:
-                toast.display(self.tr("Transfer"),
-                          self.tr("Success sending money to {0}").format(recipient))
-            else:
-                await QAsyncMessageBox.information(self.widget, self.tr("Transfer"),
-                          self.tr("Success sending money to {0}").format(recipient))
-            logging.debug("Restore cursor...")
-            QApplication.restoreOverrideCursor()
-
-            # If we sent back a transaction we cancel the first one
-            if self.transfer:
-                self.transfer.cancel()
-            self.app.refresh_transfers.emit()
-            self.widget.accept()
-        else:
-            logging.debug("Error occured...")
-            if self.app.preferences['notifications']:
-                toast.display(self.tr("Transfer"), "Error : {0}".format(result[1]))
-            else:
-                await QAsyncMessageBox.critical(self.widget, self.tr("Transfer"), result[1])
-
-            QApplication.restoreOverrideCursor()
-            self.ui.button_box.setEnabled(True)
-
-    @asyncify
-    async def amount_changed(self, value):
-        ud_block = await self.community.get_ud_block()
-        if ud_block:
-            dividend = ud_block['dividend']
-            base = ud_block['unitbase']
-        else:
-            dividend = 1
-            base = 0
-        relative = value / (dividend * pow(10, base))
-        self.ui.spinbox_relative.blockSignals(True)
-        self.ui.spinbox_relative.setValue(relative)
-        self.ui.spinbox_relative.blockSignals(False)
-        correct_amount = int(pow(10, base) * round(float(value) / pow(10, base)))
-        self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(correct_amount == value)
-
-    @asyncify
-    async def relative_amount_changed(self, value):
-        ud_block = await self.community.get_ud_block()
-        if ud_block:
-            dividend = ud_block['dividend']
-            base = ud_block['unitbase']
-        else:
-            dividend = 1
-            base = 0
-        amount = value * dividend * pow(10, base)
-        amount = int(pow(10, base) * round(float(amount) / pow(10, base)))
-        self.ui.spinbox_amount.blockSignals(True)
-        self.ui.spinbox_amount.setValue(amount)
-        self.ui.spinbox_amount.blockSignals(False)
-        self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(True)
-
-    @asyncify
-    async def change_current_community(self, index):
-        self.community = self.account.communities[index]
-        amount = await self.wallet.value(self.community)
-
-        ref_text = await self.account.current_ref.instance(amount, self.community, self.app)\
-            .diff_localized(units=True,
-                            international_system=self.app.preferences['international_system_of_units'])
-        self.ui.label_total.setText("{0}".format(ref_text))
-        self.ui.spinbox_amount.setSuffix(" " + self.community.currency)
-        await self.refresh_spinboxes()
-
-    @asyncify
-    async def change_displayed_wallet(self, index):
-        self.wallet = self.account.wallets[index]
-        amount = await self.wallet.value(self.community)
-        ref_text = await self.account.current_ref.instance(amount, self.community, self.app)\
-            .diff_localized(units=True,
-                            international_system=self.app.preferences['international_system_of_units'])
-        self.ui.label_total.setText("{0}".format(ref_text))
-        await self.refresh_spinboxes()
-
-    async def refresh_spinboxes(self):
-        max_amount = await self.wallet.value(self.community)
-        ud_block = await self.community.get_ud_block()
-        if ud_block:
-            dividend = ud_block['dividend']
-            base = ud_block['unitbase']
-        else:
-            dividend = 1
-            base = 0
-        max_amount = int(pow(10, base) * round(float(max_amount) / pow(10, base)))
-        max_relative = max_amount / dividend
-        self.ui.spinbox_amount.setMaximum(max_amount)
-        self.ui.spinbox_relative.setMaximum(max_relative)
-        self.ui.spinbox_amount.setSingleStep(pow(10, base))
-
-    def recipient_mode_changed(self, radio):
-        self.ui.edit_pubkey.setEnabled(radio == "pubkey")
-        self.ui.combo_contact.setEnabled(radio == "contact")
-        self.ui.search_user.setEnabled(radio == "search")
-
-    def async_exec(self):
-        future = asyncio.Future()
-        self.widget.finished.connect(lambda r: future.set_result(r) and self.widget.finished.disconnect())
-        self.widget.open()
-        return future
-
-    def exec(self):
-        self.widget.exec()
\ No newline at end of file
diff --git a/src/sakia/gui/views/__init__.py b/src/sakia/gui/views/__init__.py
deleted file mode 100644
index 3ca1e9f7ff0ac7bd3d3e7c9e4d8344dd36a87a54..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .explorer import ExplorerView
-from .wot import WotView
\ No newline at end of file
diff --git a/src/sakia/gui/views/edges/__init__.py b/src/sakia/gui/views/edges/__init__.py
deleted file mode 100644
index 188e3acfd2ccf101092227b52be070f696cd68c4..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/edges/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .wot_edge import WotEdge
-from .explorer_edge import ExplorerEdge
\ No newline at end of file
diff --git a/src/sakia/gui/views/edges/explorer_edge.py b/src/sakia/gui/views/edges/explorer_edge.py
deleted file mode 100644
index 6f39bea9cc3fc8879eaa3575eadf0b5d2065b2c8..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/edges/explorer_edge.py
+++ /dev/null
@@ -1,154 +0,0 @@
-from PyQt5.QtCore import Qt, QRectF, QLineF, QPointF, QSizeF, \
-                        qFuzzyCompare, QTimeLine
-from PyQt5.QtGui import QColor, QPen, QPolygonF
-import math
-from .base_edge import BaseEdge
-from ....core.graph.constants import EdgeStatus
-
-
-class ExplorerEdge(BaseEdge):
-    def __init__(self, source_node, destination_node, metadata, nx_pos, steps, steps_max):
-        """
-        Create an arc between two nodes
-
-        :param Node source_node: Source node of the arc
-        :param Node destination_node: Destination node of the arc
-        :param dict metadata: Arc metadata
-        :param dict nx_pos: The position generated by nx_graph
-        :param int steps: The steps from the center identity
-        :param int steps_max: The steps max of the graph
-        """
-        super().__init__(source_node, destination_node, metadata, nx_pos)
-
-        self.source_point = self.destination_point
-        self.steps = steps
-        self.steps_max = steps_max
-        self.highlighted = False
-
-        self.arrow_size = 5
-        #  cursor change on hover
-        self.setAcceptHoverEvents(True)
-        self.setZValue(0)
-        self._line_styles = {
-            EdgeStatus.STRONG: Qt.SolidLine,
-            EdgeStatus.WEAK: Qt.DashLine
-        }
-        self.timeline = None
-
-    @property
-    def line_style(self):
-        return self._line_styles[self.status]
-
-    # virtual function require subclassing
-    def boundingRect(self):
-        """
-        Return the bounding rectangle size
-
-        :return: QRectF
-        """
-        if not self.source or not self.destination:
-            return QRectF()
-        pen_width = 1.0
-        extra = (pen_width + self.arrow_size) / 2.0
-
-        return QRectF(
-            self.source_point, QSizeF(
-                self.destination_point.x() - self.source_point.x(),
-                self.destination_point.y() - self.source_point.y()
-            )
-        ).normalized().adjusted(
-            -extra,
-            -extra,
-            extra,
-            extra
-        )
-
-    def paint(self, painter, option, widget):
-        """
-        Customize line adding an arrow head
-
-        :param QPainter painter: Painter instance of the item
-        :param option:  Painter option of the item
-        :param widget:  Widget instance
-        """
-        if not self.source or not self.destination:
-            return
-        line = QLineF(self.source_point, self.destination_point)
-        if qFuzzyCompare(line.length(), 0):
-            return
-
-        # Draw the line itself
-        color = QColor()
-        color.setHsv(120 - 60 / self.steps_max * self.steps,
-                     180 + 50 / self.steps_max * self.steps,
-                     150 + 80 / self.steps_max * self.steps)
-        if self.highlighted:
-            color.setHsv(0, 0, 0)
-
-        style = self.line_style
-
-        painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin))
-        painter.drawLine(line)
-        painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
-
-        # Draw the arrows
-        angle = math.acos(line.dx() / line.length())
-        if line.dy() >= 0:
-            angle = (2.0 * math.pi) - angle
-
-        #  arrow in the middle of the arc
-        hpx = line.p1().x() + (line.dx() / 2.0)
-        hpy = line.p1().y() + (line.dy() / 2.0)
-        head_point = QPointF(hpx, hpy)
-
-        painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
-        destination_arrow_p1 = head_point + QPointF(
-            math.sin(angle - math.pi / 3) * self.arrow_size,
-            math.cos(angle - math.pi / 3) * self.arrow_size)
-        destination_arrow_p2 = head_point + QPointF(
-            math.sin(angle - math.pi + math.pi / 3) * self.arrow_size,
-            math.cos(angle - math.pi + math.pi / 3) * self.arrow_size)
-
-        painter.setBrush(color)
-        painter.drawPolygon(QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2]))
-
-        if self.metadata["confirmation_text"]:
-            painter.drawText(head_point, self.metadata["confirmation_text"])
-
-    def move_source_point(self, node_id, x, y):
-        """
-        Move to corresponding position
-        :param str node_id: the node id
-        :param float x: x coordinates
-        :param float y: y coordinates
-        :return:
-        """
-        if node_id == self.source:
-            self.source_point = QPointF(x, y)
-            self.update(self.boundingRect())
-
-    def move_destination_point(self, node_id, x, y):
-        """
-        Move to corresponding position
-        :param str node_id: the node id
-        :param float x: x coordinates
-        :param float y: y coordinates
-        :return:
-        """
-        if node_id == self.destination:
-            self.destination_point = QPointF(x, y)
-            self.update(self.boundingRect())
-
-    def highlight(self):
-        """
-        Highlight the edge in the scene
-        """
-        self.highlighted = True
-        self.update(self.boundingRect())
-
-    def neutralize(self):
-        """
-        Neutralize the edge in the scene
-        """
-        self.highlighted = False
-        self.update(self.boundingRect())
diff --git a/src/sakia/gui/views/explorer.py b/src/sakia/gui/views/explorer.py
deleted file mode 100644
index 927d9f085cb63c2c633a878a0c70cb4f380781f1..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/explorer.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import logging
-
-import networkx
-from PyQt5.QtCore import Qt, QPoint, pyqtSignal
-from PyQt5.QtGui import QPainter, QWheelEvent
-from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene
-
-from .scenes import ExplorerScene
-
-
-class ExplorerView(QGraphicsView):
-    def __init__(self, parent=None):
-        """
-        Create View to display scene
-
-        :param parent:  [Optional, default=None] Parent widget
-        """
-        super().__init__(parent)
-
-        self.setScene(ExplorerScene(self))
-
-        self.setCacheMode(QGraphicsView.CacheBackground)
-        self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
-        self.setRenderHint(QPainter.Antialiasing)
-        self.setRenderHint(QPainter.SmoothPixmapTransform)
-
-    def wheelEvent(self, event: QWheelEvent):
-        """
-        Zoom in/out on the mouse cursor
-        """
-        # zoom only when CTRL key pressed
-        if (event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier:
-            steps = event.angleDelta().y() / 15 / 8
-
-            if steps == 0:
-                event.ignore()
-                return
-
-            # scale factor 1.25
-            sc = pow(1.25, steps)
-            self.scale(sc, sc)
-            self.centerOn(self.mapToScene(event.pos()))
-            event.accept()
-        #  act normally on scrollbar
-        else:
-            # transmit event to parent class wheelevent
-            super(QGraphicsView, self).wheelEvent(event)
diff --git a/src/sakia/gui/views/nodes/__init__.py b/src/sakia/gui/views/nodes/__init__.py
deleted file mode 100644
index 28332381ab8beea33a4eb493b6d21af09a582adb..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/nodes/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .wot_node import WotNode
-from .explorer_node import ExplorerNode
\ No newline at end of file
diff --git a/src/sakia/gui/views/nodes/explorer_node.py b/src/sakia/gui/views/nodes/explorer_node.py
deleted file mode 100644
index 854d1e8fef5ac9b9eaa50aaf9981a1a696840037..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/nodes/explorer_node.py
+++ /dev/null
@@ -1,189 +0,0 @@
-from PyQt5.QtWidgets import QGraphicsSimpleTextItem
-from PyQt5.QtCore import Qt, QPointF, QTimeLine, QTimer
-from PyQt5.QtGui import QTransform, QColor, QPen, QBrush, QRadialGradient
-from ....core.graph.constants import NodeStatus
-from .base_node import BaseNode
-import math
-
-
-class ExplorerNode(BaseNode):
-    def __init__(self, nx_node, center_pos, nx_pos, steps, steps_max, small):
-        """
-        Create node in the graph scene
-
-        :param tuple nx_node: Node info
-        :param center_pos: The position of the center node
-        :param nx_pos: Position of the nodes in the graph
-        :param int steps: The steps from the center identity
-        :param int steps_max: The steps max of the graph
-        :param bool small: Small dots for big networks
-        """
-        super().__init__(nx_node, nx_pos)
-
-        self.steps = steps
-        self.steps_max = steps_max
-        self.highlighted = False
-        self.status_sentry = False
-
-        if small:
-            self.setRect(
-                0,
-                0,
-                10,
-                10
-            )
-            self.text_item = None
-        else:
-            # text inside ellipse
-            self.text_item = QGraphicsSimpleTextItem(self)
-            self.text_item.setText(self.text)
-            # center ellipse around text
-            self.setRect(
-                0,
-                0,
-                self.text_item.boundingRect().width() * 2,
-                self.text_item.boundingRect().height() * 2
-            )
-            # center text in ellipse
-            self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0)
-
-
-        #  set anchor to the center
-        self.setTransform(
-            QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0))
-
-        # cursor change on hover
-        self.setAcceptHoverEvents(True)
-        self.setZValue(1)
-
-        # animation and moves
-        self.timeline = None
-        self.loading_timer = QTimer()
-        self.loading_timer.timeout.connect(self.next_tick)
-        self.loading_counter = 0
-        self._refresh_colors()
-        self.setPos(center_pos)
-        self.move_to(nx_pos)
-
-    def update_metadata(self, metadata):
-        super().update_metadata(metadata)
-        self.status_sentry = self.metadata['is_sentry'] if 'is_sentry' in self.metadata else False
-        self._refresh_colors()
-
-    def _refresh_colors(self):
-        """
-        Refresh elements in the node
-        """
-        # color around ellipse
-        outline_color = QColor('grey')
-        outline_style = Qt.SolidLine
-        outline_width = 1
-        if self.status_wallet:
-            outline_width = 2
-        if not self.status_member:
-            outline_color = QColor('red')
-
-        if self.status_sentry:
-            outline_color = QColor('black')
-            outline_width = 3
-
-        self.setPen(QPen(outline_color, outline_width, outline_style))
-
-        if self.highlighted:
-            text_color = QColor('grey')
-        else:
-            text_color = QColor('black')
-
-        if self.status_wallet == NodeStatus.HIGHLIGHTED:
-            text_color = QColor('grey')
-
-        if self.text_item:
-            self.text_item.setBrush(QBrush(text_color))
-
-        # create gradient inside the ellipse
-        gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width())
-        color = QColor()
-        color.setHsv(120 - 60 / self.steps_max * self.steps,
-                     180 + 50 / self.steps_max * self.steps,
-                     60 + 170 / self.steps_max * self.steps)
-        if self.highlighted:
-            color = color.darker(200)
-        color = color.lighter(math.fabs(math.sin(self.loading_counter / 100 * math.pi) * 100) + 100)
-        gradient.setColorAt(0, color)
-        gradient.setColorAt(1, color.darker(150))
-        self.setBrush(QBrush(gradient))
-
-    def move_to(self, nx_pos):
-        """
-        Move to corresponding position
-        :param nx_pos:
-        :return:
-        """
-        origin_x = self.x()
-        origin_y = self.y()
-        final_x = nx_pos[self.id][0]
-        final_y = nx_pos[self.id][1]
-
-        def frame_move(frame):
-            value = self.timeline.valueForTime(self.timeline.currentTime())
-            x = origin_x + (final_x - origin_x) * value
-            y = origin_y + (final_y - origin_y) * value
-            self.setPos(x, y)
-            if self.scene():
-                self.scene().node_moved.emit(self.id, x, y)
-
-        def timeline_ends():
-            self.setPos(final_x, final_y)
-            self.timeline = None
-
-        # Remember to hold the references to QTimeLine and QGraphicsItemAnimation instances.
-        # They are not kept anywhere, even if you invoke QTimeLine.start().
-        self.timeline = QTimeLine(1000)
-        self.timeline.setFrameRange(0, 100)
-        self.timeline.frameChanged.connect(frame_move)
-        self.timeline.finished.connect(timeline_ends)
-
-        self.timeline.start()
-
-    def highlight(self):
-        """
-        Highlight the edge in the scene
-        """
-        self.highlighted = True
-        self._refresh_colors()
-        self.update(self.boundingRect())
-
-    def neutralize(self):
-        """
-        Neutralize the edge in the scene
-        """
-        self.highlighted = False
-        self._refresh_colors()
-        self.update(self.boundingRect())
-
-    def start_loading_animation(self):
-        """
-        Neutralize the edge in the scene
-        """
-        if not self.loading_timer.isActive():
-            self.loading_timer.start(10)
-
-    def stop_loading_animation(self):
-        """
-        Neutralize the edge in the scene
-        """
-        self.loading_timer.stop()
-        self.loading_counter = 100
-        self._refresh_colors()
-        self.update(self.boundingRect())
-
-    def next_tick(self):
-        """
-        Next tick
-        :return:
-        """
-        self.loading_counter += 1
-        self.loading_counter %= 100
-        self._refresh_colors()
-        self.update(self.boundingRect())
-
diff --git a/src/sakia/gui/views/scenes/__init__.py b/src/sakia/gui/views/scenes/__init__.py
deleted file mode 100644
index f2eef13ee6a02e87b65491aa2d076785a7fc84d1..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/scenes/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .wot_scene import WotScene
-from .explorer_scene import ExplorerScene
\ No newline at end of file
diff --git a/src/sakia/gui/views/scenes/explorer_scene.py b/src/sakia/gui/views/scenes/explorer_scene.py
deleted file mode 100644
index 11b8511c8ecce401d6079dfc7275826ed0cb1b23..0000000000000000000000000000000000000000
--- a/src/sakia/gui/views/scenes/explorer_scene.py
+++ /dev/null
@@ -1,309 +0,0 @@
-import networkx
-import logging
-import math
-from PyQt5.QtCore import QPoint, pyqtSignal
-from PyQt5.QtWidgets import QGraphicsScene
-
-from ..edges import ExplorerEdge
-from ..nodes import ExplorerNode
-
-from .base_scene import BaseScene
-
-
-class ExplorerScene(BaseScene):
-
-    node_moved = pyqtSignal(str, float, float)
-
-    def __init__(self, parent=None):
-        """
-        Create scene of the graph
-
-        :param parent: [Optional, default=None] Parent view
-        """
-        super().__init__(parent)
-
-        self.lastDragPos = QPoint()
-        self.setItemIndexMethod(QGraphicsScene.NoIndex)
-
-        # list of nodes in scene
-        self.nodes = dict()
-        #  axis of the scene for debug purpose
-        # self.addLine(-100, 0, 100, 0)
-        # self.addLine(0, -100, 0, 100)
-        self.node_hovered.connect(self.display_path_to)
-
-        # list of nodes in scene
-        self.nodes = dict()
-        self.edges = dict()
-        self.busy = None
-        self.nx_graph = None
-        self.identity = None
-        #  axis of the scene for debug purpose
-        # self.addLine(-100, 0, 100, 0)
-        # self.addLine(0, -100, 0, 100)
-
-    @staticmethod
-    def _init_layout(nx_graph):
-        """
-        Init the data of the layout
-        :param MultiGraph nx_graph:
-        """
-        data = {}
-        INF = len(nx_graph.nodes()) * len(nx_graph.nodes())
-
-        for node in nx_graph.nodes():
-            data[node] = {
-                'theta': None,
-                'scenter': INF,
-                'nchild': 0,
-                'sparent': None,
-                'stsize': 0.0,
-                'span': 0.0
-            }
-        return data
-
-    @staticmethod
-    def _set_nstep_to_center(nx_graph, data, current):
-        """
-        Set the number of steps to the center
-        :param networkx.MultiGraph nx_graph: the graph
-        :param dict data: the data of the layout
-        """
-        queue = [current]
-        while queue:
-            n = queue.pop()
-            nsteps = data[n]['scenter'] + 1
-            for edge in networkx.edges(nx_graph.to_undirected(), n):
-                next_node = edge[0] if edge[0] is not n else edge[1]
-                if data[next_node]['sparent']:
-                    continue
-                if nsteps < data[next_node]['scenter']:
-                    data[next_node]['scenter'] = nsteps
-                    data[next_node]['sparent'] = n
-                    data[n]['nchild'] += 1
-                    queue.append(next_node)
-
-    @staticmethod
-    def _set_parent_nodes(nx_graph, data, center):
-        """
-        Set the parent of each node
-        :param networkx.MultiGraph nx_graph: the graph
-        :param dict data: the data of the layout
-        :param str center: the id of the node at the center
-        """
-        unset = data[center]['scenter']
-        data[center]['scenter'] = 0
-        data[center]['sparent'] = None
-
-        logging.debug("Parent node of {0}".format(center))
-        ExplorerScene._set_nstep_to_center(nx_graph, data, center)
-        for node in nx_graph.nodes():
-            if data[node]['scenter'] == unset:
-                return -1
-        return max([n['scenter'] for n in data.values()])
-
-    @staticmethod
-    def _set_subtree_size(nx_graph, data):
-        """
-        Compute the subtree size of each node, which is the
-        number of leaves in subtree rooted to the node
-        :param networkx.MultiGraph nx_graph: the graph
-        :param dict data:
-        """
-        for node in nx_graph.nodes():
-            if data[node]['nchild'] > 0:
-                continue
-            data[node]['stsize'] += 1
-            parent = data[node]['sparent']
-            while parent:
-                data[parent]['stsize'] += 1
-                parent = data[parent]['sparent']
-
-    @staticmethod
-    def _set_subtree_spans(nx_graph, data, current):
-        """
-        Compute the subtree spans of each node
-        :param networkx.MultiGraph nx_graph: the graph
-        :param dict data: the data of the layout
-        :param str current: the current node which we compute the subtree
-        """
-        ratio = data[current]['span'] / data[current]['stsize']
-        for edge in nx_graph.to_undirected().edges(current):
-            next_node = edge[0] if edge[0] != current else edge[1]
-            if data[next_node]['sparent'] != current:
-                continue
-            if data[next_node]['span'] != 0.0:
-                continue
-
-            data[next_node]['span'] = ratio * data[next_node]['stsize']
-            if data[next_node]['nchild'] > 0:
-                ExplorerScene._set_subtree_spans(nx_graph, data, next_node)
-
-    @staticmethod
-    def _set_positions(nx_graph, data, current):
-        """
-        Compute the polar positions of each node
-        :param networkx.MultiDiGraph nx_graph: the graph
-        :param dict data: the data of the layout
-        :param str current: the current node which we compute the subtree
-        """
-        if not data[current]['sparent']:
-            theta = 0
-        else:
-            theta = data[current]['theta'] - data[current]['span'] / 2
-
-        for edge in nx_graph.to_undirected().edges(current):
-            next_node = edge[0] if edge[0] != current else edge[1]
-            if data[next_node]['sparent'] != current:
-                continue
-            if data[next_node]['theta']:
-                continue
-
-            data[next_node]['theta'] = theta + data[next_node]['span'] / 2.0
-            theta += data[next_node]['span']
-            if data[next_node]['nchild'] > 0:
-                ExplorerScene._set_positions(nx_graph, data, next_node)
-
-    @staticmethod
-    def twopi_layout(nx_graph, center=None):
-        """
-        Render the twopi layout. Ported from C code available at
-        https://github.com/ellson/graphviz/blob/master/lib/twopigen/circle.c
-
-        :param networkx.MultiDiGraph nx_graph: the networkx graph
-        :param str center: the centered node
-        :return:
-        """
-        if len(nx_graph.nodes()) == 0:
-            return {}
-
-        if len(nx_graph.nodes()) == 1:
-            return {nx_graph.nodes()[0]: (0, 0)}
-        #nx_graph = nx_graph.to_undirected()
-
-        data = ExplorerScene._init_layout(nx_graph)
-        if not center:
-            center = networkx.center(nx_graph)[0]
-        ExplorerScene._set_parent_nodes(nx_graph, data, center)
-        ExplorerScene._set_subtree_size(nx_graph, data)
-        data[center]['span'] = 2 * math.pi
-        ExplorerScene._set_subtree_spans(nx_graph, data, center)
-        data[center]['theta'] = 0.0
-        ExplorerScene._set_positions(nx_graph, data, center)
-
-        distances = networkx.shortest_path_length(nx_graph.to_undirected(), center)
-        nx_pos = {}
-        for node in nx_graph.nodes():
-            hyp = distances[node] + 1
-            theta = data[node]['theta']
-            nx_pos[node] = (hyp * math.cos(theta) * 100, hyp * math.sin(theta) * 100)
-        return nx_pos
-
-    def clear(self):
-        """
-        clear the scene
-        """
-        for node in self.nodes.values():
-            if node.timeline:
-                node.timeline.stop()
-
-        self.nodes.clear()
-        self.edges.clear()
-        super().clear()
-
-    def update_current_identity(self, identity_pubkey):
-        """
-        Update the current identity loaded
-
-        :param str identity_pubkey:
-        """
-        for node in self.nodes.values():
-            node.stop_loading_animation()
-
-        if identity_pubkey in self.nodes:
-            self.nodes[identity_pubkey].start_loading_animation()
-
-    def update_wot(self, nx_graph, identity, dist_max):
-        """
-        draw community graph
-
-        :param networkx.Graph nx_graph: graph to draw
-        :param sakia.core.registry.Identity identity: the wot of the identity
-        :param dist_max: the dist_max to display
-        """
-        #  clear scene
-        self.identity = identity
-        self.nx_graph = nx_graph.copy()
-
-        graph_pos = ExplorerScene.twopi_layout(nx_graph, center=identity.pubkey)
-        if len(nx_graph.nodes()) > 0:
-            distances = networkx.shortest_path_length(nx_graph.to_undirected(), identity.pubkey)
-        else:
-            distances = {}
-
-        # create networkx graph
-        for nx_node in nx_graph.nodes(data=True):
-            if nx_node[0] in self.nodes:
-                v = self.nodes[nx_node[0]]
-                v.move_to(graph_pos)
-                v.update_metadata(nx_node[1])
-            else:
-                center_pos = None
-                if len(nx_graph.edges(nx_node[0])) > 0:
-                    for edge in nx_graph.edges(nx_node[0]):
-                        neighbour = edge[0] if edge[0] != nx_node[0] else edge[1]
-                        if neighbour in self.nodes:
-                            center_pos = self.nodes[neighbour].pos()
-                            break
-                if not center_pos:
-                    if identity.pubkey in self.nodes:
-                        center_pos = self.nodes[identity.pubkey].pos()
-                    else:
-                        center_pos = QPoint(0, 0)
-
-                small = distances[nx_node[0]] > 1
-
-                v = ExplorerNode(nx_node, center_pos, graph_pos, distances[nx_node[0]], dist_max, small)
-                self.addItem(v)
-                self.nodes[nx_node[0]] = v
-
-        for edge in nx_graph.edges(data=True):
-            edge[2]["confirmation_text"] = ""
-            if (edge[0], edge[1]) not in self.edges:
-                distance = max(self.nodes[edge[0]].steps, self.nodes[edge[1]].steps)
-                explorer_edge = ExplorerEdge(edge[0], edge[1], edge[2], graph_pos, distance, dist_max)
-                self.node_moved.connect(explorer_edge.move_source_point)
-                self.node_moved.connect(explorer_edge.move_destination_point)
-                self.addItem(explorer_edge)
-                self.edges[(edge[0], edge[1])] = explorer_edge
-
-        self.update()
-
-    def display_path_to(self, node_id):
-        if node_id != self.identity.pubkey:
-            for edge in self.edges.values():
-                edge.neutralize()
-
-            for node in self.nodes.values():
-                node.neutralize()
-
-            path = []
-            try:
-                path = networkx.shortest_path(self.nx_graph, node_id, self.identity.pubkey)
-            except (networkx.exception.NetworkXError, networkx.exception.NetworkXNoPath) as e:
-                logging.debug(str(e))
-                try:
-                    path = networkx.shortest_path(self.nx_graph, self.identity.pubkey, node_id)
-                except (networkx.exception.NetworkXError, networkx.exception.NetworkXNoPath) as e:
-                    logging.debug(str(e))
-
-            for node, next_node in zip(path[:-1], path[1:]):
-                if (node, next_node) in self.edges:
-                    edge = self.edges[(node, next_node)]
-                elif (next_node, node) in self.edges:
-                    edge = self.edges[(next_node, node)]
-                if edge:
-                    edge.highlight()
-                    self.nodes[node].highlight()
-                    self.nodes[next_node].highlight()
-                    logging.debug("Update edge between {0} and {1}".format(node, next_node))
diff --git a/src/sakia/gui/widgets/context_menu.py b/src/sakia/gui/widgets/context_menu.py
index 60e7b4a14ca899b66231c30894fe693978ee6b76..9a869bcf824345a04a08607a98c4e270822e0304 100644
--- a/src/sakia/gui/widgets/context_menu.py
+++ b/src/sakia/gui/widgets/context_menu.py
@@ -1,35 +1,31 @@
-from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox
-from PyQt5.QtCore import QObject, pyqtSignal
-from duniterpy.documents import Block, Membership
 import logging
 
-from ..member import MemberDialog
-from ..contact import ConfigureContactDialog
-from ..transfer import TransferMoneyDialog
-from ..certification import CertificationDialog
-from ...tools.decorators import asyncify
-from ...core.transfer import Transfer, TransferState
-from ...core.registry import Identity
-from ...tools.exceptions import MembershipNotFoundError
+from PyQt5.QtCore import QObject, pyqtSignal
+from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox
+
+from duniterpy.documents import Block
+from sakia.data.entities import Identity, Transaction
+from sakia.data.processors import BlockchainProcessor, TransactionsProcessor
+from sakia.decorators import asyncify
+from sakia.gui.dialogs.certification.controller import CertificationController
+from sakia.gui.dialogs.transfer.controller import TransferController
+from sakia.gui.sub.user_information.controller import UserInformationController
 
 
 class ContextMenu(QObject):
     view_identity_in_wot = pyqtSignal(object)
+    identity_information_loaded = pyqtSignal(Identity)
 
-    def __init__(self, qmenu, app, account, community, password_asker):
+    def __init__(self, qmenu, app, connection):
         """
         :param PyQt5.QtWidgets.QMenu: the qmenu widget
-        :param sakia.core.Application app: Application instance
-        :param sakia.core.Account account: The current account instance
-        :param sakia.core.Community community: The community instance
-        :param sakia.gui.PasswordAsker password_asker: The password dialog
+        :param sakia.app.Application app: Application instance
+        :param sakia.data.entities.Connection connection: The current connection instance
         """
         super().__init__()
         self.qmenu = qmenu
         self._app = app
-        self._community = community
-        self._account = account
-        self._password_asker = password_asker
+        self._connection = connection
 
     @staticmethod
     def _add_identity_actions(menu, identity):
@@ -37,40 +33,31 @@ class ContextMenu(QObject):
         :param ContextMenu menu: the qmenu to add actions to
         :param Identity identity: the identity
         """
-        menu.qmenu.addSeparator().setText(identity.uid)
+        menu.qmenu.addSeparator().setText(identity.uid if identity.uid else "Pubkey")
 
         informations = QAction(menu.qmenu.tr("Informations"), menu.qmenu.parent())
         informations.triggered.connect(lambda checked, i=identity: menu.informations(i))
         menu.qmenu.addAction(informations)
 
-        if menu._account.pubkey != identity.pubkey:
-            add_as_contact = QAction(menu.qmenu.tr("Add as contact"), menu.qmenu.parent())
-            add_as_contact.triggered.connect(lambda checked, i=identity: menu.add_as_contact(i))
-            menu.qmenu.addAction(add_as_contact)
-
-        if menu._account.pubkey != identity.pubkey:
+        if menu._connection.pubkey != identity.pubkey:
             send_money = QAction(menu.qmenu.tr("Send money"), menu.qmenu.parent())
             send_money.triggered.connect(lambda checked, i=identity: menu.send_money(i))
             menu.qmenu.addAction(send_money)
 
-        if menu._account.pubkey != identity.pubkey:
+        if identity.uid and menu._connection.pubkey != identity.pubkey:
             certify = QAction(menu.tr("Certify identity"), menu.qmenu.parent())
             certify.triggered.connect(lambda checked, i=identity: menu.certify_identity(i))
             menu.qmenu.addAction(certify)
 
-        view_wot = QAction(menu.qmenu.tr("View in Web of Trust"), menu.qmenu.parent())
-        view_wot.triggered.connect(lambda checked, i=identity: menu.view_wot(i))
-        menu.qmenu.addAction(view_wot)
+            view_wot = QAction(menu.qmenu.tr("View in Web of Trust"), menu.qmenu.parent())
+            view_wot.triggered.connect(lambda checked, i=identity: menu.view_wot(i))
+            menu.qmenu.addAction(view_wot)
 
         copy_pubkey = QAction(menu.qmenu.tr("Copy pubkey to clipboard"), menu.qmenu.parent())
         copy_pubkey.triggered.connect(lambda checked, i=identity: ContextMenu.copy_pubkey_to_clipboard(i))
         menu.qmenu.addAction(copy_pubkey)
 
-        if menu._app.preferences['expert_mode']:
-            copy_membership = QAction(menu.qmenu.tr("Copy membership document to clipboard"), menu.qmenu.parent())
-            copy_membership.triggered.connect(lambda checked, i=identity: menu.copy_membership_to_clipboard(i))
-            menu.qmenu.addAction(copy_membership)
-
+        if identity.uid and menu._app.parameters.expert_mode:
             copy_selfcert = QAction(menu.qmenu.tr("Copy self-certification document to clipboard"), menu.qmenu.parent())
             copy_selfcert.triggered.connect(lambda checked, i=identity: menu.copy_selfcert_to_clipboard(i))
             menu.qmenu.addAction(copy_selfcert)
@@ -82,7 +69,7 @@ class ContextMenu(QObject):
         :param Transfer transfer: the transfer
         """
         menu.qmenu.addSeparator().setText(menu.qmenu.tr("Transfer"))
-        if transfer.state in (TransferState.REFUSED, TransferState.TO_SEND):
+        if transfer.state in (Transaction.REFUSED, Transaction.TO_SEND):
             send_back = QAction(menu.qmenu.tr("Send again"), menu.qmenu.parent())
             send_back.triggered.connect(lambda checked, tr=transfer: menu.send_again(tr))
             menu.qmenu.addAction(send_back)
@@ -91,38 +78,35 @@ class ContextMenu(QObject):
             cancel.triggered.connect(lambda checked, tr=transfer: menu.cancel_transfer(tr))
             menu.qmenu.addAction(cancel)
 
-        if menu._app.preferences['expert_mode']:
+        if menu._app.parameters.expert_mode:
             copy_doc = QAction(menu.qmenu.tr("Copy raw transaction to clipboard"), menu.qmenu.parent())
             copy_doc.triggered.connect(lambda checked, tx=transfer: menu.copy_transaction_to_clipboard(tx))
             menu.qmenu.addAction(copy_doc)
 
-            if transfer.blockUID:
+            if transfer.blockstamp:
                 copy_doc = QAction(menu.qmenu.tr("Copy transaction block to clipboard"), menu.qmenu.parent())
-                copy_doc.triggered.connect(lambda checked, number=transfer.blockUID.number:
-                                           menu.copy_block_to_clipboard(number))
+                copy_doc.triggered.connect(lambda checked, number=transfer.blockstamp.number:
+                                           menu.copy_block_to_clipboard(transfer.blockstamp.number))
                 menu.qmenu.addAction(copy_doc)
 
-
     @classmethod
-    def from_data(cls, parent, app, account, community, password_asker, data):
+    def from_data(cls, parent, app, connection, data):
         """
         Builds a QMenu from data passed as parameters
         Data can be Identity or Transfer
 
         :param PyQt5.QtWidgets.QWidget parent: the parent widget
-        :param sakia.core.Application app: the application
-        :param sakia.core.Application app: Application instance
-        :param sakia.core.Account account: The current account instance
-        :param sakia.core.Community community: The community instance
-        :param sakia.gui.PasswordAsker password_asker: The password dialog
+        :param sakia.app.Application app: Application instance
+        :param sakia.data.entities.Connection connection: the current connection
         :param tuple data: a tuple of data to add to the menu
         :rtype: ContextMenu
         """
-        menu = cls(QMenu(parent), app, account, community, password_asker)
+        menu = cls(QMenu(parent), app, connection)
         build_actions = {
             Identity: ContextMenu._add_identity_actions,
-            Transfer: ContextMenu._add_transfers_actions,
-            dict: lambda m, d: None
+            Transaction: ContextMenu._add_transfers_actions,
+            dict: lambda m, d: None,
+            type(None): lambda m, d: None
         }
         for d in data:
             build_actions[type(d)](menu, d)
@@ -135,31 +119,27 @@ class ContextMenu(QObject):
         clipboard.setText(identity.pubkey)
 
     def informations(self, identity):
-        MemberDialog.open_dialog(self._app, self._account, self._community, identity)
+        if identity.uid:
+            UserInformationController.show_identity(self.parent(), self._app, self._connection.currency, identity)
+            self.identity_information_loaded.emit(identity)
+        else:
+            UserInformationController.search_and_show_pubkey(self.parent(), self._app, self._connection.currency,
+                                                             identity.pubkey)
 
-    def add_as_contact(self, identity):
-        dialog = ConfigureContactDialog.from_identity(self._app, self.parent(), self._account, identity)
-        dialog.exec_()
 
     @asyncify
     async def send_money(self, identity):
-        await TransferMoneyDialog.send_money_to_identity(self._app, self._account, self._password_asker,
-                                                            self._community, identity)
-        self._app.refresh_transfers.emit()
+        await TransferController.send_money_to_identity(None, self._app, self._connection, identity)
 
     def view_wot(self, identity):
         self.view_identity_in_wot.emit(identity)
 
     @asyncify
     async def certify_identity(self, identity):
-        await CertificationDialog.certify_identity(self._app, self._account, self._password_asker,
-                                             self._community, identity)
+        await CertificationController.certify_identity(None, self._app, self._connection, identity)
 
-    @asyncify
-    async def send_again(self, transfer):
-        await TransferMoneyDialog.send_transfer_again(self._app, self._app.current_account,
-                                     self._password_asker, self._community, transfer)
-        self._app.refresh_transfers.emit()
+    def send_again(self, transfer):
+        TransferController.send_transfer_again(None, self._app, self._connection, transfer)
 
     def cancel_transfer(self, transfer):
         reply = QMessageBox.warning(self.qmenu, self.tr("Warning"),
@@ -167,43 +147,21 @@ class ContextMenu(QObject):
 This money transfer will be removed and not sent."""),
 QMessageBox.Ok | QMessageBox.Cancel)
         if reply == QMessageBox.Ok:
-            transfer.cancel()
-        self._app.refresh_transfers.emit()
+            transactions_processor = TransactionsProcessor.instanciate(self._app)
+            transactions_processor.cancel(transfer)
+            self._app.db.commit()
+            self._app.transaction_state_changed.emit(transfer)
 
-    @asyncify
-    async def copy_transaction_to_clipboard(self, tx):
+    def copy_transaction_to_clipboard(self, tx):
         clipboard = QApplication.clipboard()
-        raw_doc = await tx.get_raw_document(self._community)
-        if raw_doc:
-            clipboard.setText(raw_doc.signed_raw())
+        clipboard.setText(tx.raw)
 
     @asyncify
     async def copy_block_to_clipboard(self, number):
         clipboard = QApplication.clipboard()
-        block = await self._community.get_block(number)
-        if block:
-            block_doc = Block.from_signed_raw("{0}{1}\n".format(block['raw'], block['signature']))
-            clipboard.setText(block_doc.signed_raw())
-
-    @asyncify
-    async def copy_membership_to_clipboard(self, identity):
-        """
-
-        :param sakia.core.registry.Identity identity:
-        :return:
-        """
-        clipboard = QApplication.clipboard()
-        try:
-            membership = await identity.membership(self._community)
-            if membership:
-                block_number = membership['written']
-                block = await self._community.get_block(block_number)
-                block_doc = Block.from_signed_raw("{0}{1}\n".format(block['raw'], block['signature']))
-                for ms_doc in block_doc.joiners:
-                    if ms_doc.issuer == identity.pubkey:
-                        clipboard.setText(ms_doc.signed_raw())
-        except MembershipNotFoundError:
-            logging.debug("Could not find membership")
+        blockchain_processor = BlockchainProcessor.instanciate(self._app)
+        block_doc = await blockchain_processor.get_block(self._connection.currency, number)
+        clipboard.setText(block_doc.signed_raw())
 
     @asyncify
     async def copy_selfcert_to_clipboard(self, identity):
diff --git a/src/sakia/gui/widgets/search_user.py b/src/sakia/gui/widgets/search_user.py
deleted file mode 100644
index 6f4a0195e13294bdb5ca850ec9ee2667a8df15d3..0000000000000000000000000000000000000000
--- a/src/sakia/gui/widgets/search_user.py
+++ /dev/null
@@ -1,132 +0,0 @@
-import logging
-
-from PyQt5.QtCore import QEvent, pyqtSignal, QT_TRANSLATE_NOOP, Qt
-from PyQt5.QtWidgets import QComboBox, QWidget
-
-from duniterpy.api import bma, errors
-
-from ...tools.decorators import asyncify
-from ...tools.exceptions import NoPeerAvailable
-from ...core.registry import BlockchainState, Identity
-from ...gen_resources.search_user_view_uic import Ui_SearchUserWidget
-
-
-class SearchUserWidget(QWidget, Ui_SearchUserWidget):
-    _search_placeholder = QT_TRANSLATE_NOOP("SearchUserWidget", "Research a pubkey, an uid...")
-
-    identity_selected = pyqtSignal(Identity)
-    search_started = pyqtSignal()
-    search_completed = pyqtSignal()
-    reset = pyqtSignal()
-
-    def __init__(self, parent):
-        """
-        :param sakia.core.app.Application app: Application instance
-        """
-        # construct from qtDesigner
-        super().__init__(parent)
-        self.setupUi(self)
-        # Default text when combo lineEdit is empty
-        self.combobox_search.lineEdit().setPlaceholderText(self.tr(SearchUserWidget._search_placeholder))
-        #  add combobox events
-        self.combobox_search.lineEdit().returnPressed.connect(self.search)
-        # To fix a recall of the same item with different case,
-        # the edited text is not added in the item list
-        self.combobox_search.setInsertPolicy(QComboBox.NoInsert)
-        self.combobox_search.activated.connect(self.select_node)
-        self.button_reset.clicked.connect(self.reset)
-        self.nodes = list()
-        self.community = None
-        self.account = None
-        self.app = None
-        self._current_identity = None
-
-    def current_identity(self):
-        return self._current_identity
-
-    def init(self, app):
-        """
-        Initialize the widget
-        :param sakia.core.Application app: the application
-        """
-        self.app = app
-
-    def change_account(self, account):
-        self.account = account
-
-    def change_community(self, community):
-        self.community = community
-
-    @asyncify
-    async def search(self):
-        """
-        Search nodes when return is pressed in combobox lineEdit
-        """
-        self.search_started.emit()
-        text = self.combobox_search.lineEdit().text()
-        self.combobox_search.lineEdit().clear()
-        self.combobox_search.lineEdit().setPlaceholderText(self.tr("Looking for {0}...".format(text)))
-
-        if len(text) > 2:
-            try:
-                response = await self.community.bma_access.future_request(bma.wot.Lookup, {'search': text})
-
-                nodes = {}
-                for identity in response['results']:
-                    nodes[identity['pubkey']] = identity['uids'][0]['uid']
-
-                if nodes:
-                    self.nodes = list()
-                    self.blockSignals(True)
-                    self.combobox_search.clear()
-                    self.combobox_search.lineEdit().setText(text)
-                    for pubkey, uid in nodes.items():
-                        self.nodes.append({'pubkey': pubkey, 'uid': uid})
-                        self.combobox_search.addItem(uid)
-                    self.blockSignals(False)
-                    self.combobox_search.showPopup()
-            except errors.DuniterError as e:
-                if e.ucode == errors.NO_MATCHING_IDENTITY:
-                    self.nodes = list()
-                    self.blockSignals(True)
-                    self.combobox_search.clear()
-                    self.blockSignals(False)
-                    self.combobox_search.showPopup()
-                else:
-                    pass
-            except NoPeerAvailable:
-                pass
-        self.search_completed.emit()
-
-    def select_node(self, index):
-        """
-        Select node in graph when item is selected in combobox
-        """
-        if index < 0 or index >= len(self.nodes):
-            self._current_identity = None
-            return False
-        node = self.nodes[index]
-        metadata = {'id': node['pubkey'], 'text': node['uid']}
-        self._current_identity = self.app.identities_registry.from_handled_data(
-                metadata['text'],
-                metadata['id'],
-                None,
-                BlockchainState.VALIDATED,
-                self.community
-            )
-        self.identity_selected.emit(
-            self._current_identity
-        )
-
-    def retranslateUi(self, widget):
-        """
-        Retranslate missing widgets from generated code
-        """
-        self.combobox_search.lineEdit().setPlaceholderText(self.tr(SearchUserWidget._search_placeholder))
-        super().retranslateUi(self)
-
-    def keyPressEvent(self, event):
-        if event.key() == Qt.Key_Return:
-            return
-
-        super().keyPressEvent(event)
diff --git a/src/sakia/gui/widgets/toast.py b/src/sakia/gui/widgets/toast.py
index fd50cf8f8edcb900778f0b2f87c776ba1d4cad78..afd7d0aef22b31160b990cc21c4a229b49460b26 100644
--- a/src/sakia/gui/widgets/toast.py
+++ b/src/sakia/gui/widgets/toast.py
@@ -8,7 +8,7 @@ import logging
 from PyQt5.QtCore import Qt, QThread
 from PyQt5.QtWidgets import QMainWindow, QApplication
 from PyQt5.QtGui import QImage, QPixmap
-from ...gen_resources.toast_uic import Ui_Toast
+from .toast_uic import Ui_Toast
 
 window = None   # global
 
diff --git a/res/ui/toast.ui b/src/sakia/gui/widgets/toast.ui
similarity index 100%
rename from res/ui/toast.ui
rename to src/sakia/gui/widgets/toast.ui
diff --git a/src/sakia/main.py b/src/sakia/main.py
index 6e55dbf2bd773098c02a18ba0839864ca7a079b8..859d0edeb727180ef814d853a8eef656070f6251 100755
--- a/src/sakia/main.py
+++ b/src/sakia/main.py
@@ -3,25 +3,19 @@ Created on 1 févr. 2014
 
 @author: inso
 """
-import signal
-import sys
 import asyncio
 import logging
-import os
-import traceback
-
-# To debug missing spec
-import jsonschema
+import signal
+import sys
 import traceback
 
-# To force cx_freeze import
-import PyQt5.QtSvg
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QApplication, QMessageBox
 
 from quamash import QSelectorEventLoop
-from PyQt5.QtWidgets import QApplication, QMessageBox
-from PyQt5.QtCore import Qt
-from sakia.gui.mainwindow import MainWindow
-from sakia.core.app import Application
+from sakia.app import Application
+from sakia.gui.dialogs.connection_cfg.controller import ConnectionConfigController
+from sakia.gui.main_window.controller import MainWindowController
 
 
 def async_exception_handler(loop, context):
@@ -97,15 +91,19 @@ if __name__ == '__main__':
 
     with loop:
         app = Application.startup(sys.argv, sakia, loop)
-        window = MainWindow.startup(app)
+        if not app.connection_exists():
+            conn_controller = ConnectionConfigController.create_connection(None, app)
+            loop.run_until_complete(conn_controller.async_exec())
+            app.instanciate_services()
+            app.start_coroutines()
+        window = MainWindowController.startup(app)
         loop.run_forever()
         try:
             loop.set_exception_handler(None)
-            loop.run_until_complete(app.stop())
+            loop.run_until_complete(app.stop_current_profile())
             logging.debug("Application stopped")
         except asyncio.CancelledError:
             logging.info('CancelledError')
     logging.debug("Exiting")
     sys.exit()
-    logging.debug("Application stopped")
 
diff --git a/src/sakia/models/certifications.py b/src/sakia/models/certifications.py
deleted file mode 100644
index 72bbf69717f6ff0dbdb11eedaebd34660beab5c9..0000000000000000000000000000000000000000
--- a/src/sakia/models/certifications.py
+++ /dev/null
@@ -1,199 +0,0 @@
-"""
-Created on 5 févr. 2014
-
-@author: inso
-"""
-
-import datetime
-import logging
-import asyncio
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \
-    QDateTime, QLocale, QModelIndex
-
-from PyQt5.QtGui import QFont, QColor
-
-
-class CertsFilterProxyModel(QSortFilterProxyModel):
-    def __init__(self, ts_from, ts_to, parent=None):
-        super().__init__(parent)
-        self.app = None
-        self.ts_from = ts_from
-        self.ts_to = ts_to
-
-    @property
-    def account(self):
-        return self.app.current_account
-
-    def set_period(self, ts_from, ts_to):
-        """
-        Filter table by given timestamps
-        """
-        logging.debug("Filtering from {0} to {1}".format(
-            datetime.datetime.fromtimestamp(ts_from).isoformat(' '),
-            datetime.datetime.fromtimestamp(ts_to).isoformat(' '))
-        )
-        self.ts_from = ts_from
-        self.ts_to = ts_to
-        self.modelReset.emit()
-
-    def filterAcceptsRow(self, sourceRow, sourceParent):
-        def in_period(date_ts):
-            return date_ts in range(self.ts_from, self.ts_to)
-
-        source_model = self.sourceModel()
-        date_col = source_model.columns_types.index('date')
-        source_index = source_model.index(sourceRow, date_col)
-        date = source_model.data(source_index, Qt.DisplayRole)
-        return in_period(date)
-
-    @property
-    def community(self):
-        return self.sourceModel().community
-
-    def columnCount(self, parent):
-        return self.sourceModel().columnCount(None) - 5
-
-    def setSourceModel(self, sourceModel):
-        self.app = sourceModel.app
-        super().setSourceModel(sourceModel)
-
-    def lessThan(self, left, right):
-        """
-        Sort table by given column number.
-        """
-        source_model = self.sourceModel()
-        left_data = source_model.data(left, Qt.DisplayRole)
-        right_data = source_model.data(right, Qt.DisplayRole)
-        if left_data == "":
-            return self.sortOrder() == Qt.DescendingOrder
-        elif right_data == "":
-            return self.sortOrder() == Qt.AscendingOrder
-        return (left_data < right_data)
-
-    def data(self, index, role):
-        source_index = self.mapToSource(index)
-        model = self.sourceModel()
-        source_data = model.data(source_index, role)
-        state_col = model.columns_types.index('state')
-        state_index = model.index(source_index.row(), state_col)
-        state_data = model.data(state_index, Qt.DisplayRole)
-        if role == Qt.DisplayRole:
-            if source_index.column() == model.columns_types.index('uid'):
-                return source_data
-            if source_index.column() == model.columns_types.index('date'):
-                return QLocale.toString(
-                    QLocale(),
-                    QDateTime.fromTime_t(source_data).date(),
-                    QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                )
-            if source_index.column() == model.columns_types.index('payment') or \
-                    source_index.column() == model.columns_types.index('deposit'):
-                return source_data
-
-        if role == Qt.FontRole:
-            font = QFont()
-            return font
-
-        if role == Qt.ForegroundRole:
-            if state_data == TransferState.REFUSED:
-                return QColor(Qt.red)
-            elif state_data == TransferState.TO_SEND:
-                return QColor(Qt.blue)
-
-        if role == Qt.TextAlignmentRole:
-            if source_index.column() == self.sourceModel().columns_types.index('date'):
-                return Qt.AlignCenter
-
-        if role == Qt.ToolTipRole:
-            if source_index.column() == self.sourceModel().columns_types.index('date'):
-                return QDateTime.fromTime_t(source_data).toString(Qt.SystemLocaleLongDate)
-            return None
-
-        return source_data
-
-
-class HistoryTableModel(QAbstractTableModel):
-    """
-    A Qt abstract item model to display communities in a tree
-    """
-
-    def __init__(self, app, account, community, parent=None):
-        """
-        Constructor
-        """
-        super().__init__(parent)
-        self.app = app
-        self.account = account
-        self.community = community
-        self.transfers_data = []
-        self.refresh_certs()
-        self._max_confirmations = 0
-
-        self.columns_types = (
-            'date',
-            'uid',
-            'state',
-            'pubkey',
-            'block_number'
-        )
-
-        self.column_headers = (
-            self.tr('Date'),
-            self.tr('UID/Public key'),
-            'State',
-            'Pubkey',
-            'Block Number'
-        )
-
-    def change_account(self, account):
-        cancel_once_task(self, self.refresh_certs)
-        self.account = account
-
-    def change_community(self, community):
-        cancel_once_task(self, self.refresh_certs)
-        self.community = community
-
-    def certifications(self):
-        if self.account:
-            return self.account.certifications(self.community)
-        else:
-            return []
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_certs(self):
-        self.beginResetModel()
-        self.transfers_data = []
-        self.endResetModel()
-
-    def max_confirmations(self):
-        return self._max_confirmations
-
-    def rowCount(self, parent):
-        return len(self.transfers_data)
-
-    def columnCount(self, parent):
-        return len(self.columns_types)
-
-    def headerData(self, section, orientation, role):
-        if self.account and self.community:
-            if role == Qt.DisplayRole:
-                return self.column_headers[section]
-
-    def data(self, index, role):
-        row = index.row()
-        col = index.column()
-
-        if not index.isValid():
-            return QVariant()
-
-        if role == Qt.DisplayRole:
-            return self.transfers_data[row][col]
-
-        if role == Qt.ToolTipRole:
-            return self.transfers_data[row][col]
-
-    def flags(self, index):
-        return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
diff --git a/src/sakia/models/communities.py b/src/sakia/models/communities.py
deleted file mode 100644
index d782ad461a4bcf380af6bbb6dbff24a3f624089d..0000000000000000000000000000000000000000
--- a/src/sakia/models/communities.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""
-Created on 5 févr. 2014
-
-@author: inso
-"""
-
-from PyQt5.QtCore import QAbstractListModel, Qt
-
-
-class CommunitiesListModel(QAbstractListModel):
-
-    """
-    A Qt abstract item model to display communities in a tree
-    """
-
-    def __init__(self, account, parent=None):
-        """
-        Constructor
-        """
-        super(CommunitiesListModel, self).__init__(parent)
-        self.communities = account.communities
-
-    def rowCount(self, parent):
-        return len(self.communities)
-
-    def data(self, index, role):
-
-        if role == Qt.DisplayRole:
-            row = index.row()
-            value = self.communities[row].name
-            return value
-
-    def flags(self, index):
-        return Qt.ItemIsSelectable | Qt.ItemIsEnabled
diff --git a/src/sakia/models/community.py b/src/sakia/models/community.py
deleted file mode 100644
index 16babb17f50b415a760af1d68a148f18bcd8ab4d..0000000000000000000000000000000000000000
--- a/src/sakia/models/community.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""
-Created on 5 fevr. 2014
-
-@author: inso
-"""
-
-
-class CommunityItemModel(object):
-
-    def __init__(self, community, communities_item=None):
-        self.communities_item = communities_item
-        self.community_text = community.name
-        self.main_node_items = []
-
-    def appendChild(self, item):
-        self.main_node_items.append(item)
-
-    def child(self, row):
-        return self.main_node_items[row]
-
-    def childCount(self):
-        return len(self.main_node_items)
-
-    def columnCount(self):
-        return 1
-
-    def data(self, column):
-        try:
-            return self.community_text
-        except IndexError:
-            return None
-
-    def parent(self):
-        return self.communities_item
-
-    def row(self):
-        if self.communities_item:
-            return self.communities_item.index(self)
-
-        return 0
diff --git a/src/sakia/models/generic_tree.py b/src/sakia/models/generic_tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b7eb55b785dfe8df03783a75ed3e97278cae3c5
--- /dev/null
+++ b/src/sakia/models/generic_tree.py
@@ -0,0 +1,172 @@
+"""
+Created on 5 févr. 2014
+
+@author: inso
+"""
+
+from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
+from PyQt5.QtGui import QIcon
+import logging
+
+
+def parse_node(node_data, parent_item):
+    node = NodeItem(node_data, parent_item)
+    if parent_item:
+        parent_item.appendChild(node)
+    if 'children' in node_data:
+        for child in node_data['children']:
+            parse_node(child, node)
+    return node
+
+
+class NodeItem(QAbstractItemModel):
+    def __init__(self, node, parent_item):
+        super().__init__(parent_item)
+        self.children = []
+        self.node = node
+        self.parent_item = parent_item
+
+    def appendChild(self, node_item):
+        self.children.append(node_item)
+
+    def child(self, row):
+        return self.children[row]
+
+    def childCount(self):
+        return len(self.children)
+
+    def columnCount(self):
+        return 1
+
+    def data(self, index, role):
+        if role == Qt.DisplayRole and 'title' in self.node:
+            return self.node['title']
+
+        if role == Qt.ToolTipRole and 'tooltip' in self.node:
+            return self.node['tooltip']
+
+        if role == Qt.DecorationRole and 'icon' in self.node:
+            return QIcon(self.node['icon'])
+
+        if role == GenericTreeModel.ROLE_RAW_DATA:
+            return self.node
+
+    def row(self):
+        if self.parent_item:
+            return self.parent_item.row() + self.parent_item.children.index(self)
+        return 0
+
+    def column(self):
+        return 0
+
+
+class GenericTreeModel(QAbstractItemModel):
+
+    """
+    A Qt abstract item model to display nodes from a dict
+
+
+    dict_format = {
+        'root_node': {
+            'node': ["title", "icon", "tooltip", "action"],
+            'children': {}
+        }
+    }
+
+    """
+
+    ROLE_RAW_DATA = 101
+
+    def __init__(self, title, root_item):
+        """
+        Constructor
+        """
+        super().__init__(None)
+        self.title = title
+        self.root_item = root_item
+
+    @classmethod
+    def create(cls, title, data):
+        root_item = NodeItem({}, None)
+        for node in data:
+            parse_node(node, root_item)
+
+        return cls(title, root_item)
+
+    def columnCount(self, parent):
+        return 1
+
+    def data(self, index, role):
+        if not index.isValid():
+            return None
+
+        item = index.internalPointer()
+
+        if role in (Qt.DisplayRole,
+                    Qt.DecorationRole,
+                    Qt.ToolTipRole,
+                    GenericTreeModel.ROLE_RAW_DATA) \
+            and index.column() == 0:
+            return item.data(0, role)
+
+        return None
+
+    def flags(self, index):
+        if not index.isValid():
+            return Qt.NoItemFlags
+
+        if index.column() == 0:
+            return Qt.ItemIsEnabled | Qt.ItemIsSelectable
+
+    def headerData(self, section, orientation, role):
+        if orientation == Qt.Horizontal \
+        and role == Qt.DisplayRole and section == 0:
+            return self.title
+        return None
+
+    def index(self, row, column, parent):
+        if not self.hasIndex(row, column, parent):
+            return QModelIndex()
+
+        if not parent.isValid():
+            parent_item = self.root_item
+        else:
+            parent_item = parent.internalPointer()
+
+        child_item = parent_item.child(row)
+        if child_item:
+            return self.createIndex(row, column, child_item)
+        else:
+            return QModelIndex()
+
+    def parent(self, index):
+        if not index.isValid():
+            return QModelIndex()
+
+        child_item = index.internalPointer()
+        parent_item = child_item.parent()
+
+        if parent_item == self.root_item:
+            return QModelIndex()
+
+        return self.createIndex(parent_item.row(), 0, parent_item)
+
+    def rowCount(self, parent_index):
+        if not parent_index.isValid():
+            parent_item = self.root_item
+        else:
+            parent_item = parent_index.internalPointer()
+
+        if parent_index.column() > 0:
+            return 0
+
+        return parent_item.childCount()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if index.column() == 0:
+            return True
+
+    def insert_node(self, raw_data):
+        self.beginInsertRows(QModelIndex(), self.rowCount(QModelIndex()), 0)
+        parse_node(raw_data, self.root_item)
+        self.endInsertRows()
diff --git a/src/sakia/models/peering.py b/src/sakia/models/peering.py
deleted file mode 100644
index 4b2e48c2b4a5a4c2f2041d5eb9836a81751d6b56..0000000000000000000000000000000000000000
--- a/src/sakia/models/peering.py
+++ /dev/null
@@ -1,173 +0,0 @@
-"""
-Created on 5 févr. 2014
-
-@author: inso
-"""
-
-from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
-import logging
-
-
-class RootItem(object):
-
-    def __init__(self, name):
-        self.name = name
-        self.node_items = []
-
-    def appendChild(self, item):
-        self.node_items.append(item)
-
-    def child(self, row):
-        return self.node_items[row]
-
-    def childCount(self):
-        return len(self.node_items)
-
-    def columnCount(self):
-        return 1
-
-    def data(self, column):
-        try:
-            return self.name
-        except IndexError:
-            return None
-
-    def parent(self):
-        return None
-
-    def row(self):
-        return 0
-
-
-class NodeItem(object):
-
-    def __init__(self, node, root_item):
-        e = node.endpoint
-        if e.server:
-            self.address = "{0}:{1}".format(e.server, e.port)
-        elif e.ipv4:
-            self.address = "{0}:{1}".format(e.ipv4, e.port)
-        elif e.ipv6:
-            self.address = "{0}:{1}".format(e.ipv6, e.port)
-        else:
-            self.address = "{0}".format(node.pubkey)
-
-        self.root_item = root_item
-        self.node_items = []
-
-    def appendChild(self, node_item):
-        self.node_items.append(node_item)
-
-    def child(self, row):
-        return self.node_items[row]
-
-    def childCount(self):
-        return len(self.node_items)
-
-    def columnCount(self):
-        return 1
-
-    def data(self, column):
-        try:
-            return self.address
-        except IndexError:
-            return None
-
-    def parent(self):
-        return self.root_item
-
-    def row(self):
-        if self.root_item:
-            return self.root_item.node_items.index(self)
-        return 0
-
-
-class PeeringTreeModel(QAbstractItemModel):
-
-    """
-    A Qt abstract item model to display nodes of a community
-    """
-
-    def __init__(self, community):
-        """
-        Constructor
-        """
-        super().__init__(None)
-        self.nodes = community._network.root_nodes
-        self.root_item = RootItem(community.currency)
-        self.refresh_tree()
-
-    def columnCount(self, parent):
-        return 1
-
-    def data(self, index, role):
-        if not index.isValid():
-            return None
-
-        item = index.internalPointer()
-
-        if role == Qt.DisplayRole and index.column() == 0:
-            return item.data(0)
-
-        return None
-
-    def flags(self, index):
-        if not index.isValid():
-            return Qt.NoItemFlags
-
-        if index.column() == 0:
-            return Qt.ItemIsEnabled | Qt.ItemIsSelectable
-
-    def headerData(self, section, orientation, role):
-        if orientation == Qt.Horizontal \
-        and role == Qt.DisplayRole and section == 0:
-            return self.root_item.data(0) + " nodes"
-        return None
-
-    def index(self, row, column, parent):
-        if not self.hasIndex(row, column, parent):
-            return QModelIndex()
-
-        if not parent.isValid():
-            parent_item = self.root_item
-        else:
-            parent_item = parent.internalPointer()
-
-        child_item = parent_item.child(row)
-        if child_item:
-            return self.createIndex(row, column, child_item)
-        else:
-            return QModelIndex()
-
-    def parent(self, index):
-        if not index.isValid():
-            return QModelIndex()
-
-        child_item = index.internalPointer()
-        parent_item = child_item.parent()
-
-        if parent_item == self.root_item:
-            return QModelIndex()
-
-        return self.createIndex(parent_item.row(), 0, parent_item)
-
-    def rowCount(self, parent):
-        if parent.column() > 0:
-            return 0
-
-        if not parent.isValid():
-            parent_item = self.root_item
-        else:
-            parent_item = parent.internalPointer()
-
-        return parent_item.childCount()
-
-    def setData(self, index, value, role=Qt.EditRole):
-        if index.column() == 0:
-            return True
-
-    def refresh_tree(self):
-        logging.debug("root : " + self.root_item.data(0))
-        for node in self.nodes:
-            node_item = NodeItem(node, self.root_item)
-            self.root_item.appendChild(node_item)
diff --git a/src/sakia/models/txhistory.py b/src/sakia/models/txhistory.py
deleted file mode 100644
index 5ea783e80ea738d7c5069a6faefd41b0b63ff15b..0000000000000000000000000000000000000000
--- a/src/sakia/models/txhistory.py
+++ /dev/null
@@ -1,386 +0,0 @@
-"""
-Created on 5 févr. 2014
-
-@author: inso
-"""
-
-import datetime
-import logging
-import asyncio
-import math
-from ..core.transfer import Transfer, TransferState
-from ..core.net.network import MAX_CONFIRMATIONS
-from ..tools.exceptions import NoPeerAvailable
-from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \
-    QDateTime, QLocale, QModelIndex
-
-from PyQt5.QtGui import QFont, QColor, QIcon
-
-
-class TxFilterProxyModel(QSortFilterProxyModel):
-    def __init__(self, ts_from, ts_to, parent=None):
-        super().__init__(parent)
-        self.app = None
-        self.ts_from = ts_from
-        self.ts_to = ts_to
-        self.payments = 0
-        self.deposits = 0
-
-    @property
-    def account(self):
-        return self.app.current_account
-
-    def set_period(self, ts_from, ts_to):
-        """
-        Filter table by given timestamps
-        """
-        logging.debug("Filtering from {0} to {1}".format(
-            datetime.datetime.fromtimestamp(ts_from).isoformat(' '),
-            datetime.datetime.fromtimestamp(ts_to).isoformat(' '))
-        )
-        self.ts_from = ts_from
-        self.ts_to = ts_to
-        self.modelReset.emit()
-
-    def filterAcceptsRow(self, sourceRow, sourceParent):
-        def in_period(date_ts):
-            return date_ts in range(self.ts_from, self.ts_to)
-
-        source_model = self.sourceModel()
-        date_col = source_model.columns_types.index('date')
-        source_index = source_model.index(sourceRow, date_col)
-        date = source_model.data(source_index, Qt.DisplayRole)
-        if in_period(date):
-            # calculate sum total payments
-            payment = source_model.data(
-                source_model.index(sourceRow, source_model.columns_types.index('amount')),
-                Qt.DisplayRole
-            )
-            if payment:
-                self.payments += int(payment)
-            # calculate sum total deposits
-            deposit = source_model.data(
-                source_model.index(sourceRow, source_model.columns_types.index('amount')),
-                Qt.DisplayRole
-            )
-            if deposit:
-                self.deposits += int(deposit)
-
-        return in_period(date)
-
-    @property
-    def community(self):
-        return self.sourceModel().community
-
-    def columnCount(self, parent):
-        return self.sourceModel().columnCount(None) - 5
-
-    def setSourceModel(self, sourceModel):
-        self.app = sourceModel.app
-        super().setSourceModel(sourceModel)
-
-    def lessThan(self, left, right):
-        """
-        Sort table by given column number.
-        """
-        source_model = self.sourceModel()
-        left_data = source_model.data(left, Qt.DisplayRole)
-        right_data = source_model.data(right, Qt.DisplayRole)
-        if left_data == "":
-            return self.sortOrder() == Qt.DescendingOrder
-        elif right_data == "":
-            return self.sortOrder() == Qt.AscendingOrder
-        if left_data == right_data:
-            txid_col = source_model.columns_types.index('txid')
-            txid_left = source_model.index(left.row(), txid_col)
-            txid_right = source_model.index(right.row(), txid_col)
-            return (txid_left < txid_right)
-
-        return (left_data < right_data)
-
-    def data(self, index, role):
-        source_index = self.mapToSource(index)
-        model = self.sourceModel()
-        source_data = model.data(source_index, role)
-        state_col = model.columns_types.index('state')
-        state_index = model.index(source_index.row(), state_col)
-        state_data = model.data(state_index, Qt.DisplayRole)
-        if role == Qt.DisplayRole:
-            if source_index.column() == model.columns_types.index('uid'):
-                return source_data
-            if source_index.column() == model.columns_types.index('date'):
-                return QLocale.toString(
-                    QLocale(),
-                    QDateTime.fromTime_t(source_data).date(),
-                    QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                )
-            if source_index.column() == model.columns_types.index('payment') or \
-                    source_index.column() == model.columns_types.index('deposit'):
-                return source_data
-
-        if role == Qt.FontRole:
-            font = QFont()
-            if state_data == TransferState.AWAITING or state_data == TransferState.VALIDATING:
-                font.setItalic(True)
-            elif state_data == TransferState.REFUSED:
-                font.setItalic(True)
-            elif state_data == TransferState.TO_SEND:
-                font.setBold(True)
-            else:
-                font.setItalic(False)
-            return font
-
-        if role == Qt.ForegroundRole:
-            if state_data == TransferState.REFUSED:
-                return QColor(Qt.red)
-            elif state_data == TransferState.TO_SEND:
-                return QColor(Qt.blue)
-
-        if role == Qt.TextAlignmentRole:
-            if source_index.column() == self.sourceModel().columns_types.index(
-                    'deposit') or source_index.column() == self.sourceModel().columns_types.index('payment'):
-                return Qt.AlignRight | Qt.AlignVCenter
-            if source_index.column() == self.sourceModel().columns_types.index('date'):
-                return Qt.AlignCenter
-
-        if role == Qt.ToolTipRole:
-            if source_index.column() == self.sourceModel().columns_types.index('date'):
-                return QDateTime.fromTime_t(source_data).toString(Qt.SystemLocaleLongDate)
-
-            if state_data == TransferState.VALIDATING or state_data == TransferState.AWAITING:
-                block_col = model.columns_types.index('block_number')
-                block_index = model.index(source_index.row(), block_col)
-                block_data = model.data(block_index, Qt.DisplayRole)
-
-                current_confirmations = 0
-                if state_data == TransferState.VALIDATING:
-                    current_blockUID_number = self.community.network.current_blockUID.number
-                    if current_blockUID_number:
-                        current_confirmations = current_blockUID_number - block_data
-                elif state_data == TransferState.AWAITING:
-                    current_confirmations = 0
-
-                max_confirmations = self.sourceModel().max_confirmations()
-
-                if self.app.preferences['expert_mode']:
-                    return self.tr("{0} / {1} confirmations").format(current_confirmations, max_confirmations)
-                else:
-                    confirmation = current_confirmations / max_confirmations * 100
-                    confirmation = 100 if confirmation > 100 else confirmation
-                    return self.tr("Confirming... {0} %").format(QLocale().toString(float(confirmation), 'f', 0))
-
-            return None
-        return source_data
-
-
-class HistoryTableModel(QAbstractTableModel):
-    """
-    A Qt abstract item model to display communities in a tree
-    """
-
-    def __init__(self, app, account, community, parent=None):
-        """
-        Constructor
-        """
-        super().__init__(parent)
-        self.app = app
-        self.account = account
-        self.community = community
-        self.transfers_data = []
-        self.refresh_transfers()
-
-        self.columns_types = (
-            'date',
-            'uid',
-            'payment',
-            'deposit',
-            'comment',
-            'state',
-            'txid',
-            'pubkey',
-            'block_number',
-            'amount'
-        )
-
-        self.column_headers = (
-            lambda: self.tr('Date'),
-            lambda: self.tr('UID/Public key'),
-            lambda: self.tr('Payment'),
-            lambda: self.tr('Deposit'),
-            lambda: self.tr('Comment'),
-            lambda: 'State',
-            lambda: 'TXID',
-            lambda: 'Pubkey',
-            lambda: 'Block Number'
-        )
-
-    def change_account(self, account):
-        cancel_once_task(self, self.refresh_transfers)
-        self.account = account
-
-    def change_community(self, community):
-        cancel_once_task(self, self.refresh_transfers)
-        self.community = community
-
-    def transfers(self):
-        if self.account:
-            return self.account.transfers(self.community) + self.account.dividends(self.community)
-        else:
-            return []
-
-    async def data_received(self, transfer):
-        amount = transfer.metadata['amount']
-        if transfer.blockUID:
-            block_number = transfer.blockUID.number
-        else:
-            block_number = None
-        try:
-            deposit = await self.account.current_ref.instance(transfer.metadata['amount'], self.community,
-                                                     self.app, block_number)\
-                .diff_localized(international_system=self.app.preferences['international_system_of_units'])
-        except NoPeerAvailable:
-            deposit = "Could not compute"
-        comment = ""
-        if transfer.metadata['comment'] != "":
-            comment = transfer.metadata['comment']
-        if transfer.metadata['issuer_uid'] != "":
-            sender = transfer.metadata['issuer_uid']
-        else:
-            sender = "pub:{0}".format(transfer.metadata['issuer'][:5])
-
-        date_ts = transfer.metadata['time']
-        txid = transfer.metadata['txid']
-
-        return (date_ts, sender, "", deposit,
-                comment, transfer.state, txid,
-                transfer.metadata['issuer'], block_number, amount)
-
-    async def data_sent(self, transfer):
-        if transfer.blockUID:
-            block_number = transfer.blockUID.number
-        else:
-            block_number = None
-
-        amount = transfer.metadata['amount']
-        try:
-            paiment = await self.account.current_ref.instance(transfer.metadata['amount'], self.community,
-                                                     self.app, block_number)\
-                .diff_localized(international_system=self.app.preferences['international_system_of_units'])
-        except NoPeerAvailable:
-            paiment = "Could not compute"
-        comment = ""
-        if transfer.metadata['comment'] != "":
-            comment = transfer.metadata['comment']
-        if transfer.metadata['receiver_uid'] != "":
-            receiver = transfer.metadata['receiver_uid']
-        else:
-            receiver = "pub:{0}".format(transfer.metadata['receiver'][:5])
-
-        date_ts = transfer.metadata['time']
-        txid = transfer.metadata['txid']
-        return (date_ts, receiver, paiment,
-                "", comment, transfer.state, txid,
-                transfer.metadata['receiver'], block_number, amount)
-
-    async def data_dividend(self, dividend):
-        amount = dividend['amount'] * math.pow(10, dividend['base'])
-        try:
-            deposit = await self.account.current_ref.instance(amount, self.community, self.app, dividend['block_number'])\
-                .diff_localized(international_system=self.app.preferences['international_system_of_units'])
-        except NoPeerAvailable:
-            deposit = "Could not compute"
-        comment = ""
-        receiver = self.account.name
-        date_ts = dividend['time']
-        id = dividend['id']
-        block_number = dividend['block_number']
-        state = dividend['state']
-
-        return (date_ts, receiver, "",
-                deposit, "", state, id,
-                self.account.pubkey, block_number, amount)
-
-    @once_at_a_time
-    @asyncify
-    async def refresh_transfers(self):
-        self.beginResetModel()
-        self.transfers_data = []
-        self.endResetModel()
-        self.beginResetModel()
-        transfers_data = []
-        if self.community:
-            requests_coro = []
-            data_list = []
-            count = 0
-            transfers = self.transfers()
-            for transfer in transfers:
-                coro = None
-                count += 1
-                if type(transfer) is Transfer:
-                    if transfer.metadata['issuer'] == self.account.pubkey:
-                        coro = asyncio.ensure_future(self.data_sent(transfer))
-                    else:
-                        coro = asyncio.ensure_future(self.data_received(transfer))
-                elif type(transfer) is dict:
-                    coro = asyncio.ensure_future(self.data_dividend(transfer))
-                if coro:
-                    requests_coro.append(coro)
-                if count % 25 == 0:
-                    gathered_list = await asyncio.gather(*requests_coro)
-                    requests_coro = []
-                    data_list.extend(gathered_list)
-            # One last gathering
-            gathered_list = await asyncio.gather(*requests_coro)
-            data_list.extend(gathered_list)
-
-            for data in data_list:
-                transfers_data.append(data)
-        self.transfers_data = transfers_data
-        self.endResetModel()
-
-    def max_confirmations(self):
-        return MAX_CONFIRMATIONS
-
-    def rowCount(self, parent):
-        return len(self.transfers_data)
-
-    def columnCount(self, parent):
-        return len(self.columns_types)
-
-    def headerData(self, section, orientation, role):
-        if self.account and self.community:
-            if role == Qt.DisplayRole:
-                if self.columns_types[section] == 'payment' or self.columns_types[section] == 'deposit':
-                    return '{:}\n({:})'.format(
-                        self.column_headers[section](),
-                        self.account.current_ref.instance(0, self.community, self.app, None).diff_units
-                    )
-
-                return self.column_headers[section]()
-
-    def data(self, index, role):
-        row = index.row()
-        col = index.column()
-
-        if not index.isValid():
-            return QVariant()
-
-        if role == Qt.DisplayRole:
-            return self.transfers_data[row][col]
-
-        if role == Qt.ToolTipRole:
-            return self.transfers_data[row][col]
-
-        if role == Qt.DecorationRole and index.column() == 0:
-            transfer = self.transfers_data[row]
-            if transfer[self.columns_types.index('payment')] != "":
-                return QIcon(":/icons/sent")
-            elif transfer[self.columns_types.index('uid')] == self.account.name:
-                return QIcon(":/icons/dividend")
-            else:
-                return QIcon(":/icons/received")
-
-    def flags(self, index):
-        return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
diff --git a/src/sakia/models/wallets.py b/src/sakia/models/wallets.py
deleted file mode 100644
index 5eac7066bd402bed1ff1bbe478710249584b9220..0000000000000000000000000000000000000000
--- a/src/sakia/models/wallets.py
+++ /dev/null
@@ -1,147 +0,0 @@
-"""
-Created on 8 févr. 2014
-
-@author: inso
-"""
-import asyncio
-from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt, QLocale, pyqtSlot
-
-
-class WalletsFilterProxyModel(QSortFilterProxyModel):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-        self.community = None
-        self.app = None
-
-    @property
-    def account(self):
-        return self.app.current_account
-
-    def columnCount(self, parent):
-        return self.sourceModel().columnCount(None)
-
-    def setSourceModel(self, source_model):
-        self.community = source_model.community
-        self.app = source_model.app
-        super().setSourceModel(source_model)
-
-    def lessThan(self, left, right):
-        """
-        Sort table by given column number.
-        """
-        left_data = self.sourceModel().data(left, Qt.DisplayRole)
-        right_data = self.sourceModel().data(right, Qt.DisplayRole)
-        return left_data < right_data
-
-    def data(self, index, role):
-        source_index = self.mapToSource(index)
-        source_data = self.sourceModel().data(source_index, role)
-        if role == Qt.DisplayRole:
-            if source_index.column() == self.sourceModel().columns_types.index('pubkey'):
-                pubkey = source_data
-                source_data = pubkey
-                return source_data
-            if source_index.column() == self.sourceModel().columns_types.index('amount'):
-                return self.account.current_ref.instance(source_data, self.community, self.app).localized()
-        if role == Qt.TextAlignmentRole:
-            if source_index.column() == self.sourceModel().columns_types.index('amount'):
-                return Qt.AlignRight | Qt.AlignVCenter
-
-        return source_data
-
-
-class WalletsTableModel(QAbstractTableModel):
-    """
-    A Qt list model to display wallets and edit their names
-    """
-
-    def __init__(self, app, community, parent=None):
-        """
-
-        :param list of sakia.core.wallet.Wallet wallets: The list of wallets to display
-        :param sakia.core.community.Community community: The community to show
-        :param PyQt5.QtCore.QObject parent: The parent widget
-        :return: The model
-        :rtype: WalletsTableModel
-        """
-        super().__init__(parent)
-        self.app = app
-        self.account.wallets_changed.connect(self.refresh_account_wallets)
-
-        self.community = community
-        self.columns_headers = (self.tr('Name'),
-                                self.tr('Amount'),
-                                self.tr('Pubkey'))
-        self.columns_types = ('name', 'amount', 'pubkey')
-
-    @property
-    def account(self):
-        return self.app.current_account
-
-    @property
-    def wallets(self):
-        return self.account.wallets
-
-    @pyqtSlot()
-    def refresh_account_wallets(self):
-        """
-        Change the current wallets, reconnect the slots
-        """
-        self.beginResetModel()
-        for w in self.account.wallets:
-            w.inner_data_changed.connect(lambda: self.refresh_wallet(w))
-        self.endResetModel()
-
-    def refresh_wallet(self, wallet):
-        """
-        Refresh the specified wallet value
-        :param sakia.core.wallet.Wallet wallet: The wallet to refresh
-        """
-        index = self.account.wallets.index(wallet)
-        if index > 0:
-            self.dataChanged.emit(index, index)
-
-    def rowCount(self, parent):
-        return len(self.wallets)
-
-    def columnCount(self, parent):
-        return len(self.columns_types)
-
-    def headerData(self, section, orientation, role):
-        if role == Qt.DisplayRole:
-            if self.columns_types[section] == 'amount':
-                return '{:}\n({:})'.format(
-                    self.columns_headers[section],
-                    self.account.current_ref.instance(0, self.community, self.app, None).units
-                )
-            return self.columns_headers[section]
-
-    def wallet_data(self, row):
-        name = self.wallets[row].name
-        amount = self.wallets[row].value(self.community)
-        pubkey = self.wallets[row].pubkey
-        return name, amount, pubkey
-
-    def data(self, index, role):
-        row = index.row()
-        col = index.column()
-        if role == Qt.DisplayRole:
-            return self.wallet_data(row)[col]
-
-    def setData(self, index, value, role):
-        if role == Qt.EditRole:
-            row = index.row()
-            col = index.column()
-            # Change model only if value not empty...
-            if col == self.columns_types.index('name') and value:
-                self.wallets[row].name = value
-                self.dataChanged.emit(index, index)
-                return True
-        return False
-
-    def flags(self, index):
-        default_flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled
-        #  Only name column is editable
-        if index.column() == 0:
-            return default_flags | Qt.ItemIsEditable
-        return default_flags
diff --git a/src/sakia/core/money/__init__.py b/src/sakia/money/__init__.py
similarity index 76%
rename from src/sakia/core/money/__init__.py
rename to src/sakia/money/__init__.py
index dda0375b6aecb776c1534da5f4fff6a09546ac1b..05a4b506a6ff4f03c503992f6cae11774a6a4ab1 100644
--- a/src/sakia/core/money/__init__.py
+++ b/src/sakia/money/__init__.py
@@ -2,6 +2,5 @@ from .quantitative import Quantitative
 from .relative import Relative
 from .quant_zerosum import QuantitativeZSum
 from .relative_zerosum import RelativeZSum
-from .dividend_per_day import DividendPerDay
 
-Referentials = (Quantitative, Relative, QuantitativeZSum, RelativeZSum, DividendPerDay)
+Referentials = (Quantitative, Relative, QuantitativeZSum, RelativeZSum)
diff --git a/src/sakia/money/base_referential.py b/src/sakia/money/base_referential.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dabb7cc66c2ab304399a0cf0f57791172d02ad8
--- /dev/null
+++ b/src/sakia/money/base_referential.py
@@ -0,0 +1,54 @@
+
+
+class BaseReferential:
+    """
+    Interface to all referentials
+    """
+    def __init__(self, amount, currency, app, block_number=None):
+        """
+
+        :param int amount:
+        :param str currency:
+        :param sakia.app.Application app:
+        :param int block_number:
+        """
+        self.amount = amount
+        self.app = app
+        self.currency = currency
+        self._block_number = block_number
+
+    @classmethod
+    def instance(cls, amount, currency, app, block_number=None):
+        return cls(amount, currency, app, block_number)
+
+    @classmethod
+    def translated_name(self):
+        raise NotImplementedError()
+
+    @property
+    def units(self):
+        raise NotImplementedError()
+
+    @property
+    def diff_units(self):
+        raise NotImplementedError()
+
+    def value(self):
+        raise NotImplementedError()
+
+    def differential(self):
+        raise NotImplementedError()
+
+    @staticmethod
+    def to_si(value, base):
+        raise NotImplementedError()
+
+    @staticmethod
+    def base_str(base):
+        raise NotImplementedError()
+
+    def localized(self, units=False, show_base=False):
+        raise NotImplementedError()
+
+    def diff_localized(self, units=False, show_base=False):
+        raise NotImplementedError()
diff --git a/src/sakia/money/currency.py b/src/sakia/money/currency.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d1e335666583941cfb26539ed4bfd982f1cc599
--- /dev/null
+++ b/src/sakia/money/currency.py
@@ -0,0 +1,28 @@
+import re
+
+
+def shortened(currency):
+    """
+    Format the currency name to a short one
+
+    :return: The currency name in a shot format.
+    """
+    words = re.split('[_\W]+', currency)
+    if len(words) > 1:
+        short = ''.join([w[0] for w in words])
+    else:
+        vowels = ('a', 'e', 'i', 'o', 'u', 'y')
+        short = currency
+        short = ''.join([c for c in short if c not in vowels])
+    return short.upper()
+
+
+def symbol(currency):
+    """
+    Format the currency name to a symbol one.
+
+    :return: The currency name as a utf-8 circled symbol.
+    """
+    letter = currency[0]
+    u = ord('\u24B6') + ord(letter) - ord('A')
+    return chr(u)
\ No newline at end of file
diff --git a/src/sakia/core/money/quant_zerosum.py b/src/sakia/money/quant_zerosum.py
similarity index 65%
rename from src/sakia/core/money/quant_zerosum.py
rename to src/sakia/money/quant_zerosum.py
index 767257c85d1142fb3d510e881d06b8de897150ba..b5ec19f6a914988f5339a9ba934e4d1bff720029 100644
--- a/src/sakia/core/money/quant_zerosum.py
+++ b/src/sakia/money/quant_zerosum.py
@@ -1,11 +1,13 @@
 from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, QLocale
 from . import Quantitative
 from .base_referential import BaseReferential
+from .currency import shortened
+from ..data.processors import BlockchainProcessor
 
 
 class QuantitativeZSum(BaseReferential):
     _NAME_STR_ = QT_TRANSLATE_NOOP('QuantitativeZSum', 'Quant Z-sum')
-    _REF_STR_ = QT_TRANSLATE_NOOP('QuantitativeZSum', "{0} {1}Q0 {2}")
+    _REF_STR_ = QT_TRANSLATE_NOOP('QuantitativeZSum', "{0} {1}Q0{2}")
     _UNITS_STR_ = QT_TRANSLATE_NOOP('QuantitativeZSum', "Q0 {0}")
     _FORMULA_STR_ = QT_TRANSLATE_NOOP('QuantitativeZSum',
                                       """Z0 = Q - ( M(t-1) / N(t) )
@@ -26,8 +28,9 @@ class QuantitativeZSum(BaseReferential):
                                             the value is under the average value.
                                            """.replace('\n', '<br >'))
 
-    def __init__(self, amount, community, app, block_number=None):
-        super().__init__(amount, community, app, block_number)
+    def __init__(self, amount, currency, app, block_number=None):
+        super().__init__(amount, currency, app, block_number)
+        self._blockchain_processor = BlockchainProcessor.instanciate(self.app)
 
     @classmethod
     def translated_name(cls):
@@ -35,7 +38,7 @@ class QuantitativeZSum(BaseReferential):
 
     @property
     def units(self):
-        return QCoreApplication.translate("QuantitativeZSum", QuantitativeZSum._UNITS_STR_).format(self.community.short_currency)
+        return QCoreApplication.translate("QuantitativeZSum", QuantitativeZSum._UNITS_STR_).format(shortened(self.currency))
 
     @property
     def formula(self):
@@ -47,9 +50,9 @@ class QuantitativeZSum(BaseReferential):
 
     @property
     def diff_units(self):
-        return QCoreApplication.translate("Quantitative", Quantitative._UNITS_STR_).format(self.community.short_currency)
+        return QCoreApplication.translate("Quantitative", Quantitative._UNITS_STR_).format(shortened(self.currency))
 
-    async def value(self):
+    def value(self):
         """
         Return quantitative value of amount minus the average value
 
@@ -66,35 +69,45 @@ class QuantitativeZSum(BaseReferential):
         :param sakia.core.community.Community community: Community instance
         :return: int
         """
-        ud_block = await self.community.get_ud_block()
-        if ud_block and ud_block['membersCount'] > 0:
-            monetary_mass = await self.community.monetary_mass()
-            average = int(monetary_mass / ud_block['membersCount'])
+        last_members_count = self._blockchain_processor.last_members_count(self.currency)
+        monetary_mass = self._blockchain_processor.current_mass(self.currency)
+        if last_members_count != 0:
+            average = int(monetary_mass / last_members_count)
         else:
             average = 0
-        return self.amount - average
+        return (self.amount - average)/100
 
-    async def differential(self):
-        return await Quantitative(self.amount, self.community, self.app).value()
+    @staticmethod
+    def base_str(base):
+        return Quantitative.base_str(base)
 
-    async def localized(self, units=False, international_system=False):
-        value = await self.value()
+    @staticmethod
+    def to_si(value, base):
+        return Quantitative.to_si(value, base)
+
+    def differential(self):
+        return Quantitative(self.amount, self.currency, self.app).value()
+
+    def localized(self, units=False, show_base=False):
+        value = self.value()
+        dividend, base = self._blockchain_processor.last_ud(self.currency)
 
         prefix = ""
-        if international_system:
-            localized_value, prefix = Quantitative.to_si(value, self.app.preferences['digits_after_comma'])
+        if show_base:
+            localized_value = QuantitativeZSum.to_si(value, base)
+            prefix = QuantitativeZSum.base_str(base)
         else:
-            localized_value = QLocale().toString(float(value), 'f', 0)
+            localized_value = QLocale().toString(float(value), 'f', 2)
 
-        if units or international_system:
+        if units or show_base:
             return QCoreApplication.translate("QuantitativeZSum",
                                               QuantitativeZSum._REF_STR_) \
-                .format(localized_value,
-                        prefix,
-                        self.community.short_currency if units else "")
+                    .format(localized_value,
+                            prefix + (" " if prefix else ""),
+                            (" " if units else "") + (shortened(self.currency) if units else ""))
         else:
             return localized_value
 
-    async def diff_localized(self, units=False, international_system=False):
-        localized = await Quantitative(self.amount, self.community, self.app).localized(units, international_system)
+    def diff_localized(self, units=False, show_base=False):
+        localized = Quantitative(self.amount, self.currency, self.app).localized(units, show_base)
         return localized
diff --git a/src/sakia/core/money/quantitative.py b/src/sakia/money/quantitative.py
similarity index 57%
rename from src/sakia/core/money/quantitative.py
rename to src/sakia/money/quantitative.py
index aa4808e39ce6108a770681ba55d1984246800645..1f4c06dafa456d362dff65af2883c19a8e3e8bd5 100644
--- a/src/sakia/core/money/quantitative.py
+++ b/src/sakia/money/quantitative.py
@@ -1,5 +1,7 @@
 from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, QLocale
 from .base_referential import BaseReferential
+from .currency import shortened
+from ..data.processors import BlockchainProcessor
 
 
 class Quantitative(BaseReferential):
@@ -16,8 +18,9 @@ class Quantitative(BaseReferential):
                                       )
     _DESCRIPTION_STR_ = QT_TRANSLATE_NOOP('Quantitative', "Base referential of the money. Units values are used here.")
 
-    def __init__(self, amount, community, app, block_number=None):
-        super().__init__(amount, community, app, block_number)
+    def __init__(self, amount, currency, app, block_number=None):
+        super().__init__(amount, currency, app, block_number)
+        self._blockchain_processor = BlockchainProcessor.instanciate(self.app)
 
     @classmethod
     def translated_name(cls):
@@ -25,7 +28,7 @@ class Quantitative(BaseReferential):
 
     @property
     def units(self):
-        return QCoreApplication.translate("Quantitative", Quantitative._UNITS_STR_).format(self.community.short_currency)
+        return QCoreApplication.translate("Quantitative", Quantitative._UNITS_STR_).format(shortened(self.currency))
 
     @property
     def formula(self):
@@ -39,7 +42,7 @@ class Quantitative(BaseReferential):
     def diff_units(self):
         return self.units
 
-    async def value(self):
+    def value(self):
         """
         Return quantitative value of amount
 
@@ -47,14 +50,29 @@ class Quantitative(BaseReferential):
         :param sakia.core.community.Community community: Community instance
         :return: int
         """
-        return int(self.amount)
+        return int(self.amount) / 100
 
-    async def differential(self):
-        return await self.value()
+    def differential(self):
+        return self.value()
 
     @staticmethod
-    def to_si(value, digits):
-        prefixes = ['', 'k', 'M', 'G', 'Tera', 'Peta', 'Exa', 'Zeta', 'Yotta']
+    def base_str(base):
+        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
+
+        if base > 1:
+            return "x10" + "".join([chr(unicodes[e]) for e in str(base)])
+        else:
+            return ""
+
+    @staticmethod
+    def to_si(value, base):
         if value < 0:
             value = -value
             multiplier = -1
@@ -62,51 +80,49 @@ class Quantitative(BaseReferential):
             multiplier = 1
 
         scientific_value = value
-        prefix_index = 0
-        prefix = ""
-
-        while scientific_value > 1000:
-            prefix_index += 1
-            scientific_value /= 1000
+        scientific_value /= 10**base
 
-        if prefix_index < len(prefixes):
-            prefix = prefixes[prefix_index]
-            localized_value = QLocale().toString(float(scientific_value * multiplier), 'f', digits)
+        if base > 1:
+            localized_value = QLocale().toString(float(scientific_value * multiplier), 'f', 2)
         else:
-            localized_value = QLocale().toString(float(value * multiplier), 'f', 0)
+            localized_value = QLocale().toString(float(value * multiplier), 'f', 2)
 
-        return localized_value, prefix
+        return localized_value
 
-    async def localized(self, units=False, international_system=False):
-        value = await self.value()
-        prefix = ""
-        if international_system:
-            localized_value, prefix = Quantitative.to_si(value, self.app.preferences['digits_after_comma'])
+    def localized(self, units=False, show_base=False):
+        value = self.value()
+        dividend, base = self._blockchain_processor.last_ud(self.currency)
+        if show_base:
+            localized_value = Quantitative.to_si(value, base)
+            prefix = Quantitative.base_str(base)
         else:
-            localized_value = QLocale().toString(float(value), 'f', 0)
+            localized_value = QLocale().toString(float(value), 'f', 2)
+            prefix = ""
 
-        if units or international_system:
+        if units or show_base:
             return QCoreApplication.translate("Quantitative",
                                               Quantitative._REF_STR_) \
                 .format(localized_value,
                         prefix,
-                        self.community.short_currency if units else "")
+                        (" " if prefix and units else "") + (shortened(self.currency) if units else ""))
         else:
             return localized_value
 
-    async def diff_localized(self, units=False, international_system=False):
-        value = await self.differential()
-        prefix = ""
-        if international_system:
-            localized_value, prefix = Quantitative.to_si(value, self.app.preferences['digits_after_comma'])
+    def diff_localized(self, units=False, show_base=False):
+        value = self.differential()
+        dividend, base = self._blockchain_processor.last_ud(self.currency)
+        if show_base:
+            localized_value = Quantitative.to_si(value, base)
+            prefix = Quantitative.base_str(base)
         else:
-            localized_value = QLocale().toString(float(value), 'f', 0)
+            localized_value = QLocale().toString(float(value), 'f', 2)
+            prefix = ""
 
-        if units or international_system:
+        if units or show_base:
             return QCoreApplication.translate("Quantitative",
                                               Quantitative._REF_STR_) \
                 .format(localized_value,
                         prefix,
-                        self.community.short_currency if units else "")
+                        (" " if prefix and units else "") + (shortened(self.currency) if units else ""))
         else:
             return localized_value
diff --git a/src/sakia/core/money/relative.py b/src/sakia/money/relative.py
similarity index 51%
rename from src/sakia/core/money/relative.py
rename to src/sakia/money/relative.py
index 004deb6c3aed2d7b2a597740d41e523a518c349d..f8afe4f35adf55bbffa76fec3d39a724395e1727 100644
--- a/src/sakia/core/money/relative.py
+++ b/src/sakia/money/relative.py
@@ -1,13 +1,13 @@
-from PyQt5.QtCore import QObject, QCoreApplication, QT_TRANSLATE_NOOP, QLocale
 from .base_referential import BaseReferential
-from .relative_to_past import RelativeToPast
+from .currency import shortened
+from ..data.processors import BlockchainProcessor
 
 from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, QLocale
 
 
 class Relative(BaseReferential):
     _NAME_STR_ = QT_TRANSLATE_NOOP('Relative', 'UD')
-    _REF_STR_ = QT_TRANSLATE_NOOP('Relative', "{0} {1}UD {2}")
+    _REF_STR_ = QT_TRANSLATE_NOOP('Relative', "{0} {1}UD{2}")
     _UNITS_STR_ = QT_TRANSLATE_NOOP('Relative', "UD {0}")
     _FORMULA_STR_ = QT_TRANSLATE_NOOP('Relative',
                                       """R = Q / UD(t)
@@ -28,23 +28,29 @@ class Relative(BaseReferential):
                                            the average.
                                           """.replace('\n', '<br >'))
 
-    def __init__(self, amount, community, app, block_number=None):
-        super().__init__(amount, community, app, block_number)
+    def __init__(self, amount, currency, app, block_number=None):
+        super().__init__(amount, currency, app, block_number)
+        self._blockchain_processor = BlockchainProcessor.instanciate(self.app)
 
     @classmethod
-    def instance(cls, amount, community, app, block_number=None):
-        if app.preferences['forgetfulness']:
-            return cls(amount, community, app, block_number)
-        else:
-            return RelativeToPast(amount, community, app, block_number)
+    def instance(cls, amount, currency, app, block_number=None):
+        """
 
+        :param int amount:
+        :param str currency:
+        :param sakia.app.Application app:
+        :param int block_number:
+        :return:
+        """
+        return cls(amount, currency, app, block_number)
+        
     @classmethod
     def translated_name(cls):
         return QCoreApplication.translate('Relative', Relative._NAME_STR_)
 
     @property
     def units(self):
-            return QCoreApplication.translate("Relative", Relative._UNITS_STR_).format(self.community.short_currency)
+            return QCoreApplication.translate("Relative", Relative._UNITS_STR_).format(shortened(self.currency))
 
     @property
     def formula(self):
@@ -58,7 +64,11 @@ class Relative(BaseReferential):
     def diff_units(self):
         return self.units
 
-    async def value(self):
+    @staticmethod
+    def base_str(base):
+        return ""
+
+    def value(self):
         """
         Return relative value of amount
 
@@ -68,67 +78,37 @@ class Relative(BaseReferential):
         :param sakia.core.community.Community community: Community instance
         :return: float
         """
-        dividend = await self.community.dividend()
+        dividend, base = self._blockchain_processor.last_ud(self.currency)
         if dividend > 0:
-            return self.amount / float(dividend)
+            return self.amount / (float(dividend * (10**base)))
         else:
             return self.amount
 
-    async def differential(self):
-        return await self.value()
+    def differential(self):
+        return self.value()
 
-    @staticmethod
-    def to_si(value, digits):
-        prefixes = ['', 'm', 'µ', 'n', 'p', 'f', 'a', 'z', 'y']
-        if value < 0:
-            value = -value
-            multiplier = -1
-        else:
-            multiplier = 1
-        scientific_value = value
-        prefix_index = 0
+    def localized(self, units=False, show_base=False):
+        value = self.value()
         prefix = ""
+        localized_value = QLocale().toString(float(value), 'f', self.app.parameters.digits_after_comma)
 
-        while int(scientific_value) == 0 and scientific_value > 0.0:
-            scientific_value *= 1000
-            prefix_index += 1
-
-        if prefix_index < len(prefixes):
-            prefix = prefixes[prefix_index]
-            localized_value = QLocale().toString(float(scientific_value * multiplier), 'f', digits)
-        else:
-            localized_value = QLocale().toString(float(value * multiplier), 'f', digits)
-
-        return localized_value, prefix
-
-    async def localized(self, units=False, international_system=False):
-        value = await self.value()
-        prefix = ""
-        if international_system:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
-
-        if units or international_system:
+        if units:
             return QCoreApplication.translate("Relative", Relative._REF_STR_) \
                 .format(localized_value,
-                        prefix,
-                        self.community.short_currency if units else "")
+                        prefix + " " if prefix else "",
+                        (" " + shortened(self.currency)) if units else "")
         else:
             return localized_value
 
-    async def diff_localized(self, units=False, international_system=False):
-        value = await self.differential()
+    def diff_localized(self, units=False, show_base=False):
+        value = self.differential()
         prefix = ""
-        if international_system and value != 0:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
+        localized_value = QLocale().toString(float(value), 'f', self.app.parameters.digits_after_comma)
 
-        if units or international_system:
+        if units:
             return QCoreApplication.translate("Relative", Relative._REF_STR_) \
                 .format(localized_value,
-                        prefix,
-                        self.community.short_currency if units else "")
+                        prefix + " " if prefix else "",
+                        (" " + shortened(self.currency)) if units else "")
         else:
             return localized_value
diff --git a/src/sakia/core/money/relative_zerosum.py b/src/sakia/money/relative_zerosum.py
similarity index 56%
rename from src/sakia/core/money/relative_zerosum.py
rename to src/sakia/money/relative_zerosum.py
index 86d2eb1c4aae2acefd811c3c6c3fe57fe5c4ecc4..c000a30b3f02524becad42c5fdd35869cafeaab9 100644
--- a/src/sakia/core/money/relative_zerosum.py
+++ b/src/sakia/money/relative_zerosum.py
@@ -1,19 +1,20 @@
-import math
 from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, QLocale
 from .relative import Relative
 from .base_referential import BaseReferential
+from .currency import shortened
+from ..data.processors import BlockchainProcessor
 
 
 class RelativeZSum(BaseReferential):
     _NAME_STR_ = QT_TRANSLATE_NOOP('RelativeZSum', 'Relat Z-sum')
-    _REF_STR_ = QT_TRANSLATE_NOOP('RelativeZSum', "{0} {1}R0 {2}")
+    _REF_STR_ = QT_TRANSLATE_NOOP('RelativeZSum', "{0} {1}R0{2}")
     _UNITS_STR_ = QT_TRANSLATE_NOOP('RelativeZSum', "R0 {0}")
     _FORMULA_STR_ = QT_TRANSLATE_NOOP('RelativeZSum',
-                                      """R0 = (Q / UD(t)) - (( M(t-1) / N(t) ) / UD(t))
+                                      """R0 = (R / UD(t)) - (( M(t-1) / N(t) ) / UD(t))
                                         <br >
                                         <table>
                                         <tr><td>R0</td><td>Relative value at zero sum</td></tr>
-                                        <tr><td>Q</td><td>Quantitative value</td></tr>
+                                        <tr><td>R</td><td>Relative value</td></tr>
                                         <tr><td>M</td><td>Monetary mass</td></tr>
                                         <tr><td>N</td><td>Members count</td></tr>
                                         <tr><td>t</td><td>Last UD time</td></tr>
@@ -26,8 +27,9 @@ class RelativeZSum(BaseReferential):
                                             the value is under the average value.
                                            """.replace('\n', '<br >'))
 
-    def __init__(self, amount, community, app, block_number=None):
-        super().__init__(amount, community, app, block_number)
+    def __init__(self, amount, currency, app, block_number=None):
+        super().__init__(amount, currency, app, block_number)
+        self._blockchain_processor = BlockchainProcessor.instanciate(self.app)
 
     @classmethod
     def translated_name(cls):
@@ -35,7 +37,7 @@ class RelativeZSum(BaseReferential):
 
     @property
     def units(self):
-        return QCoreApplication.translate("RelativeZSum", RelativeZSum._UNITS_STR_).format(self.community.short_currency)
+        return QCoreApplication.translate("RelativeZSum", RelativeZSum._UNITS_STR_).format(shortened(self.currency))
 
     @property
     def formula(self):
@@ -47,9 +49,13 @@ class RelativeZSum(BaseReferential):
 
     @property
     def diff_units(self):
-        return QCoreApplication.translate("Relative", Relative._UNITS_STR_).format(self.community.short_currency)
+        return QCoreApplication.translate("Relative", Relative._UNITS_STR_).format(shortened(self.currency))
 
-    async def value(self):
+    @staticmethod
+    def base_str(base):
+        return Relative.base_str(base)
+
+    def value(self):
         """
         Return relative value of amount minus the average value
 
@@ -64,48 +70,41 @@ class RelativeZSum(BaseReferential):
         :param sakia.core.community.Community community: Community instance
         :return: float
         """
-        ud_block = await self.community.get_ud_block()
-        ud_block_minus_1 = await self.community.get_ud_block(x=1)
-        if ud_block_minus_1 and ud_block['membersCount'] > 0:
-            median = ud_block_minus_1['monetaryMass'] / ud_block['membersCount']
-            relative_value = self.amount / float(ud_block['dividend'] * math.pow(10, ud_block['unitbase']))
-            relative_median = median / (ud_block['dividend'] * math.pow(10, ud_block['unitbase']))
+        dividend, base = self._blockchain_processor.previous_ud(self.currency)
+        previous_monetary_mass = self._blockchain_processor.previous_monetary_mass(self.currency)
+        members_count = self._blockchain_processor.current_members_count(self.currency)
+        if previous_monetary_mass and members_count > 0:
+            median = previous_monetary_mass / members_count
+            relative_value = self.amount / float(dividend * 10**base)
+            relative_median = median / float(dividend * 10**base)
         else:
             relative_value = self.amount
             relative_median = 0
         return relative_value - relative_median
 
-    async def differential(self):
-        return await Relative(self.amount, self.community, self.app).value()
+    def differential(self):
+        return Relative(self.amount, self.currency, self.app).value()
 
-    async def localized(self, units=False, international_system=False):
-        value = await self.value()
+    def localized(self, units=False, show_base=False):
+        value = self.value()
 
-        prefix = ""
-        if international_system:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
+        localized_value = QLocale().toString(float(value), 'f', self.app.parameters.digits_after_comma)
 
-        if units or international_system:
+        if units:
             return QCoreApplication.translate("RelativeZSum", RelativeZSum._REF_STR_)\
-                .format(localized_value,
-                        prefix,
-                        self.community.short_currency if units else "")
+                .format(localized_value, "",
+                        (" " + shortened(self.currency)) if units else "")
         else:
             return localized_value
 
-    async def diff_localized(self, units=False, international_system=False):
-        value = await self.differential()
+    def diff_localized(self, units=False, show_base=False):
+        value = self.differential()
 
-        prefix = ""
-        if international_system and value != 0:
-            localized_value, prefix = Relative.to_si(value, self.app.preferences['digits_after_comma'])
-        else:
-            localized_value = QLocale().toString(float(value), 'f', self.app.preferences['digits_after_comma'])
+        localized_value = QLocale().toString(float(value), 'f', self.app.parameters.digits_after_comma)
 
-        if units or international_system:
+        if units:
             return QCoreApplication.translate("Relative", Relative._REF_STR_)\
-                .format(localized_value, prefix, self.community.short_currency if units else "")
+                .format(localized_value, "",
+                        (" " + shortened(self.currency)) if units else "")
         else:
             return localized_value
diff --git a/src/sakia/options.py b/src/sakia/options.py
new file mode 100644
index 0000000000000000000000000000000000000000..506598acc3e4f3c7e20e5e33ec94511db3c3ab17
--- /dev/null
+++ b/src/sakia/options.py
@@ -0,0 +1,76 @@
+"""
+Created on 7 févr. 2014
+
+@author: inso
+"""
+
+import attr
+import logging
+from logging import FileHandler, StreamHandler
+from logging.handlers import RotatingFileHandler
+from optparse import OptionParser
+from os import environ, path, makedirs
+
+
+def config_path_factory():
+    if "XDG_CONFIG_HOME" in environ:
+        env_path = environ["XDG_CONFIG_HOME"]
+    elif "HOME" in environ:
+        env_path = path.join(environ["HOME"], ".config")
+    elif "APPDATA" in environ:
+        env_path = environ["APPDATA"]
+    else:
+        env_path = path.dirname(__file__)
+    return path.join(env_path, 'sakia')
+
+
+@attr.s()
+class SakiaOptions:
+    config_path = attr.ib(default=attr.Factory(config_path_factory))
+    database = attr.ib(default="sakia")
+    _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
+
+    @classmethod
+    def from_arguments(cls, argv):
+        options = cls()
+
+        if not path.exists(options.config_path):
+            makedirs(options.config_path)
+
+        options._parse_arguments(argv)
+
+        return options
+
+    def _parse_arguments(self, argv):
+        parser = OptionParser()
+        parser.add_option("-v", "--verbose",
+                          action="store_true", dest="verbose", default=False,
+                          help="Print INFO messages to stdout")
+
+        parser.add_option("-d", "--debug",
+                          action="store_true", dest="debug", default=False,
+                          help="Print DEBUG messages to stdout")
+
+        parser.add_option("--database",  dest="database", default="sakia",
+                          help="Select another database filename")
+
+        (options, args) = parser.parse_args(argv)
+
+        if options.database:
+            self.database = options.database
+
+        if options.debug:
+            self._logger.setLevel(logging.DEBUG)
+            formatter = logging.Formatter('%(levelname)s:%(module)s:%(funcName)s:%(message)s')
+        elif options.verbose:
+            self._logger.setLevel(logging.INFO)
+            formatter = logging.Formatter('%(levelname)s:%(message)s')
+
+        if options.debug or options.verbose:
+            logging.getLogger('quamash').setLevel(logging.INFO)
+            file_handler = RotatingFileHandler(path.join(self.config_path, 'sakia.log'), 'a', 1000000, 10)
+            file_handler.setFormatter(formatter)
+            stream_handler = StreamHandler()
+            stream_handler.setFormatter(formatter)
+            self._logger.handlers = [file_handler, stream_handler]
+            self._logger.propagate = False
diff --git a/src/sakia/services/__init__.py b/src/sakia/services/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..62b82310cb35d4f4e0d442bd01b676516b86a23d
--- /dev/null
+++ b/src/sakia/services/__init__.py
@@ -0,0 +1,6 @@
+from .network import NetworkService
+from .identities import IdentitiesService
+from .blockchain import BlockchainService
+from .documents import DocumentsService
+from .sources import SourcesServices
+from .transactions import TransactionsService
diff --git a/src/sakia/services/blockchain.py b/src/sakia/services/blockchain.py
new file mode 100644
index 0000000000000000000000000000000000000000..62a3e33f2d14f322a4ab80fb0255a6ebfe040c1d
--- /dev/null
+++ b/src/sakia/services/blockchain.py
@@ -0,0 +1,108 @@
+from PyQt5.QtCore import QObject
+import math
+import logging
+from duniterpy.api.errors import DuniterError
+from sakia.errors import NoPeerAvailable
+
+
+class BlockchainService(QObject):
+    """
+    Blockchain service is managing new blocks received
+    to update data locally
+    """
+    def __init__(self, app, currency, blockchain_processor, bma_connector,
+                 identities_service, transactions_service, sources_service):
+        """
+        Constructor the identities service
+
+        :param sakia.app.Application app: Sakia application
+        :param str currency: The currency name of the community
+        :param sakia.data.processors.BlockchainProcessor blockchain_processor: the blockchain processor for given currency
+        :param sakia.data.connectors.BmaConnector bma_connector: The connector to BMA API
+        :param sakia.services.IdentitiesService identities_service: The identities service
+        :param sakia.services.TransactionsService transactions_service: The transactions service
+        :param sakia.services.SourcesService sources_service: The sources service
+        """
+        super().__init__()
+        self.app = app
+        self._blockchain_processor = blockchain_processor
+        self._bma_connector = bma_connector
+        self.currency = currency
+        self._identities_service = identities_service
+        self._transactions_service = transactions_service
+        self._sources_service = sources_service
+        self._logger = logging.getLogger('sakia')
+
+    async def handle_blockchain_progress(self, network_blockstamp):
+        """
+        Handle a new current block uid
+
+        :param duniterpy.documents.BlockUID network_blockstamp:
+        """
+        try:
+            with_identities = await self._blockchain_processor.new_blocks_with_identities(self.currency)
+            with_money = await self._blockchain_processor.new_blocks_with_money(self.currency)
+            blocks = await self._blockchain_processor.blocks(with_identities + with_money + [network_blockstamp.number],
+                                                             self.currency)
+            await self._sources_service.refresh_sources()
+            if len(blocks) > 0:
+                identities = await self._identities_service.handle_new_blocks(blocks)
+                changed_tx, new_tx, new_dividends = await self._transactions_service.handle_new_blocks(blocks)
+                self._blockchain_processor.handle_new_blocks(self.currency, blocks)
+                self.app.db.commit()
+                for tx in changed_tx:
+                    self.app.transaction_state_changed.emit(tx)
+                for tx in new_tx:
+                    self.app.new_transfer.emit(tx)
+                for ud in new_dividends:
+                    self.app.new_dividend.emit(ud)
+                for idty in identities:
+                    self.app.identity_changed.emit(idty)
+                self.app.sources_refreshed.emit()
+        except (NoPeerAvailable, DuniterError) as e:
+            self._logger.debug(str(e))
+
+    def current_buid(self):
+        return self._blockchain_processor.current_buid(self.currency)
+
+    def parameters(self):
+        return self._blockchain_processor.parameters(self.currency)
+
+    def time(self):
+        return self._blockchain_processor.time(self.currency)
+
+    def current_members_count(self):
+        return self._blockchain_processor.current_members_count(self.currency)
+
+    def current_mass(self):
+        return self._blockchain_processor.current_mass(self.currency)
+
+    def last_ud(self):
+        return self._blockchain_processor.last_ud(self.currency)
+
+    def last_members_count(self):
+        return self._blockchain_processor.last_members_count(self.currency)
+
+    def last_ud_time(self):
+        return self._blockchain_processor.last_ud_time(self.currency)
+
+    def previous_members_count(self):
+        return self._blockchain_processor.previous_members_count(self.currency)
+
+    def previous_monetary_mass(self):
+        return self._blockchain_processor.previous_monetary_mass(self.currency)
+
+    def previous_ud_time(self):
+        return self._blockchain_processor.previous_ud_time(self.currency)
+
+    def previous_ud(self):
+        return self._blockchain_processor.previous_ud(self.currency)
+
+    def computed_dividend(self):
+        """
+        Computes next dividend value
+        :rtype: int
+        """
+        parameters = self.parameters()
+        next_ud = parameters.c * self.current_mass() / self.last_members_count()
+        return math.ceil(next_ud)
diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd845224d63bc3a9b983ec7a502e09c0dca12d51
--- /dev/null
+++ b/src/sakia/services/documents.py
@@ -0,0 +1,424 @@
+import jsonschema
+import attr
+import logging
+
+from duniterpy.key import SigningKey
+from duniterpy.documents import Certification, Membership, Revocation, InputSource, \
+    OutputSource, SIGParameter, Unlock, block_uid
+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, Transaction
+from sakia.data.processors import BlockchainProcessor, IdentitiesProcessor, NodesProcessor, \
+    TransactionsProcessor, SourcesProcessor
+from sakia.data.connectors import BmaConnector
+from sakia.errors import NotEnoughChangeError
+
+
+@attr.s()
+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()
+    _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
+    def instanciate(cls, app):
+        """
+        Instanciate a blockchain processor
+        :param sakia.app.Application app: the app
+        """
+        return cls(BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters),
+                   BlockchainProcessor.instanciate(app),
+                   IdentitiesProcessor.instanciate(app),
+                   TransactionsProcessor.instanciate(app),
+                   SourcesProcessor.instanciate(app))
+
+    async def broadcast_identity(self, connection, password):
+        """
+        Send our self certification to a target community
+
+        :param sakia.data.entities.Connection connection: the connection published
+        :param str password: the private key password
+        """
+        block_uid = self._blockchain_processor.current_buid(connection.currency)
+        timestamp = self._blockchain_processor.time(connection.currency)
+        selfcert = IdentityDoc(10,
+                               connection.currency,
+                               connection.pubkey,
+                               connection.uid,
+                               block_uid,
+                               None)
+        key = SigningKey(connection.salt, password, connection.scrypt_params)
+        selfcert.sign([key])
+        self._logger.debug("Key publish : {0}".format(selfcert.signed_raw()))
+
+        responses = await self._bma_connector.broadcast(connection.currency, bma.wot.add,
+                                                        req_args={'identity': selfcert.signed_raw()})
+        result = (False, "")
+        for r in responses:
+            if r.status == 200:
+                result = (True, (await r.json()))
+            elif not result[0]:
+                result = (False, (await r.text()))
+            else:
+                await r.release()
+
+        if result[0]:
+            identity = self._identities_processor.get_identity(connection.currency, connection.pubkey, connection.uid)
+            if not identity:
+                identity = Identity(connection.currency, connection.pubkey, connection.uid)
+            identity.blockstamp = block_uid
+            identity.signature = selfcert.signatures[0]
+            identity.timestamp = timestamp
+        else:
+            identity = None
+
+        return result, identity
+
+    async def broadcast_revocation(self, currency, identity_document, revocation_document):
+        signed_raw = revocation_document.signed_raw(identity_document)
+        self._logger.debug("Broadcasting : \n" + signed_raw)
+        responses = await self._bma_connector.broadcast(currency, bma.wot.revoke, req_args={
+                                                            'revocation': signed_raw
+                                                        })
+
+        result = False, ""
+        for r in responses:
+            if r.status == 200:
+                result = True, (await r.json())
+            elif not result[0]:
+                try:
+                    result = False, bma.api.parse_error(await r.text())["message"]
+                except jsonschema.ValidationError as e:
+                    result = False, str(e)
+            else:
+                await r.release()
+
+        return result
+
+    async def send_membership(self, connection, password, mstype):
+        """
+        Send a membership document to a target community.
+        Signal "document_broadcasted" is emitted at the end.
+
+        :param str currency: the currency target
+        :param sakia.data.entities.IdentityDoc identity: the identitiy data
+        :param str salt: The account SigningKey salt
+        :param str password: The account SigningKey password
+        :param str mstype: The type of membership demand. "IN" to join, "OUT" to leave
+        """
+        self._logger.debug("Send membership")
+
+        blockUID = self._blockchain_processor.current_buid(connection.currency)
+        membership = Membership(10, connection.currency,
+                                connection.pubkey, blockUID, mstype, connection.uid,
+                                connection.blockstamp, None)
+        key = SigningKey(connection.salt, password, connection.scrypt_params)
+        membership.sign([key])
+        self._logger.debug("Membership : {0}".format(membership.signed_raw()))
+        responses = await self._bma_connector.broadcast(connection.currency, bma.blockchain.membership,
+                                                        req_args={'membership': membership.signed_raw()})
+        result = (False, "")
+        for r in responses:
+            if not result[0]:
+                if isinstance(r, BaseException):
+                    result = (False, str(r))
+                else:
+                    try:
+                        result = (False, (await r.json())["message"])
+                    except KeyError:
+                        result = (False, await str(r.text()))
+
+            elif r.status == 200:
+                result = (True, (await r.json()))
+            else:
+                await r.release()
+        return result
+
+    async def certify(self, connection, password, identity):
+        """
+        Certify an other identity
+
+        :param sakia.data.entities.Connection connection: the connection published
+        :param str password: the private key password
+        :param sakia.data.entities.Identity identity: the identity certified
+        """
+        self._logger.debug("Certdata")
+        blockUID = self._blockchain_processor.current_buid(connection.currency)
+        if not identity.signature:
+            lookup_data = await self._bma_connector.get(connection.currency, bma.wot.lookup,
+                                                     req_args={'search': identity.pubkey})
+            for uid_data in next(data["uids"] for data in lookup_data["results"] if data["pubkey"] == identity.pubkey):
+                if uid_data["uid"] == identity.uid and block_uid(uid_data["meta"]["timestamp"]) == identity.blockstamp:
+                    identity.signature = uid_data["self"]
+                    break
+            else:
+                return False, "Could not find certified identity signature"
+
+        certification = Certification(10, connection.currency,
+                                      connection.pubkey, identity.pubkey, blockUID, None)
+
+        key = SigningKey(connection.salt, password, connection.scrypt_params)
+        certification.sign(identity.document(), [key])
+        signed_cert = certification.signed_raw(identity.document())
+        self._logger.debug("Certification : {0}".format(signed_cert))
+
+        responses = await self._bma_connector.broadcast(connection.currency, bma.wot.certify, req_args={'cert': signed_cert})
+        result = (False, "")
+        for r in responses:
+            if isinstance(r, BaseException) and not result[0]:
+                result = (False, (str(r)))
+            else:
+                if r.status == 200:
+                    result = (True, (await r.json()))
+                elif not result[0]:
+                    result = (False, (await r.text()))
+                else:
+                    await r.release()
+        return result
+
+    async def revoke(self, currency, identity, salt, password):
+        """
+        Revoke self-identity on server, not in blockchain
+
+        :param str currency: The currency of the identity
+        :param sakia.data.entities.IdentityDoc identity: The certified identity
+        :param str salt: The account SigningKey salt
+        :param str password: The account SigningKey password
+        """
+        revocation = Revocation(10, currency, None)
+        self_cert = identity.document()
+
+        key = SigningKey(salt, password)
+        revocation.sign(self_cert, [key])
+
+        self._logger.debug("Self-Revokation Document : \n{0}".format(revocation.raw(self_cert)))
+        self._logger.debug("Signature : \n{0}".format(revocation.signatures[0]))
+
+        data = {
+            'pubkey': identity.pubkey,
+            'self_': self_cert.signed_raw(),
+            'sig': revocation.signatures[0]
+        }
+        self._logger.debug("Posted data : {0}".format(data))
+        responses = await self._bma_connector.broadcast(currency, bma.wot.Revoke, {}, data)
+        result = (False, "")
+        for r in responses:
+            if r.status == 200:
+                result = (True, (await r.json()))
+            elif not result[0]:
+                result = (False, (await r.text()))
+            else:
+                await r.release()
+        return result
+
+    def generate_revokation(self, connection, password):
+        """
+        Generate account revokation document for given community
+
+        :param sakia.data.entities.Connection connection: The connection of the identity
+        :param str password: The account SigningKey password
+        """
+        document = Revocation(10, connection.currency, connection.pubkey, "")
+        identity = self._identities_processor.get_identity(connection.currency, connection.pubkey, connection.uid)
+        self_cert = identity.document()
+
+        key = SigningKey(connection.salt, password, connection.scrypt_params)
+
+        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(10, 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,
+                             local=True,
+                             raw=txdoc.signed_raw())
+            return await self._transactions_processor.send(tx, txdoc, connection.currency)
+        except NotEnoughChangeError as e:
+            return (False, str(e)), None
diff --git a/src/sakia/services/identities.py b/src/sakia/services/identities.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef1958fc0c4e39b8bc2157f34f6f7c1ba6fe4d84
--- /dev/null
+++ b/src/sakia/services/identities.py
@@ -0,0 +1,324 @@
+from PyQt5.QtCore import QObject
+import asyncio
+from duniterpy.api import bma, errors
+from duniterpy.documents import BlockUID, block_uid
+from sakia.errors import NoPeerAvailable
+from sakia.data.entities import Certification
+import logging
+
+
+class IdentitiesService(QObject):
+    """
+    Identities service is managing identities data received
+    to update data locally
+    """
+    def __init__(self, currency, connections_processor, identities_processor, certs_processor,
+                 blockchain_processor, bma_connector):
+        """
+        Constructor the identities service
+
+        :param str currency: The currency name of the community
+        :param sakia.data.processors.IdentitiesProcessor identities_processor: the identities processor for given currency
+        :param sakia.data.processors.CertificationsProcessor certs_processor: the certifications processor for given currency
+        :param sakia.data.processors.BlockchainProcessor blockchain_processor: the blockchain processor for given currency
+        :param sakia.data.connectors.BmaConnector bma_connector: The connector to BMA API
+        """
+        super().__init__()
+        self._connections_processor = connections_processor
+        self._identities_processor = identities_processor
+        self._certs_processor = certs_processor
+        self._blockchain_processor = blockchain_processor
+        self._bma_connector = bma_connector
+        self.currency = currency
+        self._logger = logging.getLogger('sakia')
+
+    def certification_expired(self, cert_time):
+        """
+        Return True if the certificaton time is too old
+
+        :param int cert_time: the timestamp of the certification
+        """
+        parameters = self._blockchain_processor.parameters(self.currency)
+        blockchain_time = self._blockchain_processor.time(self.currency)
+        return blockchain_time - cert_time > parameters.sig_validity
+
+    def certification_writable(self, cert_time):
+        """
+        Return True if the certificaton time is too old
+
+        :param int cert_time: the timestamp of the certification
+        """
+        parameters = self._blockchain_processor.parameters(self.currency)
+        blockchain_time = self._blockchain_processor.time(self.currency)
+        return blockchain_time - cert_time < parameters.sig_window * parameters.avg_gen_time
+
+    def expiration_date(self, identity):
+        """
+        Get the expiration date of the identity
+        :param sakia.data.entities.Identity identity:
+        :return: the expiration timestamp
+        :rtype: int
+        """
+        validity = self._blockchain_processor.parameters(self.currency).ms_validity
+        if identity.membership_timestamp:
+            return identity.membership_timestamp + validity
+        else:
+            return 0
+
+    def _get_connections_identities(self):
+        connections = self._connections_processor.connections_to(self.currency)
+        identities = []
+        for c in connections:
+            identities.append(self._identities_processor.get_identity(self.currency, c.pubkey))
+        return identities
+
+    async def load_memberships(self, identity):
+        """
+        Request the identity data and save it to written identities
+        It does nothing if the identity is already written and updated with blockchain lookups
+        :param sakia.data.entities.Identity identity: the identity
+        """
+        try:
+            search = await self._bma_connector.get(self.currency, bma.blockchain.memberships,
+                                                   req_args={'search': identity.pubkey})
+            blockstamp = BlockUID.empty()
+            membership_data = None
+
+            for ms in search['memberships']:
+                if ms['blockNumber'] > blockstamp.number:
+                    blockstamp = BlockUID(ms["blockNumber"], ms['blockHash'])
+                    membership_data = ms
+            if membership_data:
+                identity.membership_timestamp = await self._blockchain_processor.timestamp(self.currency, blockstamp)
+                identity.membership_buid = blockstamp
+                identity.membership_type = ms["membership"]
+                identity.membership_written_on = ms["written"]
+                identity = await self.load_requirements(identity)
+            # We save connections pubkeys
+            if identity.pubkey in self._connections_processor.pubkeys():
+                self._identities_processor.insert_or_update_identity(identity)
+        except errors.DuniterError as e:
+            logging.debug(str(e))
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+        return identity
+
+    async def load_certifiers_of(self, identity):
+        """
+        Request the identity data and save it to written certifications
+        It does nothing if the identity is already written and updated with blockchain lookups
+        :param sakia.data.entities.Identity identity: the identity
+        """
+        certifications = []
+        try:
+            data = await self._bma_connector.get(self.currency, bma.wot.certifiers_of,
+                                                 {'search': identity.pubkey})
+            for certifier_data in data['certifications']:
+                cert = Certification(currency=self.currency,
+                                     certified=data["pubkey"],
+                                     certifier=certifier_data["pubkey"],
+                                     block=certifier_data["cert_time"]["block"],
+                                     timestamp=certifier_data["cert_time"]["medianTime"],
+                                     signature=certifier_data['signature'])
+                if certifier_data['written']:
+                    cert.written_on = certifier_data['written']['number']
+                certifications.append(cert)
+                # We save connections pubkeys
+                if identity.pubkey in self._connections_processor.pubkeys():
+                    self._certs_processor.insert_or_update_certification(cert)
+        except errors.DuniterError as e:
+            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
+                logging.debug("Certified by error : {0}".format(str(e)))
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+        return certifications
+
+    async def load_certified_by(self, identity):
+        """
+        Request the identity data and save it to written certifications
+        It does nothing if the identity is already written and updated with blockchain lookups
+        :param sakia.data.entities.Identity identity: the identity
+        """
+        certifications = []
+        try:
+            data = await self._bma_connector.get(self.currency, bma.wot.certified_by, {'search': identity.pubkey})
+            for certified_data in data['certifications']:
+                cert = Certification(currency=self.currency,
+                                     certifier=data["pubkey"],
+                                     certified=certified_data["pubkey"],
+                                     block=certified_data["cert_time"]["block"],
+                                     timestamp=certified_data["cert_time"]["medianTime"],
+                                     signature=certified_data['signature'])
+                if certified_data['written']:
+                    cert.written_on = certified_data['written']['number']
+                certifications.append(cert)
+                # We save connections pubkeys
+                if identity.pubkey in self._connections_processor.pubkeys():
+                    self._certs_processor.insert_or_update_certification(cert)
+        except errors.DuniterError as e:
+            if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID):
+                logging.debug("Certified by error : {0}".format(str(e)))
+        except NoPeerAvailable as e:
+            logging.debug(str(e))
+        return certifications
+
+    def _parse_revocations(self, block):
+        """
+        Parse revoked pubkeys found in a block and refresh local data
+
+        :param duniterpy.documents.Block block: the block received
+        :return: list of pubkeys updated
+        """
+        revoked = set([])
+        for rev in block.revoked:
+            revoked.add(rev.pubkey)
+
+        for pubkey in revoked:
+            written = self._identities_processor.get_identity(self.currency, pubkey)
+            # we update every written identities known locally
+            if written:
+                written.revoked_on = block.blockUID
+        return revoked
+
+    def _parse_memberships(self, block):
+        """
+        Parse memberships pubkeys found in a block and refresh local data
+
+        :param duniterpy.documents.Block block: the block received
+        :return: list of pubkeys requiring a refresh of requirements
+        """
+        need_refresh = []
+        connections_identities = self._get_connections_identities()
+        for ms in block.joiners + block.actives:
+            # we update every written identities known locally
+            for identity in connections_identities:
+                if ms.issuer == identity:
+                    identity.membership_written_on = block.number
+                    identity.membership_type = "IN"
+                    identity.membership_buid = ms.membership_ts
+                    self._identities_processor.insert_or_update_identity(identity)
+                    # If the identity was not member
+                    # it can become one
+                    if not identity.member:
+                        need_refresh.append(identity)
+
+        for ms in block.leavers:
+            # we update every written identities known locally
+            for identity in connections_identities:
+                identity.membership_written_on = block.number
+                identity.membership_type = "OUT"
+                identity.membership_buid = ms.membership_ts
+                self._identities_processor.insert_or_update_identity(identity)
+                # If the identity was a member
+                # it can stop to be one
+                if identity.member:
+                    need_refresh.append(identity)
+
+        return need_refresh
+
+    def _parse_certifications(self, block):
+        """
+        Parse certified pubkeys found in a block and refresh local data
+        This method only creates certifications if one of both identities is
+        locally known as written.
+        This method returns the identities needing to be refreshed. These can only be
+        the identities which we already known as written before parsing this certification.
+        :param duniterpy.documents.Block block:
+        :return:
+        """
+        connections_identities = self._get_connections_identities()
+        need_refresh = []
+        for cert in block.certifications:
+            # if we have are a target or a source of the certification
+            for identity in connections_identities:
+                if cert.pubkey_from == identity.pubkey or cert.pubkey_to in identity.pubkey:
+                    self._certs_processor.create_certification(self.currency, cert, block.blockUID)
+                    need_refresh.append(identity)
+        return need_refresh
+
+    async def load_requirements(self, identity):
+        """
+        Refresh a given identity information
+        :param sakia.data.entities.Identity identity:
+        :return:
+        """
+        try:
+            requirements = await self._bma_connector.get(self.currency, bma.wot.requirements,
+                                                         req_args={'search': identity.pubkey})
+            identity_data = requirements['identities'][0]
+            identity.uid = identity_data["uid"]
+            identity.blockstamp = block_uid(identity_data["meta"]["timestamp"])
+            identity.timestamp = await self._blockchain_processor.timestamp(self.currency, identity.blockstamp)
+            identity.outdistanced = identity_data["outdistanced"]
+            identity.member = identity_data["membershipExpiresIn"] > 0 and not identity_data["outdistanced"]
+            median_time = self._blockchain_processor.time(self.currency)
+            expiration_time = self._blockchain_processor.parameters(self.currency).ms_validity
+            identity.membership_timestamp = median_time - (expiration_time - identity_data["membershipExpiresIn"])
+            # We save connections pubkeys
+            if identity.pubkey in self._connections_processor.pubkeys():
+                self._identities_processor.insert_or_update_identity(identity)
+        except NoPeerAvailable as e:
+            self._logger.debug(str(e))
+        return identity
+
+    def parse_block(self, block):
+        """
+        Parse a block to refresh local data
+        :param block:
+        :return:
+        """
+        self._parse_revocations(block)
+        need_refresh = []
+        need_refresh += self._parse_memberships(block)
+        need_refresh += self._parse_certifications(block)
+        return set(need_refresh)
+
+    async def handle_new_blocks(self, blocks):
+        """
+        Handle new block received and refresh local data
+        :param duniterpy.documents.Block block: the received block
+        """
+        need_refresh = []
+        for block in blocks:
+            need_refresh += self.parse_block(block)
+        refresh_futures = []
+        # for every identity for which we need a refresh, we gather
+        # requirements requests
+        for identity in set(need_refresh):
+            refresh_futures.append(self.load_requirements(identity))
+        await asyncio.gather(*refresh_futures)
+        return need_refresh
+
+    async def lookup(self, text):
+        """
+        Lookup for a given text in the network and in local db
+        :param str text: text contained in identity data
+        :rtype: list[sakia.data.entities.Identity]
+        :return: the list of identities found
+        """
+        return await self._identities_processor.lookup(self.currency, text)
+
+    def get_identity(self, pubkey, uid=""):
+        return self._identities_processor.get_identity(self.currency, pubkey, uid)
+
+    async def find_from_pubkey(self, pubkey):
+        return await self._identities_processor.find_from_pubkey(self.currency, pubkey)
+
+    def ms_time_remaining(self, identity):
+        return self.expiration_date(identity) - self._blockchain_processor.time(identity.currency)
+
+    def certifications_received(self, pubkey):
+        """
+        Get the list of certifications received by a given identity
+        :param str pubkey: the pubkey
+        :rtype: List[sakia.data.entities.Certifications]
+        """
+        return self._certs_processor.certifications_received(self.currency, pubkey)
+
+    def certifications_sent(self, pubkey):
+        """
+        Get the list of certifications received by a given identity
+        :param str pubkey: the pubkey
+        :rtype: List[sakia.data.entities.Certifications]
+        """
+        return self._certs_processor.certifications_sent(self.currency, pubkey)
diff --git a/src/sakia/services/network.py b/src/sakia/services/network.py
new file mode 100644
index 0000000000000000000000000000000000000000..472a72b6d03cde4468cdce204b715e94bf34ce6a
--- /dev/null
+++ b/src/sakia/services/network.py
@@ -0,0 +1,278 @@
+import asyncio
+import logging
+import time
+from collections import Counter
+
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
+from duniterpy.api import errors
+from duniterpy.key import VerifyingKey
+from sakia.data.connectors import NodeConnector
+from sakia.data.entities import Node
+from sakia.decorators import asyncify
+from sakia.errors import InvalidNodeCurrency
+
+
+class NetworkService(QObject):
+    """
+    A network is managing nodes polling and crawling of a
+    given community.
+    """
+    nodes_changed = pyqtSignal()
+    root_nodes_changed = pyqtSignal()
+
+    def __init__(self, app, currency, node_processor, connectors, blockchain_service, identities_service):
+        """
+        Constructor of a network
+
+        :param sakia.app.Application app: The application
+        :param str currency: The currency name of the community
+        :param sakia.data.processors.NodesProcessor node_processor: the nodes processor for given currency
+        :param list connectors: The connectors to nodes of the network
+        :param sakia.services.BlockchainService blockchain_service: the blockchain service
+        :param sakia.services.IdentitiesService identities_service: the identities service
+        """
+        super().__init__()
+        self._app = app
+        self._logger = logging.getLogger('sakia')
+        self._processor = node_processor
+        self._connectors = []
+        for c in connectors:
+            self.add_connector(c)
+        self.currency = currency
+        self._must_crawl = False
+        self._block_found = self._processor.current_buid(self.currency)
+        self._discovery_stack = []
+        self._blockchain_service = blockchain_service
+        self._identities_service = identities_service
+        self._discovery_loop_task = None
+
+    @classmethod
+    def create(cls, node_processor, node_connector):
+        """
+        Create a new network with one knew node
+        Crawls the nodes from the first node to build the
+        community network
+
+        :param sakia.data.processors.NodeProcessor node_processor: The nodes processor
+        :param sakia.data.connectors.NodeConnector node_connector: The first connector of the network service
+        :return:
+        """
+        connectors = [node_connector]
+        node_processor.insert_node(node_connector.node)
+        network = cls(node_connector.node.currency, node_processor, connectors, node_connector.session)
+        return network
+
+    @classmethod
+    def load(cls, app, currency, node_processor, blockchain_service, identities_service):
+        """
+        Create a new network with all known nodes
+
+        :param sakia.app.Application app: Sakia application
+        :param str currency: The currency of this service
+        :param sakia.data.processors.NodeProcessor node_processor: The nodes processor
+        :return:
+        """
+        connectors = []
+        for node in node_processor.nodes(currency):
+            connectors.append(NodeConnector(node, app.parameters))
+        network = cls(app, currency, node_processor, connectors, blockchain_service, identities_service)
+        return network
+
+    def start_coroutines(self):
+        """
+        Start network nodes crawling
+        :return:
+        """
+        if not self._discovery_loop_task:
+            self._discovery_loop_task = asyncio.ensure_future(self.discover_network())
+
+    def nodes(self):
+        """
+        Get all nodes
+        :return:
+        """
+        return self._processor.nodes(self.currency)
+
+    def commit_node(self, node):
+        self._processor.commit_node(node)
+
+    async def stop_coroutines(self, closing=False):
+        """
+        Stop network nodes crawling.
+        """
+        self._must_crawl = False
+        close_tasks = []
+        self._logger.debug("Start closing")
+        for connector in self._connectors:
+            close_tasks.append(asyncio.ensure_future(connector.close_ws()))
+        self._logger.debug("Closing {0} websockets".format(len(close_tasks)))
+        if len(close_tasks) > 0:
+            await asyncio.wait(close_tasks, timeout=15)
+        self._logger.debug("Closed")
+
+    def continue_crawling(self):
+        return self._must_crawl
+
+    def _check_nodes_sync(self):
+        """
+        Check nodes sync with the following rules :
+        1 : The block of the majority
+        2 : The more last different issuers
+        3 : The more difficulty
+        4 : The biggest number or timestamp
+        """
+        online_nodes = self._processor.online_nodes(self.currency)
+        # rule number 1 : block of the majority
+        blocks = [n.current_buid.sha_hash for n in online_nodes if n.current_buid.sha_hash]
+        blocks_occurences = Counter(blocks)
+        blocks_by_occurences = {}
+        for key, value in blocks_occurences.items():
+            the_block = [n.current_buid.sha_hash
+                         for n in online_nodes if n.current_buid.sha_hash == key][0]
+            if value not in blocks_by_occurences:
+                blocks_by_occurences[value] = [the_block]
+            else:
+                blocks_by_occurences[value].append(the_block)
+
+        if len(blocks_by_occurences) == 0:
+            for n in [n for n in online_nodes if n.state in (Node.ONLINE, Node.DESYNCED)]:
+                n.state = Node.ONLINE
+                self._processor.update_node(n)
+            return
+
+        most_present = max(blocks_by_occurences.keys())
+
+        synced_block_hash = blocks_by_occurences[most_present][0]
+
+        for n in online_nodes:
+            if n.current_buid.sha_hash == synced_block_hash:
+                n.state = Node.ONLINE
+            else:
+                n.state = Node.DESYNCED
+            self._processor.update_node(n)
+
+    def add_connector(self, node_connector):
+        """
+        Add a nod to the network.
+        """
+        self._connectors.append(node_connector)
+        node_connector.changed.connect(self.handle_change)
+        node_connector.error.connect(self.handle_error)
+        node_connector.identity_changed.connect(self.handle_identity_change)
+        node_connector.neighbour_found.connect(self.handle_new_node)
+        self._logger.debug("{:} connected".format(node_connector.node.pubkey[:5]))
+
+    @asyncify
+    async def refresh_once(self):
+        for connector in self._connectors:
+            await asyncio.sleep(1)
+            await connector.init_session()
+            connector.refresh(manual=True)
+
+    async def discover_network(self):
+        """
+        Start crawling which never stops.
+        To stop this crawling, call "stop_crawling" method.
+        """
+        self._must_crawl = True
+        first_loop = True
+        asyncio.ensure_future(self.discovery_loop())
+        while self.continue_crawling():
+            for connector in self._connectors:
+                if self.continue_crawling():
+                    await connector.init_session()
+                    connector.refresh()
+                    if not first_loop:
+                        await asyncio.sleep(15)
+            first_loop = False
+            await asyncio.sleep(15)
+
+        self._logger.debug("End of network discovery")
+
+    async def discovery_loop(self):
+        """
+        Handle poping of nodes in discovery stack
+        :return:
+        """
+        while self.continue_crawling():
+            try:
+                await asyncio.sleep(1)
+                peer = self._discovery_stack.pop()
+                node = self._processor.update_peer(self.currency, peer)
+                if not node:
+                    self._logger.debug("New node found : {0}".format(peer.pubkey[:5]))
+                    try:
+                        connector = NodeConnector.from_peer(self.currency, peer, self._app.parameters)
+                        node = connector.node
+                        self._processor.insert_node(connector.node)
+                        await connector.init_session()
+                        connector.refresh(manual=True)
+                        self.add_connector(connector)
+                        self.nodes_changed.emit()
+                    except InvalidNodeCurrency as e:
+                        self._logger.debug(str(e))
+                if node:
+                    try:
+                        identity = await self._identities_service.find_from_pubkey(node.pubkey)
+                        identity = await self._identities_service.load_requirements(identity)
+                        node.member = identity.member
+                        node.uid = identity.uid
+                        self._processor.update_node(node)
+                        self.nodes_changed.emit()
+                    except errors.DuniterError as e:
+                        self._logger.error(e.message)
+
+                self._app.db.commit()
+            except IndexError:
+                await asyncio.sleep(2)
+
+    def handle_new_node(self, peer):
+        key = VerifyingKey(peer.pubkey)
+        if key.verify_document(peer):
+            if len(self._discovery_stack) < 1000 \
+            and peer.signatures[0] not in [p.signatures[0] for p in self._discovery_stack]:
+                self._logger.debug("Stacking new peer document : {0}".format(peer.pubkey))
+                self._discovery_stack.append(peer)
+        else:
+            self._logger.debug("Wrong document received : {0}".format(peer.signed_raw()))
+
+    @pyqtSlot()
+    def handle_identity_change(self):
+        connector = self.sender()
+        self._processor.update_node(connector.node)
+        self.nodes_changed.emit()
+
+    @pyqtSlot()
+    def handle_error(self):
+        node = self.sender()
+        if node.state in (Node.OFFLINE, Node.CORRUPTED) and \
+                                node.last_change + 3600 < time.time():
+            node.disconnect()
+            self._processor.delete_node(node)
+            self.nodes_changed.emit()
+
+    def handle_change(self):
+        node_connector = self.sender()
+
+        if node_connector.node.state in (Node.ONLINE, Node.DESYNCED):
+            self._check_nodes_sync()
+        self.nodes_changed.emit()
+        self._processor.update_node(node_connector.node)
+
+        if node_connector.node.state == Node.ONLINE:
+            current_buid = self._processor.current_buid(self.currency)
+            self._logger.debug("{0} -> {1}".format(self._block_found.sha_hash[:10], current_buid.sha_hash[:10]))
+            if self._block_found.sha_hash != current_buid.sha_hash:
+                self._logger.debug("Latest block changed : {0}".format(current_buid.number))
+                # If new latest block is lower than the previously found one
+                # or if the previously found block is different locally
+                # than in the main chain, we declare a rollback
+                if current_buid < self._block_found \
+                   or node_connector.node.previous_buid != self._block_found:
+                    self._logger.debug("Start rollback")
+                    self._block_found = current_buid
+                    #TODO: self._blockchain_service.rollback()
+                else:
+                    self._logger.debug("Start refresh")
+                    self._block_found = current_buid
+                    asyncio.ensure_future(self._blockchain_service.handle_blockchain_progress(self._block_found))
diff --git a/src/sakia/services/sources.py b/src/sakia/services/sources.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe082a28252da85dd09e59041c64f3e3f80c8939
--- /dev/null
+++ b/src/sakia/services/sources.py
@@ -0,0 +1,47 @@
+from PyQt5.QtCore import QObject
+from duniterpy.api import bma, errors
+import logging
+from sakia.data.entities import Source
+
+
+class SourcesServices(QObject):
+    """
+    Source service is managing sources received
+    to update data locally
+    """
+    def __init__(self, currency, sources_processor, connections_processor, bma_connector):
+        """
+        Constructor the identities service
+
+        :param str currency: The currency name of the community
+        :param sakia.data.processors.SourcesProcessor sources_processor: the sources processor for given currency
+        :param sakia.data.processors.ConnectionsProcessor connections_processor: the connections processor
+        :param sakia.data.connectors.BmaConnector bma_connector: The connector to BMA API
+        """
+        super().__init__()
+        self._sources_processor = sources_processor
+        self._connections_processor = connections_processor
+        self._bma_connector = bma_connector
+        self.currency = currency
+        self._logger = logging.getLogger('sakia')
+
+    def amount(self, pubkey):
+        return self._sources_processor.amount(self.currency, pubkey)
+
+    async def refresh_sources(self):
+        connections_pubkeys = [c.pubkey for c in self._connections_processor.connections_to(self.currency)]
+        for pubkey in connections_pubkeys:
+            sources_data = await self._bma_connector.get(self.currency, bma.tx.sources,
+                                                         req_args={'pubkey': pubkey})
+
+            self._logger.debug("Found {0} sources".format(len(sources_data['sources'])))
+            self._sources_processor.drop_all_of(currency=self.currency, pubkey=pubkey)
+            for i, s in enumerate(sources_data['sources']):
+                source = Source(currency=self.currency, pubkey=pubkey,
+                                identifier=s['identifier'],
+                                type=s['type'],
+                                noffset=s['noffset'],
+                                amount=s['amount'],
+                                base=s['base'])
+                self._sources_processor.commit(source)
+                self._logger.debug("{0}/{1} sources".format(i, len(sources_data['sources'])))
diff --git a/src/sakia/services/transactions.py b/src/sakia/services/transactions.py
new file mode 100644
index 0000000000000000000000000000000000000000..c537d333bad69ffbd1450e88ebbbf0ff2fda7b85
--- /dev/null
+++ b/src/sakia/services/transactions.py
@@ -0,0 +1,141 @@
+from PyQt5.QtCore import QObject
+from sakia.data.entities.transaction import parse_transaction_doc
+from duniterpy.documents import Transaction as TransactionDoc
+from duniterpy.documents import SimpleTransaction
+from sakia.data.entities import Dividend
+from duniterpy.api import bma
+import logging
+import sqlite3
+
+
+class TransactionsService(QObject):
+    """
+    Transaction service is managing sources received
+    to update data locally
+    """
+    def __init__(self, currency, transactions_processor, dividends_processor,
+                 identities_processor, connections_processor, bma_connector):
+        """
+        Constructor the identities service
+
+        :param str currency: The currency name of the community
+        :param sakia.data.processors.IdentitiesProcessor identities_processor: the identities processor for given currency
+        :param sakia.data.processors.TransactionsProcessor transactions_processor: the transactions processor for given currency
+        :param sakia.data.processors.DividendsProcessor dividends_processor: the dividends processor for given currency
+        :param sakia.data.processors.ConnectionsProcessor connections_processor: the connections processor for given currency
+        :param sakia.data.connectors.BmaConnector bma_connector: The connector to BMA API
+        """
+        super().__init__()
+        self._transactions_processor = transactions_processor
+        self._dividends_processor = dividends_processor
+        self._identities_processor = identities_processor
+        self._connections_processor = connections_processor
+        self._bma_connector = bma_connector
+        self.currency = currency
+        self._logger = logging.getLogger('sakia')
+
+    def _parse_block(self, block_doc, txid):
+        """
+        Parse a block
+        :param duniterpy.documents.Block block_doc: The block
+        :param int txid: Latest tx id
+        :return: The list of transfers sent
+        """
+        transfers_changed = []
+        new_transfers = []
+        for tx in [t for t in self._transactions_processor.awaiting(self.currency)]:
+            if self._transactions_processor.run_state_transitions(tx, block_doc):
+                transfers_changed.append(tx)
+
+        new_transactions = [t for t in block_doc.transactions
+                            if not self._transactions_processor.find_by_hash(t.sha_hash)
+                            and SimpleTransaction.is_simple(t)]
+        connections_pubkeys = [c.pubkey for c in self._connections_processor.connections_to(self.currency)]
+        for pubkey in connections_pubkeys:
+            for (i, tx_doc) in enumerate(new_transactions):
+                tx = parse_transaction_doc(tx_doc, pubkey, block_doc.blockUID.number,  block_doc.mediantime, txid+i)
+                if tx:
+                    new_transfers.append(tx)
+                    self._transactions_processor.commit(tx)
+                else:
+                    logging.debug("Error during transfer parsing")
+
+        return transfers_changed, new_transfers
+
+    async def handle_new_blocks(self, blocks):
+        """
+        Refresh last transactions
+
+        :param list[duniterpy.documents.Block] blocks: The blocks containing data to parse
+        """
+        self._logger.debug("Refresh transactions")
+        transfers_changed = []
+        new_transfers = []
+        txid = 0
+        for block in blocks:
+            changes, new_tx = self._parse_block(block, txid)
+            txid += len(new_tx)
+            transfers_changed += changes
+            new_transfers += new_tx
+        new_dividends = await self.parse_dividends_history(blocks, new_transfers)
+        return transfers_changed, new_transfers, new_dividends
+
+    async def parse_dividends_history(self, blocks, transactions):
+        """
+        Request transactions from the network to initialize data for a given pubkey
+        :param List[duniterpy.documents.Block] blocks: the list of transactions found by tx parsing
+        :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx parsing
+        """
+        connections_pubkeys = [c.pubkey for c in self._connections_processor.connections_to(self.currency)]
+        min_block_number = blocks[0].number
+        dividends = []
+        for pubkey in connections_pubkeys:
+            history_data = await self._bma_connector.get(self.currency, bma.ud.history,
+                                                         req_args={'pubkey': pubkey})
+            block_numbers = []
+            for ud_data in history_data["history"]["history"]:
+                dividend = Dividend(currency=self.currency,
+                                    pubkey=pubkey,
+                                    block_number=ud_data["block_number"],
+                                    timestamp=ud_data["time"],
+                                    amount=ud_data["amount"],
+                                    base=ud_data["base"])
+                if dividend.block_number > min_block_number:
+                    self._logger.debug("Dividend of block {0}".format(dividend.block_number))
+                    block_numbers.append(dividend.block_number)
+                    if self._dividends_processor.commit(dividend):
+                        dividends.append(dividend)
+
+            for tx in transactions:
+                txdoc = TransactionDoc.from_signed_raw(tx.raw)
+                for input in txdoc.inputs:
+                    if input.source == "D" and input.origin_id == pubkey and input.index not in block_numbers:
+                        block = next((b for b in blocks if b.number == input.index))
+                        dividend = Dividend(currency=self.currency,
+                                            pubkey=pubkey,
+                                            block_number=input.index,
+                                            timestamp=block.mediantime,
+                                            amount=block.ud,
+                                            base=block.unit_base)
+                        self._logger.debug("Dividend of block {0}".format(dividend.block_number))
+                        if self._dividends_processor.commit(dividend):
+                            dividends.append(dividend)
+        return dividends
+
+    def transfers(self, pubkey):
+        """
+        Get all transfers from or to a given pubkey
+        :param str pubkey:
+        :return: the list of Transaction entities
+        :rtype: List[sakia.data.entities.Transaction]
+        """
+        return self._transactions_processor.transfers(self.currency, pubkey)
+
+    def dividends(self, pubkey):
+        """
+        Get all dividends from or to a given pubkey
+        :param str pubkey:
+        :return: the list of Dividend entities
+        :rtype: List[sakia.data.entities.Dividend]
+        """
+        return self._dividends_processor.dividends(self.currency, pubkey)
\ No newline at end of file
diff --git a/src/sakia/tests/__init__.py b/src/sakia/tests/__init__.py
deleted file mode 100644
index a7d4d00287516418923b1e4dbef7ff519ded3ed1..0000000000000000000000000000000000000000
--- a/src/sakia/tests/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .quamash_utils import QuamashTest
\ No newline at end of file
diff --git a/src/sakia/tests/functional/certification/test_certification.py b/src/sakia/tests/functional/certification/test_certification.py
deleted file mode 100644
index d1615149433bb51c866e5cc7aeada594339db847..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/certification/test_certification.py
+++ /dev/null
@@ -1,90 +0,0 @@
-import sys
-import unittest
-import asyncio
-import time
-import logging
-import aiohttp
-from duniterpy.documents.peer import BMAEndpoint
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, QApplication
-from PyQt5.QtCore import QLocale, Qt
-from PyQt5.QtTest import QTest
-from sakia.tests.mocks.bma import init_new_community
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.certification import CertificationDialog, Ui_CertificationDialog
-from sakia.gui.password_asker import PasswordAskerDialog
-from sakia.core.app import Application
-from sakia.core import Account, Community, Wallet
-from sakia.core.net import Network, Node
-from sakia.core.net.api.bma.access import BmaAccess
-from sakia.tests import QuamashTest
-from duniterpy.key import ScryptParams
-
-
-class TestCertificationDialog(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_new_community = init_new_community.get_mock(self.lp)
-
-        self.endpoint = BMAEndpoint("", "127.0.0.1", "", 50010)
-        self.node = Node(self.mock_new_community.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",
-                               ScryptParams(4096, 16, 1),
-                               "john", [self.community], [self.wallet], [], self.identities_registry)
-        self.account.notifications['warning_certifying_first_time'] = False
-        self.password_asker = PasswordAskerDialog(self.account)
-        self.password_asker.password = "testsakia"
-        self.password_asker.remember = True
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_certification_init_community(self):
-        time.sleep(2)
-        certification_dialog = CertificationDialog(self.application,
-                                                   self.account,
-                                                   self.password_asker,
-                                                   QDialog(),
-                                                    Ui_CertificationDialog())
-
-        async def open_dialog(certification_dialog):
-            srv, port, url = await self.mock_new_community.create_server()
-            self.addCleanup(srv.close)
-            await certification_dialog.async_exec()
-            await self.mock_new_community.close()
-
-        def close_dialog():
-            if certification_dialog.widget.isVisible():
-                certification_dialog.widget.close()
-
-        async def exec_test():
-            await asyncio.sleep(1)
-            QTest.mouseClick(certification_dialog.ui.radio_pubkey, Qt.LeftButton)
-            QTest.keyClicks(certification_dialog.ui.edit_pubkey, "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
-            QTest.mouseClick(certification_dialog.ui.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton)
-            await asyncio.sleep(3)
-            topWidgets = QApplication.topLevelWidgets()
-            for w in topWidgets:
-                if type(w) is QMessageBox:
-                    QTest.keyClick(w, Qt.Key_Enter)
-
-        self.lp.call_later(10, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(open_dialog(certification_dialog))
diff --git a/src/sakia/tests/functional/identities_tab/test_identities_table.py b/src/sakia/tests/functional/identities_tab/test_identities_table.py
deleted file mode 100644
index 55389c795911351c0c8732a8f1d95774951610c7..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/identities_tab/test_identities_table.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import sys
-import unittest
-import asyncio
-import aiohttp
-import logging
-import time
-from PyQt5.QtCore import QLocale, Qt
-from PyQt5.QtTest import QTest
-
-from sakia.tests.mocks.bma import nice_blockchain
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.identities_tab import IdentitiesTabWidget
-from sakia.gui.password_asker import PasswordAskerDialog
-from sakia.core.app import Application
-from sakia.core import Account, Community, Wallet
-from sakia.core.net import Network, Node
-from sakia.core.net.api.bma.access import BmaAccess
-from sakia.tests import QuamashTest
-from duniterpy.key import ScryptParams
-
-
-class TestIdentitiesTable(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",
-                               ScryptParams(4096, 16, 1),
-                               "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_search_identity_found(self):
-        time.sleep(2)
-        identities_tab = IdentitiesTabWidget(self.application)
-        future = asyncio.Future()
-
-        def open_widget():
-            identities_tab.widget.show()
-            return future
-
-        def close_dialog():
-            if identities_tab.widget.isVisible():
-                identities_tab.widget.close()
-            future.set_result(True)
-
-        async def exec_test():
-            srv, port, url = await self.mock_nice_blockchain.create_server()
-            self.addCleanup(srv.close)
-
-            identities_tab.change_account(self.account, self.password_asker)
-            identities_tab.change_community(self.community)
-            await asyncio.sleep(1)
-
-            QTest.keyClicks(identities_tab.ui.edit_textsearch, "doe")
-            QTest.mouseClick(identities_tab.ui.button_search, Qt.LeftButton)
-            await asyncio.sleep(2)
-
-            self.assertEqual(identities_tab.ui.table_identities.model().rowCount(), 1)
-            await self.mock_nice_blockchain.close()
-            self.lp.call_soon(close_dialog)
-
-        asyncio.ensure_future(exec_test())
-        self.lp.call_later(15, close_dialog)
-        self.lp.run_until_complete(open_widget())
diff --git a/src/sakia/tests/functional/main_window/test_main_window_dialogs.py b/src/sakia/tests/functional/main_window/test_main_window_dialogs.py
deleted file mode 100644
index ed9157a9816b196643e7ed133ec8d2a053c53779..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/main_window/test_main_window_dialogs.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import unittest
-import asyncio
-from PyQt5.QtWidgets import QDialog, QFileDialog
-from PyQt5.QtCore import QLocale, QTimer
-from sakia.gui.mainwindow import MainWindow
-from sakia.core.app import Application
-from sakia.tests import QuamashTest
-from sakia.core.registry.identities import IdentitiesRegistry
-
-
-class MainWindowDialogsTest(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        self.application = Application(self.qapplication, self.lp, IdentitiesRegistry())
-        self.main_window = MainWindow.startup(self.application)
-
-    def tearDown(self):
-        self.main_window.widget.close()
-        self.tearDownQuamash()
-
-    def test_action_about(self):
-        #select menu
-        self.main_window.ui.actionAbout.trigger()
-        widgets = self.qapplication.topLevelWidgets()
-        for widget in widgets:
-            if isinstance(widget, QDialog):
-                if widget.isVisible():
-                    self.assertEqual('AboutPopup', widget.objectName())
-                    widget.close()
-                    break
-
-    def test_action_add_account(self):
-        async def exec_test():
-            self.main_window.ui.action_add_account.trigger()
-            await asyncio.sleep(1)
-            widgets = self.qapplication.topLevelWidgets()
-            for widget in widgets:
-                if isinstance(widget, QDialog):
-                    if widget.isVisible():
-                        try:
-                            self.assertEqual('AccountConfigurationDialog', widget.objectName())
-                            break
-                        finally:
-                            widget.close()
-        self.lp.run_until_complete(exec_test())
-
-    # fixme: require a app.current_account fixture
-    # def test_action_configure_account(self):
-    #     # asynchronous test, cause dialog is waiting user response
-    #     QTimer.singleShot(0, self._async_test_action_configure_account)
-    #     # select about menu
-    #     self.main_window.action_configure_parameters.trigger()
-    #
-    # def _async_test_action_configure_account(self):
-    #     widgets = qapplication.topLevelWidgets()
-    #     for widget in widgets:
-    #         if isinstance(widget, PyQt5.QtWidgets.QDialog):
-    #             self.assertEqual(widget.objectName(), 'AccountConfigurationDialog')
-    #             self.assertEqual(widget.isVisible(), True)
-    #             widget.close()
-    #             break
-    #
-    def test_action_export_account(self):
-        #select menu
-        self.main_window.ui.action_export.trigger()
-
-        widgets = self.qapplication.topLevelWidgets()
-        for widget in widgets:
-            if isinstance(widget, QFileDialog):
-                if widget.isVisible():
-                    try:
-                        self.assertEqual('ExportFileDialog', widget.objectName())
-                        break
-                    finally:
-                        widget.close()
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/src/sakia/tests/functional/main_window/test_main_window_menus.py b/src/sakia/tests/functional/main_window/test_main_window_menus.py
deleted file mode 100644
index 7022217db7230392c1725c0140f58afe3af88245..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/main_window/test_main_window_menus.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import sys
-import unittest
-import os
-import asyncio
-import quamash
-from PyQt5.QtWidgets import QMenu
-from PyQt5.QtCore import QLocale
-from sakia.gui.mainwindow import MainWindow
-from sakia.core.app import Application
-from sakia.tests import QuamashTest
-
-class MainWindowMenusTest(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        self.application = Application(self.qapplication, self.lp, None)
-        self.main_window = MainWindow.startup(self.application)
-
-    def tearDown(self):
-        self.main_window.widget.close()
-        self.tearDownQuamash()
-
-    def test_menubar(self):
-        children = self.main_window.ui.menubar.children()
-        menus = []
-        """:type: list[QMenu]"""
-        for child in children:
-            if isinstance(child, QMenu):
-                menus.append(child)
-        self.assertEqual(len(menus), 4)
-        self.assertEqual(menus[0].objectName(), 'menu_file')
-        self.assertEqual(menus[1].objectName(), 'menu_account')
-        self.assertEqual(menus[2].objectName(), 'menu_help')
-        self.assertEqual(menus[3].objectName(), 'menu_duniter')
-
-    def test_menu_account(self):
-        actions = self.main_window.ui.menu_account.actions()
-        """:type: list[QAction]"""
-        self.assertEqual('action_configure_parameters', actions[1].objectName())
-        self.assertEqual('action_add_account', actions[2].objectName())
-        self.assertEqual('actionCertification', actions[4].objectName())
-        self.assertEqual('actionTransfer_money', actions[5].objectName())
-        self.assertEqual('action_add_a_contact', actions[7].objectName())
-        self.assertEqual(11, len(actions))
-
-    def test_menu_actions(self):
-        actions = self.main_window.ui.menu_help.actions()
-        """:type: list[QAction]"""
-        self.assertEqual(len(actions), 1)
-        self.assertEqual(actions[0].objectName(), 'actionAbout')
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/src/sakia/tests/functional/preferences/test_preferences_dialog.py b/src/sakia/tests/functional/preferences/test_preferences_dialog.py
deleted file mode 100644
index f2f1ce3bbbc99572818c07a34c4530fe465bf325..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/preferences/test_preferences_dialog.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import sys
-import unittest
-import asyncio
-import quamash
-import logging
-from PyQt5.QtCore import QLocale
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.preferences import PreferencesDialog
-from sakia.core.app import Application
-from sakia.tests import QuamashTest
-from duniterpy.api import bma
-
-
-class TestPreferencesDialog(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-        self.identities_registry = IdentitiesRegistry({})
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_preferences_default(self):
-        self.application = Application(self.qapplication, self.lp, self.identities_registry)
-        preferences_dialog = PreferencesDialog(self.application)
-        self.assertEqual(preferences_dialog.combo_account.currentText(),
-                         self.application.preferences['account'])
-        self.assertEqual(preferences_dialog.combo_language.currentText(),
-                         self.application.preferences['lang'])
-        self.assertEqual(preferences_dialog.combo_referential.currentIndex(),
-                         self.application.preferences['ref'])
-        self.assertEqual(preferences_dialog.checkbox_expertmode.isChecked(),
-                         self.application.preferences['expert_mode'])
-        self.assertEqual(preferences_dialog.checkbox_maximize.isChecked(),
-                         self.application.preferences['maximized'])
-        self.assertEqual(preferences_dialog.checkbox_notifications.isChecked(),
-                         self.application.preferences['notifications'])
-        self.assertEqual(preferences_dialog.checkbox_proxy.isChecked(),
-                         self.application.preferences['enable_proxy'])
-        self.assertEqual(preferences_dialog.edit_proxy_address.text(),
-                         self.application.preferences['proxy_address'])
-        self.assertEqual(preferences_dialog.spinbox_proxy_port.value(),
-                         self.application.preferences['proxy_port'])
-        self.assertEqual(preferences_dialog.checkbox_international_system.isChecked(),
-                         self.application.preferences['international_system_of_units'])
-        self.assertEqual(preferences_dialog.checkbox_auto_refresh.isChecked(),
-                         self.application.preferences['auto_refresh'])
-
-if __name__ == '__main__':
-    logging.basicConfig(stream=sys.stderr)
-    logging.getLogger().setLevel(logging.DEBUG)
-    unittest.main()
diff --git a/src/sakia/tests/functional/process_cfg_account/test_add_account.py b/src/sakia/tests/functional/process_cfg_account/test_add_account.py
deleted file mode 100644
index d5afff6e96935f24ee8a1015be9d3995e42cab8b..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/process_cfg_account/test_add_account.py
+++ /dev/null
@@ -1,95 +0,0 @@
-import sys
-import unittest
-import asyncio
-import logging
-from duniterpy.key import ScryptParams
-from PyQt5.QtCore import QLocale, Qt
-from PyQt5.QtTest import QTest
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.process_cfg_account import ProcessConfigureAccount
-from sakia.gui.password_asker import PasswordAskerDialog
-from sakia.core.app import Application
-from sakia.core.account import Account
-from sakia.tests import QuamashTest
-
-
-class ProcessAddCommunity(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
-        # Salt/password : "testsakia/testsakia"
-        # Pubkey : 7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ
-        self.account = Account("testsakia", "B7J4sopyfqzi3uh4Gzsdnp1XHc87NaxY7rqW2exgivCa",
-                               ScryptParams(4096, 16, 1),
-                               "test", [], [], [], 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_create_account(self):
-        process_account = ProcessConfigureAccount(self.application,
-                                                    None)
-
-        def close_dialog():
-            if process_account.isVisible():
-                process_account.close()
-
-        async def exec_test():
-            QTest.keyClicks(process_account.edit_account_name, "test")
-            self.assertEqual(process_account.stacked_pages.currentWidget(),
-                             process_account.page_init,
-                             msg="Current widget : {0}".format(process_account.stacked_pages.currentWidget().objectName()))
-            QTest.mouseClick(process_account.button_next, Qt.LeftButton)
-            await asyncio.sleep(1)
-
-            self.assertEqual(process_account.stacked_pages.currentWidget(),
-                             process_account.page_gpg,
-                             msg="Current widget : {0}".format(process_account.stacked_pages.currentWidget().objectName()))
-            QTest.keyClicks(process_account.edit_salt, "testsakia")
-            self.assertFalse(process_account.button_next.isEnabled())
-            self.assertFalse(process_account.button_generate.isEnabled())
-            QTest.keyClicks(process_account.edit_password, "testsakia")
-            self.assertFalse(process_account.button_next.isEnabled())
-            self.assertFalse(process_account.button_generate.isEnabled())
-            QTest.keyClicks(process_account.edit_password_repeat, "wrongpassword")
-            self.assertFalse(process_account.button_next.isEnabled())
-            self.assertFalse(process_account.button_generate.isEnabled())
-            process_account.edit_password_repeat.setText("")
-            QTest.keyClicks(process_account.edit_password_repeat, "testsakia")
-            self.assertTrue(process_account.button_next.isEnabled())
-            self.assertTrue(process_account.button_generate.isEnabled())
-            QTest.mouseClick(process_account.button_generate, Qt.LeftButton)
-            self.assertEqual(process_account.label_info.text(),
-                             "B7J4sopyfqzi3uh4Gzsdnp1XHc87NaxY7rqW2exgivCa")
-            QTest.mouseClick(process_account.button_next, Qt.LeftButton)
-            await asyncio.sleep(1)
-
-            self.assertEqual(process_account.stacked_pages.currentWidget(),
-                             process_account.page__communities,
-                             msg="Current widget : {0}".format(process_account.stacked_pages.currentWidget().objectName()))
-            process_account.password_asker.password = "testsakia"
-            process_account.password_asker.remember = True
-            await asyncio.sleep(1)
-            QTest.mouseClick(process_account.button_next, Qt.LeftButton)
-            self.assertEqual(len(self.application.accounts), 1)
-            await asyncio.sleep(0.1)
-            self.assertEqual(self.application.current_account.name, "test")
-            self.assertEqual(self.application.preferences['account'], "test")
-            self.assertEqual(len(self.application.current_account.wallets), 1)
-            await asyncio.sleep(1)
-
-        self.lp.call_later(10, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(process_account.async_exec())
-
-if __name__ == '__main__':
-    logging.basicConfig( stream=sys.stderr )
-    logging.getLogger().setLevel( logging.DEBUG )
-    unittest.main()
diff --git a/src/sakia/tests/functional/process_cfg_community/test_add_community.py b/src/sakia/tests/functional/process_cfg_community/test_add_community.py
deleted file mode 100644
index fc47726728f4f0537b8fe8b5a7797b31ccc1f7db..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/process_cfg_community/test_add_community.py
+++ /dev/null
@@ -1,238 +0,0 @@
-import sys
-import unittest
-import asyncio
-import quamash
-import logging
-import time
-from PyQt5.QtWidgets import QDialog
-from PyQt5.QtCore import QLocale, Qt
-from PyQt5.QtTest import QTest
-from sakia.tests.mocks.bma import new_blockchain, nice_blockchain
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.process_cfg_community import ProcessConfigureCommunity
-from sakia.gui.password_asker import PasswordAskerDialog
-from sakia.core.app import Application
-from sakia.core.account import Account
-from sakia.tests import QuamashTest
-from duniterpy.key import ScryptParams
-
-
-class ProcessAddCommunity(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
-        # Salt/password : "testsakia/testsakia"
-        # Pubkey : 7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ
-        self.account = Account("testsakia", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                               ScryptParams(4096, 16, 1),
-                               "john", [], [], [], 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_register_community_empty_blockchain(self):
-        mock = new_blockchain.get_mock(self.lp)
-        time.sleep(2)
-        process_community = ProcessConfigureCommunity(self.application,
-                                                    self.account,
-                                                    None, self.password_asker)
-
-        def close_dialog():
-            if process_community.isVisible():
-                process_community.close()
-
-        async def exec_test():
-            srv, port, url = await mock.create_server()
-            self.addCleanup(srv.close)
-            await asyncio.sleep(1)
-            QTest.mouseClick(process_community.lineedit_server, Qt.LeftButton)
-            QTest.keyClicks(process_community.lineedit_server, "127.0.0.1")
-            QTest.mouseDClick(process_community.spinbox_port, Qt.LeftButton)
-            process_community.spinbox_port.setValue(port)
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_node,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
-            self.assertEqual(process_community.spinbox_port.value(), port)
-            QTest.mouseClick(process_community.button_register, Qt.LeftButton)
-            await asyncio.sleep(1)
-            self.assertEqual(process_community.label_error.text(), "Broadcasting identity...")
-            await asyncio.sleep(1)
-
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_add_nodes,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            await mock.close()
-            QTest.mouseClick(process_community.button_next, Qt.LeftButton)
-
-        self.lp.call_later(15, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(process_community.async_exec())
-        self.assertEqual(process_community.result(), QDialog.Accepted)
-
-    def test_connect_community_empty_blockchain(self):
-        mock = new_blockchain.get_mock(self.lp)
-        time.sleep(2)
-        process_community = ProcessConfigureCommunity(self.application,
-                                                    self.account,
-                                                    None, self.password_asker)
-
-        def close_dialog():
-            if process_community.isVisible():
-                process_community.close()
-
-        async def exec_test():
-            srv, port, url = await mock.create_server()
-            self.addCleanup(srv.close)
-
-            await asyncio.sleep(1)
-            QTest.mouseClick(process_community.lineedit_server, Qt.LeftButton)
-            QTest.keyClicks(process_community.lineedit_server, "127.0.0.1")
-            QTest.mouseDClick(process_community.spinbox_port, Qt.LeftButton)
-            process_community.spinbox_port.setValue(port)
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_node,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
-            self.assertEqual(process_community.spinbox_port.value(), port)
-            QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
-            await asyncio.sleep(2)
-            self.assertEqual(mock.get_request(0).method, 'GET')
-            self.assertEqual(mock.get_request(0).url, '/network/peering')
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_node,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            self.assertEqual(process_community.label_error.text(), "Could not find your identity on the network.")
-            await mock.close()
-            process_community.close()
-
-        self.lp.call_later(15, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(process_community.async_exec())
-
-    def test_connect_community_wrong_pubkey(self):
-        mock = nice_blockchain.get_mock(self.lp)
-        time.sleep(2)
-        self.account.pubkey = "wrong_pubkey"
-        process_community = ProcessConfigureCommunity(self.application,
-                                                    self.account,
-                                                    None, self.password_asker)
-
-        def close_dialog():
-            if process_community.isVisible():
-                process_community.close()
-
-        async def exec_test():
-            srv, port, url = await mock.create_server()
-            self.addCleanup(srv.close)
-            await asyncio.sleep(1)
-            QTest.mouseClick(process_community.lineedit_server, Qt.LeftButton)
-            QTest.keyClicks(process_community.lineedit_server, "127.0.0.1")
-            QTest.mouseDClick(process_community.spinbox_port, Qt.LeftButton)
-            process_community.spinbox_port.setValue(port)
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_node,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
-            self.assertEqual(process_community.spinbox_port.value(), port)
-            QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
-            await asyncio.sleep(1)
-            self.assertEqual(mock.get_request(0).method, 'GET')
-            self.assertEqual(mock.get_request(0).url, '/network/peering')
-            self.assertEqual(mock.get_request(1).method, 'GET')
-            self.assertEqual(mock.get_request(1).url,
-                             '/wot/certifiers-of/wrong_pubkey')
-            self.assertEqual(process_community.label_error.text(), """Your pubkey or UID is different on the network.
-Yours : wrong_pubkey, the network : 7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ""")
-            await mock.close()
-            process_community.close()
-
-        self.lp.call_later(15, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(process_community.async_exec())
-        self.assertEqual(process_community.result(), QDialog.Rejected)
-
-    def test_connect_community_wrong_uid(self):
-        mock = nice_blockchain.get_mock(self.lp)
-        time.sleep(2)
-        self.account.name = "wrong_uid"
-        process_community = ProcessConfigureCommunity(self.application,
-                                                    self.account,
-                                                    None, self.password_asker)
-
-        def close_dialog():
-            if process_community.isVisible():
-                process_community.close()
-
-        async def exec_test():
-            srv, port, url = await mock.create_server()
-            self.addCleanup(srv.close)
-            await asyncio.sleep(1)
-            QTest.mouseClick(process_community.lineedit_server, Qt.LeftButton)
-            QTest.keyClicks(process_community.lineedit_server, "127.0.0.1")
-            QTest.mouseDClick(process_community.spinbox_port, Qt.LeftButton)
-            process_community.spinbox_port.setValue(port)
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_node,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
-            self.assertEqual(process_community.spinbox_port.value(), port)
-            QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
-            await asyncio.sleep(1)
-            self.assertEqual(mock.get_request(0).method, 'GET')
-            self.assertEqual(mock.get_request(0).url, '/network/peering')
-            self.assertEqual(process_community.label_error.text(), """Your pubkey or UID is different on the network.
-Yours : wrong_uid, the network : john""")
-            await mock.close()
-            process_community.close()
-
-        self.lp.call_later(15, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(process_community.async_exec())
-        self.assertEqual(process_community.result(), QDialog.Rejected)
-
-    def test_connect_community_success(self):
-        mock = nice_blockchain.get_mock(self.lp)
-        time.sleep(2)
-        process_community = ProcessConfigureCommunity(self.application,
-                                                    self.account,
-                                                    None, self.password_asker)
-
-        def close_dialog():
-            if process_community.isVisible():
-                process_community.close()
-
-        async def exec_test():
-            srv, port, url = await mock.create_server()
-            QTest.mouseClick(process_community.lineedit_server, Qt.LeftButton)
-            QTest.keyClicks(process_community.lineedit_server, "127.0.0.1")
-            QTest.mouseDClick(process_community.spinbox_port, Qt.LeftButton)
-            process_community.spinbox_port.setValue(port)
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_node,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
-            self.assertEqual(process_community.spinbox_port.value(), port)
-            QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
-            await asyncio.sleep(1)
-            self.assertEqual(mock.get_request(0).method, 'GET')
-            self.assertEqual(mock.get_request(0).url, '/network/peering')
-            self.assertEqual(mock.get_request(1).method, 'GET')
-            self.assertEqual(mock.get_request(1).url,
-                             '/wot/certifiers-of/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ')
-            self.assertEqual(process_community.stacked_pages.currentWidget(),
-                             process_community.page_add_nodes,
-                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
-            await mock.close()
-            QTest.mouseClick(process_community.button_next, Qt.LeftButton)
-
-        self.lp.call_later(15, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(process_community.async_exec())
\ No newline at end of file
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 3d5e010a7c47d5d43a03fdcd1fde9a655fb6b64c..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/transfer/test_transfer.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import sys
-import unittest
-import asyncio
-import aiohttp
-import time
-import logging
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, QApplication
-from PyQt5.QtCore import QLocale, Qt
-from PyQt5.QtTest import QTest
-
-from sakia.tests.mocks.bma import nice_blockchain
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.transfer import TransferMoneyDialog
-from sakia.gui.password_asker import PasswordAskerDialog
-from sakia.core.app import Application
-from sakia.core import Account, Community, Wallet
-from sakia.core.net import Network, Node
-from sakia.core.net.api.bma.access import BmaAccess
-from sakia.tests import QuamashTest
-from duniterpy.key import ScryptParams
-
-
-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",
-                               ScryptParams(4096, 16, 1),
-                               "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))
diff --git a/src/sakia/tests/functional/wot_tab/__init__.py b/src/sakia/tests/functional/wot_tab/__init__.py
deleted file mode 100644
index 39ab2a0b56350baad834cb7fb0cfecb8223e1fcd..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/wot_tab/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__author__ = 'inso'
diff --git a/src/sakia/tests/functional/wot_tab/test_wot_tab.py b/src/sakia/tests/functional/wot_tab/test_wot_tab.py
deleted file mode 100644
index 90f1ace2e479bdc96ffbc11db706f3c692678d23..0000000000000000000000000000000000000000
--- a/src/sakia/tests/functional/wot_tab/test_wot_tab.py
+++ /dev/null
@@ -1,86 +0,0 @@
-import asyncio
-import logging
-import sys
-import time
-import aiohttp
-import unittest
-
-from PyQt5.QtCore import QLocale
-from duniterpy.key import ScryptParams
-from sakia.core import Account, Community, Wallet
-from sakia.core.app import Application
-from sakia.core.net import Network, Node
-from sakia.core.net.api.bma.access import BmaAccess
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.graphs.wot_tab import WotTabWidget
-from sakia.gui.password_asker import PasswordAskerDialog
-from sakia.tests import QuamashTest
-from sakia.tests.mocks.bma import nice_blockchain
-
-
-class TestWotTab(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",
-                               ScryptParams(4096, 16, 1),
-                               "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_empty_wot_tab(self):
-        wot_tab = WotTabWidget(self.application)
-        future = asyncio.Future()
-
-        def open_widget():
-            wot_tab.widget.show()
-            return future
-
-        async def async_open_widget():
-            srv, port, url = await self.mock_nice_blockchain.create_server()
-            self.addCleanup(srv.close)
-            await open_widget()
-            await self.mock_nice_blockchain.close()
-
-        def close_dialog():
-            if wot_tab.widget.isVisible():
-                wot_tab.widget.close()
-            future.set_result(True)
-
-        async def exec_test():
-            await asyncio.sleep(1)
-            self.assertTrue(wot_tab.widget.isVisible())
-            self.lp.call_soon(close_dialog)
-
-        asyncio.ensure_future(exec_test())
-        self.lp.call_later(15, close_dialog)
-        self.lp.run_until_complete(async_open_widget())
-
-if __name__ == '__main__':
-    logging.basicConfig( stream=sys.stderr )
-    logging.getLogger().setLevel( logging.DEBUG )
-    unittest.main()
diff --git a/src/sakia/tests/mocks/bma/__init__.py b/src/sakia/tests/mocks/bma/__init__.py
deleted file mode 100644
index 7e4cdeb7cb59ef2876f24361d303fcc281ec4ef8..0000000000000000000000000000000000000000
--- a/src/sakia/tests/mocks/bma/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__author__ = 'ggoinvic'
diff --git a/src/sakia/tests/mocks/bma/corrupted.py b/src/sakia/tests/mocks/bma/corrupted.py
deleted file mode 100644
index a147e884635086bb3ded3ee2f3d76008c4939b9f..0000000000000000000000000000000000000000
--- a/src/sakia/tests/mocks/bma/corrupted.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import json
-from ..server import MockServer
-
-bma_memberships_empty_array = {
-    "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-    "uid": "john",
-    "sigDate": 123456789,
-    "memberships": [ ]
-}
-
-
-bma_null_data = {
-  "certifications": [
-    {
-      "written": {
-      },
-    },
-    {
-      "written": None,
-    }
-  ]
-}
-
-def get_mock(loop):
-    mock = MockServer(loop)
-
-    mock.add_route('GET', '/blockchain/memberships/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_memberships_empty_array)
-
-    return mock
diff --git a/src/sakia/tests/mocks/bma/init_new_community.py b/src/sakia/tests/mocks/bma/init_new_community.py
deleted file mode 100644
index af732fd1caab4b2b26a35dc3714e35cd47ff1a52..0000000000000000000000000000000000000000
--- a/src/sakia/tests/mocks/bma/init_new_community.py
+++ /dev/null
@@ -1,121 +0,0 @@
-from ..server import MockServer
-from duniterpy.api import errors
-
-bma_lookup_test_john = {
-    "partial": False,
-    "results": [
-        {
-            "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-            "uids": [
-                {
-                    "uid": "john",
-                    "meta": {
-                        "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
-                    },
-                    "self": "ZrHK0cCqrxWReROK0ciiSb45+dRphJa68qFaSjdve8bBdnGAu7+DIu0d+u/fXrNRXuObihOKMBIawaIVPNHqDw==",
-                    "others": [],
-                    "revocation_sig": "CTmlh3tO4B8f8IbL8iDy5ZEr3jZDcxkPmDmRPQY74C39MRLXi0CKUP+oFzTZPYmyUC7fZrUXrb3LwRKWw1jEBQ==",
-                    "revoked": False,
-                }
-            ],
-            "signed": []
-        }
-    ]
-}
-
-bma_lookup_test_doe = {
-    "partial": False,
-    "results": [
-        {
-            "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-            "uids": [
-                {
-                    "uid": "doe",
-                    "meta": {
-                        "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
-                    },
-                    "self": "cIkHPQQ5+xTb4cKWv85rcYcZT+E3GDtX8B2nCK9Vs12p2Yz4bVaZiMvBBwisAAy2WBOaqHS3ydpXGtADchOICw==",
-                    "others": [],
-                    "revocation_sig": "CTmlh3tO4B8f8IbL8iDy5ZEr3jZDcxkPmDmRPQY74C39MRLXi0CKUP+oFzTZPYmyUC7fZrUXrb3LwRKWw1jEBQ==",
-                    "revoked": False,
-                }
-            ],
-            "signed": []
-        }
-    ]
-}
-
-bma_lookup_test_patrick = {
-    "partial": False,
-    "results": [
-        {
-            "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-            "uids": [
-                {
-                    "uid": "patrick",
-                    "meta": {
-                        "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
-                    },
-                    "self": "QNX2HDAxcHawc47TnMqb5/ou2lwa+zYOyeNk0a52dQDJX/NWmeTzGfTjdCtjpXmSCuPSg0F1mOnLQVd60xAzDA==",
-                    "others": [],
-                    "revocation_sig": "CTmlh3tO4B8f8IbL8iDy5ZEr3jZDcxkPmDmRPQY74C39MRLXi0CKUP+oFzTZPYmyUC7fZrUXrb3LwRKWw1jEBQ==",
-                    "revoked": False,
-                }
-            ],
-            "signed": []
-        }
-    ]
-}
-
-bma_parameters = {
-    "currency": "test_currency",
-    "c": 0.1,
-    "dt": 86400,
-    "ud0": 100,
-    "sigPeriod": 600,
-    "sigValidity": 2629800,
-    "sigQty": 3,
-    "xpercent": 0.9,
-    "sigStock": 10,
-    "sigWindow": 1000,
-    "msValidity": 2629800,
-    "stepMax": 3,
-    "medianTimeBlocks": 11,
-    "avgGenTime": 600,
-    "dtDiffEval": 20,
-    "blocksRot": 144,
-    "percentRot": 0.67
-}
-
-def get_mock(loop):
-    mock = MockServer(loop)
-
-    mock.add_route('GET', '/blockchain/parameters', bma_parameters, 200)
-
-    mock.add_route('GET', '/blockchain/block/0', {'ucode': errors.BLOCK_NOT_FOUND,
-                                                  "message": "Block not found"}, 404)
-
-    mock.add_route('GET', '/blockchain/current', {'ucode': errors.NO_CURRENT_BLOCK,
-                                                  'message': "Block not found"}, 404)
-
-    mock.add_route('GET', '/wot/certifiers-of/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID,
-                    'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/lookup/john', bma_lookup_test_john, 200)
-
-    mock.add_route('GET', '/wot/lookup/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_lookup_test_john, 200)
-
-    mock.add_route('GET', '/wot/lookup/doe', bma_lookup_test_doe, 200)
-
-    mock.add_route('GET', '/wot/lookup/FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn', bma_lookup_test_doe, 200)
-
-    mock.add_route('GET', '/wot/lookup/patrick', bma_lookup_test_patrick, 200)
-
-    mock.add_route('GET', '/wot/lookup/FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn', bma_lookup_test_patrick, 200)
-
-    mock.add_route('POST', '/wot/add', {}, 200)
-
-    mock.add_route('POST', '/wot/certify', {}, 200)
-
-    return mock
diff --git a/src/sakia/tests/mocks/bma/new_blockchain.py b/src/sakia/tests/mocks/bma/new_blockchain.py
deleted file mode 100644
index fc83d1241d29f383c27913c534ad744b758605a9..0000000000000000000000000000000000000000
--- a/src/sakia/tests/mocks/bma/new_blockchain.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from ..server import MockServer
-from duniterpy.api import errors
-
-bma_wot_add = {
-  "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-  "uids": [
-    {
-      "uid": "test",
-      "meta": {
-        "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
-      },
-      "self": "J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci",
-      "others": [
-      ],
-    "revocation_sig": "CTmlh3tO4B8f8IbL8iDy5ZEr3jZDcxkPmDmRPQY74C39MRLXi0CKUP+oFzTZPYmyUC7fZrUXrb3LwRKWw1jEBQ==",
-    "revoked": False,
-    }
-  ]
-}
-
-bma_parameters = {
-    "currency": "test_currency",
-    "c": 0.1,
-    "dt": 86400,
-    "ud0": 100,
-    "sigPeriod": 600,
-    "sigValidity": 2629800,
-    "sigQty": 3,
-    "xpercent": 0.9,
-    "sigStock": 10,
-    "sigWindow": 1000,
-    "msValidity": 2629800,
-    "stepMax": 3,
-    "medianTimeBlocks": 11,
-    "avgGenTime": 600,
-    "dtDiffEval": 20,
-    "blocksRot": 144,
-    "percentRot": 0.67
-}
-
-def get_mock(loop):
-    mock = MockServer(loop)
-
-    mock.add_route('GET', '/blockchain/parameters', bma_parameters, 200)
-
-    mock.add_route('GET', '/blockchain/block/0', {'ucode': errors.BLOCK_NOT_FOUND,
-                                                  'message': "Block not found"}, 404)
-
-    mock.add_route('GET', '/blockchain/current', {'ucode': errors.NO_CURRENT_BLOCK,
-                                                  'message': "Block not found"}, 404)
-
-    mock.add_route('GET', '/wot/certifiers-of/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID,
-                    'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/lookup/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ',
-                   {'ucode': errors.NO_MATCHING_IDENTITY,
-                    'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/lookup/john',
-                   {'ucode': errors.NO_MATCHING_IDENTITY,
-                    'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/certifiers-of/john',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/lookup/doe',
-                   {'ucode': errors.NO_MATCHING_IDENTITY,
-                    'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/certifiers-of/doe',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('POST', '/wot/add', bma_wot_add, 200)
-
-    mock.add_route('POST', '/wot/certify', {}, 200)
-    return mock
diff --git a/src/sakia/tests/mocks/bma/nice_blockchain.py b/src/sakia/tests/mocks/bma/nice_blockchain.py
deleted file mode 100644
index b8ad5e293d29b5b3ddab20b05c942799ba4072f6..0000000000000000000000000000000000000000
--- a/src/sakia/tests/mocks/bma/nice_blockchain.py
+++ /dev/null
@@ -1,523 +0,0 @@
-from ..server import MockServer
-from duniterpy.api import errors
-
-bma_lookup_john = {
-    "partial": False,
-    "results": [
-        {
-            "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-            "uids": [
-                {
-                    "uid": "john",
-                    "meta": {
-                        "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
-                    },
-                    "revocation_sig": "CTmlh3tO4B8f8IbL8iDy5ZEr3jZDcxkPmDmRPQY74C39MRLXi0CKUP+oFzTZPYmyUC7fZrUXrb3LwRKWw1jEBQ==",
-                    "revoked": False,
-                    "self": "ZrHK0cCqrxWReROK0ciiSb45+dRphJa68qFaSjdve8bBdnGAu7+DIu0d+u/fXrNRXuObihOKMBIawaIVPNHqDw==",
-                    "others": [
-                        {
-                            "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                            "meta": {
-                                "block_number": 15
-                            },
-                            "uids": [
-                                "doe"
-                            ],
-                            "isMember": True,
-                            "wasMember": True,
-                            "signature": "4ulycI2MtBu/8bZipy+OsXDCNm9EyUIdZ1HA7hbJ66phKRNvv70Oo2YOF/+VDRJb97z9TqWKgfIQ0NbXU15xDg=="
-                        },
-                    ]
-                }
-            ],
-            "signed": []
-        }
-    ]
-}
-
-bma_membership_john = {
-    "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-    "uid": "john",
-    "sigDate": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
-    "memberships":
-        [
-            {
-                "version": 2,
-                "currency": "test_currency",
-                "membership": "IN",
-                "blockNumber": 0,
-                "blockHash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
-                "written": 10000
-            }
-        ]
-}
-
-bma_lookup_doe = {
-    "partial": False,
-    "results": [
-        {
-            "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-            "uids": [
-                {
-                    "uid": "doe",
-                    "meta": {
-                        "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
-                    },
-                    "revocation_sig": "CTmlh3tO4B8f8IbL8iDy5ZEr3jZDcxkPmDmRPQY74C39MRLXi0CKUP+oFzTZPYmyUC7fZrUXrb3LwRKWw1jEBQ==",
-                    "revoked": False,
-                    "self": "cIkHPQQ5+xTb4cKWv85rcYcZT+E3GDtX8B2nCK9Vs12p2Yz4bVaZiMvBBwisAAy2WBOaqHS3ydpXGtADchOICw==",
-                    "others": []
-                }
-            ],
-            "signed": [
-                        {
-                            "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                            "meta": {
-                                "block_number": 38580
-                            },
-                            "uids": [
-                                "john"
-                            ],
-                            "isMember": True,
-                            "wasMember": True,
-                            "signature": "4ulycI2MtBu/8bZipy+OsXDCNm9EyUIdZ1HA7hbJ66phKRNvv70Oo2YOF/+VDRJb97z9TqWKgfIQ0NbXU15xDg=="
-                        },
-                    ]
-        }
-    ]
-}
-
-bma_certifiers_of_john = {
-    "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-    "uid": "john",
-    "isMember": True,
-    "certifications": [
-        {
-            "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-            "uid": "doe",
-            "isMember": True,
-            "wasMember": True,
-            "cert_time": {
-                "block": 15,
-                "medianTime": 1500000000
-            },
-            "sigDate": "101-BAD49448A1AD73C978CEDCB8F137D20A5715EBAA739DAEF76B1E28EE67B2C00C",
-            "written": {
-                "number": 15,
-                "hash": "0000EC88BBBAA29D530D2B815DEE264DDC9F07F4"
-            },
-            "signature": "oliiPDhniZAGHrIFL66oHR+cqD4aTgXX+20VFLMfNHwdYPeik76hy334zxhoDC4cPODMb9df2nF/EDfCefrNBg=="
-        },
-    ]
-}
-
-bma_certified_by_john = {
-    "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-    "uid": "john",
-    "isMember": True,
-    "wasMember": False,
-    "certifications": [
-    ]
-}
-
-bma_certified_by_doe = {
-    "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-    "uid": "doe",
-    "isMember": True,
-    "certifications": [
-        {
-            "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-            "uid": "john",
-            "isMember": True,
-            "wasMember": True,
-            "cert_time": {
-                "block": 15,
-                "medianTime": 1500000000
-            },
-            "sigDate": "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
-            "written": {
-                "number": 15,
-                "hash": "0000EC88BBBAA29D530D2B815DEE264DDC9F07F4"
-            },
-            "signature": "oliiPDhniZAGHrIFL66oHR+cqD4aTgXX+20VFLMfNHwdYPeik76hy334zxhoDC4cPODMb9df2nF/EDfCefrNBg=="
-        },
-    ]
-}
-
-bma_parameters = {
-    "currency": "test_currency",
-    "c": 0.1,
-    "dt": 86400,
-    "ud0": 100,
-    "sigPeriod": 600,
-    "sigValidity": 2629800,
-    "sigQty": 3,
-    "xpercent": 0.9,
-    "sigStock": 10,
-    "sigWindow": 1000,
-    "msValidity": 2629800,
-    "stepMax": 3,
-    "medianTimeBlocks": 11,
-    "avgGenTime": 600,
-    "dtDiffEval": 20,
-    "blocksRot": 144,
-    "percentRot": 0.67
-}
-
-bma_blockchain_0 = {
-    "version": 2,
-    "nonce": 10144,
-    "number": 0,
-    "powMin": 3,
-    "time": 1421838980,
-    "medianTime": 1421838980,
-    "membersCount": 4,
-    "monetaryMass": 0,
-    "currency": "test_currency",
-    "issuer": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-    "signature": "+78w7251vvRdhoIJ6IWHEiEOLxNrmfQf45Y5sYvPdnAdXkVpO1unMV5YA/G5Vhphyz1dICrbeKCPM5qbFsoWAQ==",
-    "hash": "00063EB6E83F8717CEF1D25B3E2EE308374A14B1",
-    "inner_hash": "00063EB6E83F8717CEF1D25B3E2EE308374A14B1",
-    "parameters": "0.1:86400:100:604800:2629800:3:3:2629800:3:11:600:20:144:0.67",
-    "previousHash": None,
-    "previousIssuer": None,
-    "dividend": None,
-    "membersChanges": [],
-    "identities": [
-        "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:Ot3zIp/nsHT3zgJy+2YcXPL6vaM5WFsD+F8w3qnJoBRuBG6lv761zoaExp2iyUnm8fDAyKPpMxRK2kf437QSCw==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:inso",
-        "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:GZKLgaxJKL+GqxVLePMt8OVLJ6qTLrib5Mr/j2gjiNRY2k485YLB2OlzhBzZVnD3xLs0xi69JUfmLnM54j3aCA==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:cgeek",
-        "BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:th576H89dfymkG7/sH+DAIzjlmIqNEW6zY3ONrGeAml+k3f1ver399kYnEgG5YCaKXnnVM7P0oJHah80BV3mDw==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:moul",
-        "37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:XRmbTYFkPeGVEU2mJzzN4h1oVNDsZ4yyNZlDAfBm9CWhBsZ82QqX9GPHye2hBxxiu4Nz1BHgQiME6B4JcAC8BA==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:galuel"
-    ],
-    "joiners": [
-        "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:ccJm3F44eLMhQtnQY/7+14SWCDqVTL3Miw65hBVpV+YiUSUknIGhBNN0C0Cf+Pf0/pa1tjucW8Us3z5IklFSDg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:inso",
-        "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:1lFIiaR0QX0jibr5zQpXVGzBvMGqcsTRlmHiwGz5HOAZT8PTdVUb5q6YGZ6qAUZjdMjPmhLaiMIpYc47wUnzBA==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:cgeek",
-        "BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:ctyAhpTRrAAOhFJukWI8RBr//nqYYdQibVzjOfaCdcWLb3TNFKrNBBothNsq/YrYHr7gKrpoftucf/oxLF8zAg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:moul",
-        "37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:uoiGaC5b7kWqtqdPxwatPk9QajZHCNT9rf8/8ud9Rli24z/igcOf0Zr4A6RTAIKWUq9foW39VqJe+Y9R3rhACw==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:galuel"
-    ],
-    "actives": [],
-    "leavers": [],
-    "revoked": [],
-    "excluded": [],
-    "certifications": [
-        "37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:0:3wmCVW8AbVxRFm2PuLXD9UTCIg93MhUblZJvlYrDldSV4xuA7mZCd8TV4vb/6Bkc0FMQgBdHtpXrQ7dpo20uBA==",
-        "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:0:7UMQsUjLvuiZKIzOH5rrZDdDi5rXUo69EuQulY1Zm42xpRx/Gt5CkoTcJ/Mu83oElQbcZZTz/lVJ6IS0jzMiCQ==",
-        "BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:0:twWSY9etI82FLEHzhdqIoHsC9ehWCA7DCPiGxDLCWGPO4TG77hwtn3RcC68qoKHCib577JCp+fcKyp2vyI6FDA==",
-        "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:0:7K5MHkO8ibf5SchmPkRrmsg9owEZZ23uEMJJSQYG7L3PUmAKmmV/0VSjivxXH8gJGQBGsXQoK79x1jsYnj2nAg==",
-        "BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:0:Jua4FcEJFptSE5OoG1/Mgzx4e9jgGnYu7t8g1sqqPujI9hRhLFNXbQXedPS1q1OD5vWivA045gKOq/gnj8opDg==",
-        "37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:0:R/DV4/wYjvBG09QSOGtnxd3bfPFhVjEE5Uy3BsBMVUvjLsgxjf8NgLhYVozcHTRWS43ArxlXKfS5m3+KIPhhAQ==",
-        "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:0:4hP+ahJK021akL4UxB6c5QLaGJXa9eapd3nfdFQe+Xy87f/XLhj8BCa22XbbOlyGdaZRT3AYzbCL2UD5tI8mCw==",
-        "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:0:sZTQJr0d/xQnxrIIdSePUJpSTOa8v6IYGXMF2fVDZxQU8vwfzPm2dUKTaF0nU6E9wOYszzkBHaXL85nir+WtCQ==",
-        "37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:0:hDuBkoFhWhR/FgOU1+9SbQGBMIr47xqUzw1ZMERaPQo4aWm0WFbZurG4lvuJZzTyG6RF/gSw4VPvYZFPxWmADg==",
-        "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:0:79ZVrBehElVZh82fJdR18IJx06GkEVZTbwdHH4zb0S6VaGwdtLh1rvomm4ukBvUc8r/suTweG/SScsJairXNAg==",
-        "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:0:e/ai9E4G5CFB9Qi329e0ffYpZMgxj8mM4rviqIr2+UESA0UG86OuAAyHO11hYeyolZRiU8I7WdtNE98B1uZuBg==",
-        "BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:0:q4PCneYkcPH8AHEqEvqTtYQWslhlYO2B87aReuOl1uPczn5Q3VkZFAsU48ZTYryeyWp2nxdQojdFYhlAUNchAw=="
-    ],
-    "transactions": [],
-    "raw": """Version: 2
-Type: Block
-Currency: test_currency
-Number: 0
-PoWMin: 3
-Time: 1421838980
-MedianTime: 1421838980
-Issuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk
-Parameters: 0.1:86400:100:604800:15:604800:2629800:3:0.9:2629800:3:11:600:20:144:0.67
-MembersCount: 4
-Identities:
-8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:Ot3zIp/nsHT3zgJy+2YcXPL6vaM5WFsD+F8w3qnJoBRuBG6lv761zoaExp2iyUnm8fDAyKPpMxRK2kf437QSCw==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:inso
-HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:GZKLgaxJKL+GqxVLePMt8OVLJ6qTLrib5Mr/j2gjiNRY2k485YLB2OlzhBzZVnD3xLs0xi69JUfmLnM54j3aCA==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:cgeek
-BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:th576H89dfymkG7/sH+DAIzjlmIqNEW6zY3ONrGeAml+k3f1ver399kYnEgG5YCaKXnnVM7P0oJHah80BV3mDw==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:moul
-37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:XRmbTYFkPeGVEU2mJzzN4h1oVNDsZ4yyNZlDAfBm9CWhBsZ82QqX9GPHye2hBxxiu4Nz1BHgQiME6B4JcAC8BA==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:galuel
-Joiners:
-8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:ccJm3F44eLMhQtnQY/7+14SWCDqVTL3Miw65hBVpV+YiUSUknIGhBNN0C0Cf+Pf0/pa1tjucW8Us3z5IklFSDg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:inso
-HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:1lFIiaR0QX0jibr5zQpXVGzBvMGqcsTRlmHiwGz5HOAZT8PTdVUb5q6YGZ6qAUZjdMjPmhLaiMIpYc47wUnzBA==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:cgeek
-BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:ctyAhpTRrAAOhFJukWI8RBr//nqYYdQibVzjOfaCdcWLb3TNFKrNBBothNsq/YrYHr7gKrpoftucf/oxLF8zAg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:moul
-37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:uoiGaC5b7kWqtqdPxwatPk9QajZHCNT9rf8/8ud9Rli24z/igcOf0Zr4A6RTAIKWUq9foW39VqJe+Y9R3rhACw==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:galuel
-Actives:
-Leavers:
-Revoked:
-Excluded:
-Certifications:
-37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:0:3wmCVW8AbVxRFm2PuLXD9UTCIg93MhUblZJvlYrDldSV4xuA7mZCd8TV4vb/6Bkc0FMQgBdHtpXrQ7dpo20uBA==
-HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:0:7UMQsUjLvuiZKIzOH5rrZDdDi5rXUo69EuQulY1Zm42xpRx/Gt5CkoTcJ/Mu83oElQbcZZTz/lVJ6IS0jzMiCQ==
-BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:0:twWSY9etI82FLEHzhdqIoHsC9ehWCA7DCPiGxDLCWGPO4TG77hwtn3RcC68qoKHCib577JCp+fcKyp2vyI6FDA==
-8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:0:7K5MHkO8ibf5SchmPkRrmsg9owEZZ23uEMJJSQYG7L3PUmAKmmV/0VSjivxXH8gJGQBGsXQoK79x1jsYnj2nAg==
-BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:0:Jua4FcEJFptSE5OoG1/Mgzx4e9jgGnYu7t8g1sqqPujI9hRhLFNXbQXedPS1q1OD5vWivA045gKOq/gnj8opDg==
-37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:0:R/DV4/wYjvBG09QSOGtnxd3bfPFhVjEE5Uy3BsBMVUvjLsgxjf8NgLhYVozcHTRWS43ArxlXKfS5m3+KIPhhAQ==
-8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:0:4hP+ahJK021akL4UxB6c5QLaGJXa9eapd3nfdFQe+Xy87f/XLhj8BCa22XbbOlyGdaZRT3AYzbCL2UD5tI8mCw==
-HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:0:sZTQJr0d/xQnxrIIdSePUJpSTOa8v6IYGXMF2fVDZxQU8vwfzPm2dUKTaF0nU6E9wOYszzkBHaXL85nir+WtCQ==
-37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:0:hDuBkoFhWhR/FgOU1+9SbQGBMIr47xqUzw1ZMERaPQo4aWm0WFbZurG4lvuJZzTyG6RF/gSw4VPvYZFPxWmADg==
-8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:0:79ZVrBehElVZh82fJdR18IJx06GkEVZTbwdHH4zb0S6VaGwdtLh1rvomm4ukBvUc8r/suTweG/SScsJairXNAg==
-HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:0:e/ai9E4G5CFB9Qi329e0ffYpZMgxj8mM4rviqIr2+UESA0UG86OuAAyHO11hYeyolZRiU8I7WdtNE98B1uZuBg==
-BMAVuMDcGhYAV4wA27DL1VXX2ZARZGJYaMwpf7DJFMYH:37qBxM4hLV2jfyYo2bNzAjkeLngLr2r7G2HpdpKieVxw:0:q4PCneYkcPH8AHEqEvqTtYQWslhlYO2B87aReuOl1uPczn5Q3VkZFAsU48ZTYryeyWp2nxdQojdFYhlAUNchAw==
-Transactions:
-InnerHash: 09500111588846873CA0110602DDC17FB34AA9F4548B7CE322C845902FFC1429
-Nonce: 10144
-"""
-}
-
-bma_blockchain_current = {
-    "version": 2,
-    "nonce": 6909,
-    "number": 15,
-    "powMin": 4,
-    "time": 1441618206,
-    "medianTime": 1441614759,
-    "membersCount": 20,
-    "monetaryMass": 11711349901120,
-    "currency": "test_currency",
-    "issuer": "EPs9qX7HmCDy6ptUoMLpTzbh9toHu4au488pBTU9DN6y",
-    "signature": "kz/34w1cG+8tYacuPXf3FPmsFwrvtWkwp1POLJuX1P0zYaB9Tuu7iyYJzMQS0Xa3vwuWRqfz+fgyoCGnBjBLBQ==",
-    "hash": "0000CB4E9CCDE6F579135331C97F13903E8B6E21",
-    "parameters": "",
-    "previousHash": "00003BDA844D77EEE7CF32A6C3C87F2ACBFCFCBB",
-    "previousIssuer": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-    "dividend": None,
-    "membersChanges": [],
-    "identities": [],
-    "joiners": [],
-    "actives": [],
-    "leavers": [],
-    "excluded": [],
-    "certifications": [],
-    "transactions": [],
-    "raw": """Version: 2
-Type: Block
-Currency: meta_brouzouf
-Number: 30898
-PoWMin: 4
-Time: 1441618206
-MedianTime: 1441614759
-Issuer: EPs9qX7HmCDy6ptUoMLpTzbh9toHu4au488pBTU9DN6y
-PreviousHash: 00003BDA844D77EEE7CF32A6C3C87F2ACBFCFCBB
-PreviousIssuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk
-MembersCount: 20
-Identities:
-Joiners:
-Actives:
-Leavers:
-Revoked:
-Excluded:
-Certifications:
-Transactions:
-InnerHash: 6BB2E0BE18BEA428379336FB5F09DCE0EB594D09CDD705085CA91AA966C27CFA
-Nonce: 6909
-"""
-}
-
-# Sent 6, received 20 + 30
-bma_txhistory_john = {
-    "currency": "test_currency",
-    "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-    "history":
-        {
-            "sent":
-                [
-                    {
-                        "version": 2,
-                        "issuers":
-                            [
-                                "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
-                            ],
-                        "inputs":
-                            [
-                                "D:7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ:8"
-                            ],
-                        "unlocks":
-                            [
-                                "SIG(0)"
-                            ],
-                        "outputs":
-                            [
-                                "2:1:SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)",
-                                "6:1:SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)"
-                            ],
-                        "comment": "",
-                        "signatures":
-                            [
-                                "1Mn8q3K7N+R4GZEpAUm+XSyty1Uu+BuOy5t7BIRqgZcKqiaxfhAUfDBOcuk2i4TJy1oA5Rntby8hDN+cUCpvDg=="
-                            ],
-                        "hash": "5FB3CB80A982E2BDFBB3EA94673A74763F58CB2A",
-                        "block_number": 2,
-                        "time": 1421932545
-                    },
-                ],
-            "received":
-                [
-                    {
-                        "version": 2,
-                        "issuers":
-                            [
-                                "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-                            ],
-                        "inputs":
-                            [
-                                "D:FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn:8"
-                            ],
-                        "unlocks":
-                            [
-                                "SIG(0)"
-                            ],
-                        "outputs":
-                            [
-                                "2:1:SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)",
-                                "6:1:SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)"
-                            ],
-                        "comment": "",
-                        "signatures":
-                            [
-                                "1Mn8q3K7N+R4GZEpAUm+XSyty1Uu+BuOy5t7BIRqgZcKqiaxfhAUfDBOcuk2i4TJy1oA5Rntby8hDN+cUCpvDg=="
-                            ],
-                        "hash": "5FB3CB80A982E2BDFBB3EA94673A74763F58CB2A",
-                        "block_number": 2,
-                        "time": 1421932545
-                    },
-                    {
-                        "version": 2,
-                        "issuers":
-                            [
-                                "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-                            ],
-                        "inputs":
-                            [
-                                "D:FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn:8"
-                            ],
-                        "unlocks":
-                            [
-                                "SIG(0)"
-                            ],
-                        "outputs":
-                            [
-                                "5:1:SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)",
-                                "40:1:SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)"
-                            ],
-                        "comment": "",
-                        "signatures":
-                            [
-                                "1Mn8q3K7N+R4GZEpAUm+XSyty1Uu+BuOy5t7BIRqgZcKqiaxfhAUfDBOcuk2i4TJy1oA5Rntby8hDN+cUCpvDg=="
-                            ],
-                        "hash": "5FB3CB80A982E2BDFBB3EA94673A74763F58CB2A",
-                        "block_number": 2,
-                        "time": 1421932545
-                    },
-                ],
-            "sending": [],
-            "receiving": []
-        }
-}
-
-bma_udhistory_john = {
-    "currency": "test_currency",
-    "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-    "history":
-        {
-            "history":
-                [
-                    {
-                        "block_number": 2,
-                        "consumed": False,
-                        "time": 1435749971,
-                        "amount": 5,
-                        "base": 1
-                    },
-                    {
-                        "block_number": 10,
-                        "consumed": False,
-                        "time": 1435836032,
-                        "amount": 10,
-                        "base": 1
-                    }
-                ]
-        }}
-
-bma_txsources_john = {
-    "currency": "test_currency",
-    "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-    "sources":
-        [
-            {
-                "type": "D",
-                "noffset": 2,
-                "identifier": "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365",
-                "amount": 70,
-                "base": 0
-            },
-            {
-                "type": "D",
-                "noffset": 4,
-                "identifier": "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5",
-                "amount": 90,
-                "base": 0
-            }
-        ]}
-
-bma_with_ud = {
-    "result":
-        {
-            "blocks": []
-        }
-}
-
-
-def get_mock(loop):
-    mock = MockServer(loop)
-
-    mock.add_route('GET', '/blockchain/parameters', bma_parameters, 200)
-
-    mock.add_route('GET', '/blockchain/with/{topic}', bma_with_ud, 200)
-
-    mock.add_route('GET', '/blockchain/current', bma_blockchain_current, 200)
-
-    mock.add_route('GET', '/blockchain/block/0', bma_blockchain_0, 200)
-
-    mock.add_route('GET', '/blockchain/block/15', bma_blockchain_current, 200)
-
-    mock.add_route('GET', '/tx/history/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ/blocks/0/99', bma_txhistory_john,
-                   200)
-
-    mock.add_route('GET', '/tx/sources/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_txsources_john, 200)
-
-    mock.add_route('GET', '/ud/history/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_udhistory_john, 200)
-
-    mock.add_route('GET', '/wot/certifiers-of/john', bma_certifiers_of_john,
-                   200)
-
-    mock.add_route('GET', '/wot/certifiers-of/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_certifiers_of_john,
-                   200)
-
-    mock.add_route('GET', '/wot/certified-by/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_certified_by_john, 200)
-
-    mock.add_route('GET', '/wot/lookup/john', bma_lookup_john, 200)
-
-    mock.add_route('GET', '/wot/lookup/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_lookup_john, 200)
-
-    mock.add_route('GET', '/wot/lookup/doe', bma_lookup_doe, 200)
-
-    mock.add_route('GET', '/wot/lookup/FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn', bma_lookup_doe, 200)
-
-    mock.add_route('GET', '/blockchain/memberships/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ', bma_membership_john,
-                   200)
-
-    mock.add_route('GET', '/wot/lookup/wrong_pubkey',
-                   {'ucode': errors.NO_MATCHING_IDENTITY, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/certifiers-of/wrong_pubkey',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/lookup/wrong_uid',
-                   {'ucode': errors.NO_MATCHING_IDENTITY, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/certifiers-of/wrong_uid',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/wot/certifiers-of/FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('GET', '/blockchain/memberships/FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn',
-                   {'ucode': errors.NO_MEMBER_MATCHING_PUB_OR_UID, 'message': "No member matching this pubkey or uid"}, 404)
-
-    mock.add_route('POST', '/tx/process', {}, 200, )
-
-    return mock
diff --git a/src/sakia/tests/mocks/server.py b/src/sakia/tests/mocks/server.py
deleted file mode 100644
index c49456267a585f2d22e07d8d696183f94934bc7b..0000000000000000000000000000000000000000
--- a/src/sakia/tests/mocks/server.py
+++ /dev/null
@@ -1,102 +0,0 @@
-from aiohttp import web, log, errors
-import json
-import socket
-from duniterpy.documents import Peer
-
-
-def bma_peering_generator(port):
-    return {
-          "version": 2,
-          "currency": "test_currency",
-          "endpoints": [
-            "BASIC_MERKLED_API 127.0.0.1 {port}".format(port=port)
-          ],
-          "status": "UP",
-          "block": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
-          "signature": "cXuqZuDfyHvxYAEUkPH1TQ1M+8YNDpj8kiHGYi3LIaMqEdVqwVc4yQYGivjxFMYyngRfxXkyvqBKZA6rKOulCA==",
-          "raw": "Version: 2\nType: Peer\nCurrency: meta_brouzouf\nPublicKey: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk\nBlock: 30152-00003E7F9234E7542FCF669B69B0F84FF79CCCD3\nEndpoints:\nBASIC_MERKLED_API 127.0.0.1 {port}\n".format(port=port),
-          "pubkey": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
-        }
-
-
-def peer_document_generator(port):
-    return Peer.from_signed_raw("""Version: 2
-Type: Peer
-Currency: meta_brouzouf
-PublicKey: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk
-Block: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
-Endpoints:
-BASIC_MERKLED_API 127.0.0.1 {port}
-cXuqZuDfyHvxYAEUkPH1TQ1M+8YNDpj8kiHGYi3LIaMqEdVqwVc4yQYGivjxFMYyngRfxXkyvqBKZA6rKOulCA==
-""".format(port=port))
-
-
-class Request():
-    def __init__(self, method, url, content):
-        self.url = url
-        self.method = method
-        self.content = content
-
-
-class MockServer():
-    def __init__(self, loop):
-        self.lp = loop
-        self.requests = []
-        self.app = web.Application(loop=self.lp,
-                                   middlewares=[self.middleware_factory])
-        self.handler = None
-        self.port = self.find_unused_port()
-
-    def get_request(self, i):
-        return self.requests[i]
-
-    async def middleware_factory(self, app, handler):
-        async def middleware_handler(request):
-            try:
-                resp = await handler(request)
-                return resp
-            except web.HTTPNotFound:
-                return web.Response(status=404, body=bytes(json.dumps({"ucode":1001,
-                                                                    "message": "404 error"}),
-                                                           "utf-8"),
-                                    headers={'Content-Type': 'application/json'})
-
-        return middleware_handler
-
-    async def _handler(self, request, data_dict, http_code):
-        await request.read()
-        self.requests.append(Request(request.method, request.path, request.content))
-        return web.Response(body=bytes(json.dumps(data_dict), "utf-8"),
-                            headers={'Content-Type': 'application/json'},
-                            status=http_code)
-
-    def add_route(self, req_type, url, data_dict, http_code=200):
-        self.app.router.add_route(req_type, url,
-                             lambda request: self._handler(request, data_dict=data_dict, http_code=http_code))
-
-    def find_unused_port(self):
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        s.bind(('127.0.0.1', 0))
-        port = s.getsockname()[1]
-        s.close()
-        return port
-
-    def peer(self):
-        return peer_document_generator(self.port)
-
-    async def create_server(self, ssl_ctx=None):
-        protocol = "https" if ssl_ctx else "http"
-        url = "{}://127.0.0.1:{}".format(protocol, self.port)
-
-        self.add_route('GET', '/network/peering', bma_peering_generator(self.port))
-
-        self.handler = self.app.make_handler(
-            keep_alive_on=False,
-            access_log=log.access_logger,
-        )
-
-        srv = await self.lp.create_server(self.handler, '127.0.0.1', self.port)
-        return srv, self.port, url
-
-    async def close(self):
-        await self.handler.finish_connections()
\ No newline at end of file
diff --git a/src/sakia/tests/quamash_utils.py b/src/sakia/tests/quamash_utils.py
deleted file mode 100644
index 591b566d6df12daffe296d1f2ab02f3e64e10b49..0000000000000000000000000000000000000000
--- a/src/sakia/tests/quamash_utils.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import asyncio
-import quamash
-import socket
-from aiohttp import web, log
-
-_application_ = []
-
-
-class QuamashTest:
-    def setUpQuamash(self):
-        self.qapplication = get_application()
-        self.lp = quamash.QSelectorEventLoop(self.qapplication)
-        self.qapplication.setQuitOnLastWindowClosed(False)
-        asyncio.set_event_loop(self.lp)
-        self.lp.set_exception_handler(lambda l, c: unitttest_exception_handler(self, l, c))
-        self.exceptions = []
-        self.handler = None
-
-    def tearDownQuamash(self):
-        try:
-            self.lp.close()
-        finally:
-            asyncio.set_event_loop(None)
-
-        for exc in self.exceptions:
-            raise exc
-
-
-def unitttest_exception_handler(test, loop, context):
-    """
-    An exception handler which exists the program if the exception
-    was not catch
-    :param loop: the asyncio loop
-    :param context: the exception context
-    """
-    if 'exception' in context:
-        exception = context['exception']
-    else:
-        exception = BaseException(context['message'])
-    test.exceptions.append(exception)
-
-
-def get_application():
-    """Get the singleton QApplication"""
-    from quamash import QApplication
-    if not len(_application_):
-        application = QApplication.instance()
-        if not application:
-            import sys
-            application = QApplication(sys.argv)
-        _application_.append( application )
-    return _application_[0]
-
diff --git a/src/sakia/tests/unit/core/__init__.py b/src/sakia/tests/unit/core/__init__.py
deleted file mode 100644
index 39ab2a0b56350baad834cb7fb0cfecb8223e1fcd..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__author__ = 'inso'
diff --git a/src/sakia/tests/unit/core/graph/test_base_graph.py b/src/sakia/tests/unit/core/graph/test_base_graph.py
deleted file mode 100644
index f7a3bbdd3151a6f0b01e0720b5f033c7b451e710..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/graph/test_base_graph.py
+++ /dev/null
@@ -1,196 +0,0 @@
-import sys
-import unittest
-import logging
-from asynctest.mock import Mock, CoroutineMock, patch
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core.graph import BaseGraph
-from sakia.core.graph.constants import EdgeStatus, NodeStatus
-
-
-class TestBaseGraph(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        self.account_identity = Mock(specs='core.registry.Identity')
-        self.account_identity.pubkey = "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
-        self.account_identity.uid = "account_identity"
-        self.account_identity.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        self.first_identity = Mock(specs='core.registry.Identity')
-        self.first_identity.pubkey = "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
-        self.first_identity.uid = "first_identity"
-        self.first_identity.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        self.second_identity = Mock(specs='core.registry.Identity')
-        self.second_identity.pubkey = "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-        self.second_identity.uid = "second_uid"
-        self.second_identity.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=False)
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('time.time', Mock(return_value=50000))
-    def test_arc_status(self, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        app = Mock()
-
-        base_graph = BaseGraph(app, community)
-
-        async def exec_test():
-            self.assertEquals((await base_graph.arc_status(48000)), EdgeStatus.WEAK)
-            self.assertEquals((await base_graph.arc_status(49500)), EdgeStatus.STRONG)
-            self.assertEquals((await base_graph.arc_status(49200)), EdgeStatus.WEAK)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Application')
-    @patch('sakia.core.Community')
-    def test_node_status_member(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-
-        base_graph = BaseGraph(app, community)
-        certifier = Mock(specs='core.registry.Identity')
-        certifier.pubkey = "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
-        certifier.uid = "first_identity"
-        certifier.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=False)
-
-        account_identity = Mock(specs='core.registry.Identity')
-        account_identity.pubkey = "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-        account_identity.uid = "second_uid"
-        account_identity.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        async def exec_test():
-            self.assertEquals((await base_graph.node_status(certifier, account_identity)), NodeStatus.OUT)
-            self.assertEquals((await base_graph.node_status(account_identity, account_identity)), NodeStatus.HIGHLIGHTED)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Application')
-    @patch('sakia.core.Community')
-    def test_confirmation_text_expert_enabled(self, app, community):
-        community.network.confirmations = Mock(return_value=2)
-        app.preferences = {'expert_mode': True}
-
-        base_graph = BaseGraph(app, community)
-
-        self.assertEquals(base_graph.confirmation_text(200), "2/6")
-
-    @patch('sakia.core.Application')
-    @patch('sakia.core.Community')
-    def test_confirmation_text_expert_disabled(self, app, community):
-        community.network.confirmations = Mock(return_value=2)
-        app.preferences = {'expert_mode': False}
-
-        base_graph = BaseGraph(app, community)
-
-        self.assertEquals(base_graph.confirmation_text(200), "33 %")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_add_identitiers(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        app.preferences = {'expert_mode': True}
-
-        base_graph = BaseGraph(app, community)
-
-        certifications = [
-            {
-                'identity': self.first_identity,
-                'cert_time': 49100,
-                'block_number': 900
-            },
-            {
-                'identity': self.second_identity,
-                'cert_time': 49800,
-                'block_number': 996
-            }
-        ]
-        async def exec_test():
-            await base_graph.add_certifier_list(certifications, self.account_identity, self.account_identity)
-            self.assertEqual(len(base_graph.nx_graph.nodes()), 3)
-            self.assertEqual(len(base_graph.nx_graph.edges()), 2)
-            nodes = base_graph.nx_graph.nodes(data=True)
-            edges = base_graph.nx_graph.edges(data=True)
-
-            first_node = [n for n in nodes if n[0] == self.first_identity.pubkey][0]
-            self.assertEqual(first_node[1]['status'], NodeStatus.NEUTRAL)
-            self.assertEqual(first_node[1]['text'], certifications[0]['identity'].uid)
-            self.assertEqual(first_node[1]['tooltip'], certifications[0]['identity'].pubkey)
-
-            second_node = [n for n in nodes if n[0] == self.second_identity.pubkey][0]
-            self.assertEqual(second_node[1]['status'], NodeStatus.OUT)
-            self.assertEqual(second_node[1]['text'], certifications[1]['identity'].uid)
-            self.assertEqual(second_node[1]['tooltip'], certifications[1]['identity'].pubkey)
-
-            arc_from_first = [e for e in edges if e[0] == self.first_identity.pubkey][0]
-            self.assertEqual(arc_from_first[1], self.account_identity.pubkey)
-            self.assertEqual(arc_from_first[2]['status'], EdgeStatus.WEAK)
-            self.assertEqual(arc_from_first[2]['cert_time'], certifications[0]['cert_time'])
-
-            arc_from_second = [e for e in edges if e[0] == self.second_identity.pubkey][0]
-            self.assertEqual(arc_from_second[1], self.account_identity.pubkey)
-            self.assertEqual(arc_from_second[2]['status'], EdgeStatus.STRONG)
-            self.assertEqual(arc_from_second[2]['cert_time'], certifications[1]['cert_time'])
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_add_certified(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        app.preferences = {'expert_mode': True}
-
-        base_graph = BaseGraph(app, community)
-
-        certifications = [
-            {
-                'identity': self.first_identity,
-                'cert_time': 49100,
-                'block_number': 900
-            },
-            {
-                'identity': self.second_identity,
-                'cert_time': 49800,
-                'block_number': 996
-            }
-        ]
-        async def exec_test():
-            await base_graph.add_certified_list(certifications, self.account_identity, self.account_identity)
-            self.assertEqual(len(base_graph.nx_graph.nodes()), 3)
-            self.assertEqual(len(base_graph.nx_graph.edges()), 2)
-            nodes = base_graph.nx_graph.nodes(data=True)
-            first_node = [n for n in nodes if n[0] == self.first_identity.pubkey][0]
-            self.assertEqual(first_node[1]['status'], NodeStatus.NEUTRAL)
-            self.assertEqual(first_node[1]['text'], certifications[0]['identity'].uid)
-            self.assertEqual(first_node[1]['tooltip'], certifications[0]['identity'].pubkey)
-
-            second_node = [n for n in nodes if n[0] == self.second_identity.pubkey][0]
-            self.assertEqual(second_node[1]['status'], NodeStatus.OUT)
-            self.assertEqual(second_node[1]['text'], certifications[1]['identity'].uid)
-            self.assertEqual(second_node[1]['tooltip'], certifications[1]['identity'].pubkey)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_add_identity(self, app, community):
-        app.preferences = {'expert_mode': True}
-
-        base_graph = BaseGraph(app, community)
-
-        base_graph.add_identity(self.account_identity, NodeStatus.HIGHLIGHTED)
-        self.assertEqual(len(base_graph.nx_graph.nodes()), 1)
-        self.assertEqual(len(base_graph.nx_graph.edges()), 0)
-        nodes = base_graph.nx_graph.nodes(data=True)
-        account_node = [n for n in nodes if n[0] == self.account_identity.pubkey][0]
-        self.assertEqual(account_node[1]['status'], NodeStatus.HIGHLIGHTED)
-        self.assertEqual(account_node[1]['text'], self.account_identity.uid)
-        self.assertEqual(account_node[1]['tooltip'], self.account_identity.pubkey)
diff --git a/src/sakia/tests/unit/core/graph/test_explorer_graph.py b/src/sakia/tests/unit/core/graph/test_explorer_graph.py
deleted file mode 100644
index e3c99600bc1507f49da756241ee3c4977cf643a0..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/graph/test_explorer_graph.py
+++ /dev/null
@@ -1,210 +0,0 @@
-import sys
-import unittest
-import asyncio
-from asynctest.mock import Mock, CoroutineMock, patch
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core.graph import ExplorerGraph
-
-
-class TestExplorerGraph(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        ## Graph to test :
-        ##           - E
-        ## A - B - C - D
-        ##
-        ## Path : Between A and C
-
-        self.idA = Mock(specs='core.registry.Identity')
-        self.idA.pubkey = "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
-        self.idA.uid = "A"
-        self.idA.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        self.idB = Mock(specs='core.registry.Identity')
-        self.idB.pubkey = "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
-        self.idB.uid = "B"
-        self.idB.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        self.idC = Mock(specs='core.registry.Identity')
-        self.idC.pubkey = "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-        self.idC.uid = "C"
-        self.idC.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=False)
-
-        self.idD = Mock(specs='core.registry.Identity')
-        self.idD.pubkey = "6R11KGpG6w5Z6JfiwaPf3k4BCMY4dwhjCdmjGpvn7Gz5"
-        self.idD.uid = "D"
-        self.idD.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        self.idE = Mock(specs='core.registry.Identity')
-        self.idE.pubkey = "CZVDEsM6pPNxhAvXApGM8MJ6ExBZVpc8PNVyDZ7hKxLu"
-        self.idE.uid = "E"
-        self.idE.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=False)
-
-        self.idA.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49800,
-                                                                   'identity': self.idB,
-                                                                   'block_number': 996
-                                                               }
-                                                           ])
-        self.idA.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[])
-
-        self.idB.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49100,
-                                                                   'identity': self.idC,
-                                                                   'block_number': 990
-                                                               }
-                                                           ])
-
-        self.idB.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49800,
-                                                                   'identity': self.idA,
-                                                                   'block_number': 996
-                                                               }
-                                                           ])
-
-        self.idC.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49100,
-                                                                   'identity': self.idD,
-                                                                   'block_number': 990
-                                                               },
-                                                               {
-                                                                   'cert_time': 49110,
-                                                                   'identity': self.idE,
-                                                                   'block_number': 990
-                                                               }
-                                                           ])
-
-        self.idC.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49100,
-                                                                   'identity': self.idB,
-                                                                   'block_number': 990
-                                                               }
-                                                           ])
-
-        self.idD.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
-                                                           return_value=[
-                                                           ])
-        self.idD.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49100,
-                                                                   'identity': self.idC,
-                                                                   'block_number': 990
-                                                               }])
-
-        self.idE.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
-                                                           return_value=[
-                                                           ])
-        self.idE.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49100,
-                                                                   'identity': self.idC,
-                                                                   'block_number': 990
-                                                               }])
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_explore_full_from_center(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        community.nb_members = CoroutineMock(return_value = 3)
-        app.preferences = {'expert_mode': True}
-
-        explorer_graph = ExplorerGraph(app, community)
-
-        async def exec_test():
-            await explorer_graph._explore(self.idB, 5)
-            self.assertEqual(len(explorer_graph.nx_graph.nodes()), 5)
-            self.assertEqual(len(explorer_graph.nx_graph.edges()), 4)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_explore_full_from_extremity(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        community.nb_members = CoroutineMock(return_value = 3)
-        app.preferences = {'expert_mode': True}
-
-        explorer_graph = ExplorerGraph(app, community)
-
-        async def exec_test():
-            await explorer_graph._explore(self.idA, 5)
-            self.assertEqual(len(explorer_graph.nx_graph.nodes()), 5)
-            self.assertEqual(len(explorer_graph.nx_graph.edges()), 4)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_explore_partial(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        community.nb_members = CoroutineMock(return_value = 3)
-        app.preferences = {'expert_mode': True}
-
-        explorer_graph = ExplorerGraph(app, community)
-
-        async def exec_test():
-            await explorer_graph._explore(self.idB, 1)
-            self.assertEqual(len(explorer_graph.nx_graph.nodes()), 3)
-            self.assertEqual(len(explorer_graph.nx_graph.edges()), 2)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_start_stop_exploration(self, app, community):
-        async def explore_mock(id, steps):
-            await asyncio.sleep(0.1)
-            await asyncio.sleep(0.1)
-            await asyncio.sleep(0.1)
-
-        explorer_graph = ExplorerGraph(app, community)
-        explorer_graph._explore = explore_mock
-
-        async def exec_test():
-            self.assertEqual(explorer_graph.exploration_task, None)
-            explorer_graph.start_exploration(self.idA, 1)
-            self.assertNotEqual(explorer_graph.exploration_task, None)
-            task = explorer_graph.exploration_task
-            explorer_graph.start_exploration(self.idA, 1)
-            self.assertEqual(task, explorer_graph.exploration_task)
-            explorer_graph.start_exploration(self.idB, 1)
-            await asyncio.sleep(0)
-            self.assertTrue(task.cancelled())
-            self.assertNotEqual(task, explorer_graph.exploration_task)
-            task2 = explorer_graph.exploration_task
-            explorer_graph.start_exploration(self.idB, 2)
-            await asyncio.sleep(0)
-            self.assertTrue(task2.cancelled())
-            task3 = explorer_graph.exploration_task
-            explorer_graph.stop_exploration()
-            await asyncio.sleep(0)
-            self.assertTrue(task2.cancelled())
-
-
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/core/graph/test_wot_graph.py b/src/sakia/tests/unit/core/graph/test_wot_graph.py
deleted file mode 100644
index e0e355a24cee102cc3e57f161304f52fb3b889ae..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/graph/test_wot_graph.py
+++ /dev/null
@@ -1,158 +0,0 @@
-import sys
-import unittest
-import logging
-from asynctest.mock import Mock, CoroutineMock, patch
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core.graph import WoTGraph
-from sakia.core.graph.constants import EdgeStatus, NodeStatus
-
-
-class TestWotGraph(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        ## Graph to test :
-        ##
-        ## A - B - C
-        ##
-        ## Path : Between A and C
-
-        self.account_identity = Mock(specs='core.registry.Identity')
-        self.account_identity.pubkey = "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
-        self.account_identity.uid = "A"
-        self.account_identity.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        self.idB = Mock(specs='core.registry.Identity')
-        self.idB.pubkey = "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
-        self.idB.uid = "B"
-        self.idB.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=True)
-
-        self.idC = Mock(specs='core.registry.Identity')
-        self.idC.pubkey = "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
-        self.idC.uid = "C"
-        self.idC.is_member = CoroutineMock(spec='core.registry.Identity.is_member', return_value=False)
-
-        self.account_identity.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49800,
-                                                                   'identity': self.idB,
-                                                                   'block_number': 996
-                                                               }
-                                                           ])
-        self.account_identity.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[])
-
-        self.idC.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certifierd_by',
-                                                           return_value=[])
-
-        self.idC.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49100,
-                                                                   'identity': self.idB,
-                                                                   'block_number': 990
-                                                               }
-                                                           ])
-
-        self.idB.unique_valid_certified_by = CoroutineMock(spec='core.registry.Identity.certified_by',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49100,
-                                                                   'identity': self.idC,
-                                                                   'block_number': 996
-                                                               }
-                                                           ])
-
-        self.idB.unique_valid_certifiers_of = CoroutineMock(spec='core.registry.Identity.certifiers_of',
-                                                           return_value=[
-                                                               {
-                                                                   'cert_time': 49800,
-                                                                   'identity': self.account_identity,
-                                                                   'block_number': 996
-                                                               }
-                                                           ])
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_explore_to_find_member(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        app.preferences = {'expert_mode': True}
-
-        wot_graph = WoTGraph(app, community)
-
-        async def exec_test():
-            result = await wot_graph.explore_to_find_member(self.account_identity, self.idC)
-            self.assertTrue(result)
-            self.assertEqual(len(wot_graph.nx_graph.nodes()), 3)
-            self.assertEqual(len(wot_graph.nx_graph.edges()), 2)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Application')
-    @patch('sakia.core.Community')
-    @patch('time.time', Mock(return_value=50000))
-    def test_explore_to_find_unknown(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        app.preferences = {'expert_mode': True}
-
-        wot_graph = WoTGraph(app, community)
-
-        identity_unknown = Mock(specs='core.registry.Identity')
-        identity_unknown.pubkey = "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU"
-        identity_unknown.uid = "unkwn"
-
-        async def exec_test():
-            result = await wot_graph.explore_to_find_member(self.account_identity, identity_unknown)
-            self.assertFalse(result)
-            self.assertEqual(len(wot_graph.nx_graph.nodes()), 3)
-            self.assertEqual(len(wot_graph.nx_graph.edges()), 2)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_shortest_path(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        app.preferences = {'expert_mode': True}
-
-        wot_graph = WoTGraph(app, community)
-
-        async def exec_test():
-            result = await wot_graph.explore_to_find_member(self.account_identity, self.idC)
-            self.assertTrue(result)
-            self.assertEqual(len(wot_graph.nx_graph.nodes()), 3)
-            self.assertEqual(len(wot_graph.nx_graph.edges()), 2)
-            path = await wot_graph.get_shortest_path_to_identity(self.account_identity, self.idC)
-            self.assertEqual(path[0], self.account_identity.pubkey,)
-            self.assertEqual(path[1], self.idB.pubkey)
-            self.assertEqual(path[2], self.idC.pubkey)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    @patch('time.time', Mock(return_value=50000))
-    def test_initialize(self, app, community):
-        community.parameters = CoroutineMock(return_value = {'sigValidity': 1000})
-        community.network.confirmations = Mock(side_effect=lambda n: 4 if 996 else None)
-        app.preferences = {'expert_mode': True}
-
-        wot_graph = WoTGraph(app, community)
-
-        async def exec_test():
-            await wot_graph.initialize(self.account_identity, self.account_identity)
-            self.assertEqual(len(wot_graph.nx_graph.nodes()), 2)
-            self.assertEqual(len(wot_graph.nx_graph.edges()), 1)
-
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/core/money/test_quantitative.py b/src/sakia/tests/unit/core/money/test_quantitative.py
deleted file mode 100644
index dd32a301ff91a7280f982bb66e68ade163d32be4..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/money/test_quantitative.py
+++ /dev/null
@@ -1,144 +0,0 @@
-import unittest
-from asynctest.mock import Mock, CoroutineMock, patch, PropertyMock
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core.money import Quantitative
-
-
-class TestQuantitative(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = Quantitative(0, community, app, None)
-        self.assertEqual(referential.units, "TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = Quantitative(0, community, app, None)
-        self.assertEqual(referential.units, "TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_value(self, app, community):
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.value()
-            self.assertEqual(value, 101010110)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_differential(self, app, community):
-        referential = Quantitative(110, community, app, None)
-        async def exec_test():
-            value = await referential.value()
-            self.assertEqual(value, 110)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True)
-            self.assertEqual(value, "101,010,110 TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True, international_system=True)
-            self.assertEqual(value, "101.010110 MTC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=False)
-            self.assertEqual(value, "101,010,110")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=True)
-            self.assertEqual(value, "101.010110 M")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True)
-            self.assertEqual(value, "101,010,110 TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True, international_system=True)
-            self.assertEqual(value, "101.010110 MTC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=False)
-            self.assertEqual(value, "101,010,110")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Quantitative(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=True)
-            self.assertEqual(value, "101.010110 M")
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/core/money/test_quantitative_zsum.py b/src/sakia/tests/unit/core/money/test_quantitative_zsum.py
deleted file mode 100644
index 68ecaad7c751ba4b8f6fa014c93875e0bc85ebe7..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/money/test_quantitative_zsum.py
+++ /dev/null
@@ -1,164 +0,0 @@
-import unittest
-from asynctest.mock import Mock, CoroutineMock, patch, PropertyMock
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core.money import QuantitativeZSum
-
-
-class TestQuantitativeZSum(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = QuantitativeZSum(0, community, app, None)
-        self.assertEqual(referential.units, "Q0 TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = QuantitativeZSum(0, community, app, None)
-        self.assertEqual(referential.units, "Q0 TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_value(self, app, community):
-        referential = QuantitativeZSum(110, community, app, None)
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 5})
-        community.monetary_mass = CoroutineMock(return_value=500)
-        async def exec_test():
-            value = await referential.value()
-            self.assertEqual(value, 10)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_differential(self, app, community):
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 5})
-        community.monetary_mass = CoroutineMock(return_value=500)
-        referential = QuantitativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.value()
-            self.assertEqual(value, 10)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 5})
-        community.monetary_mass = CoroutineMock(return_value=500)
-        referential = QuantitativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True)
-            self.assertEqual(value, "10 Q0 TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 1000})
-        community.monetary_mass = CoroutineMock(return_value=500 * 1000)
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = QuantitativeZSum(110 * 1000, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True, international_system=True)
-            self.assertEqual(value, "109.500000 kQ0 TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 5})
-        community.monetary_mass = CoroutineMock(return_value=500)
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = QuantitativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=False)
-            self.assertEqual(value, "10")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 1000})
-        community.monetary_mass = CoroutineMock(return_value=500 * 1000)
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = QuantitativeZSum(110 * 1000, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=True)
-            self.assertEqual(value, "109.500000 kQ0 ")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 1000})
-        community.monetary_mass = CoroutineMock(return_value=500 * 1000 * 1000)
-        referential = QuantitativeZSum(110 * 1000, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True)
-            self.assertEqual(value, "110,000 TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 10})
-        community.monetary_mass = CoroutineMock(return_value=500 * 1000 * 1000)
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = QuantitativeZSum(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True, international_system=True)
-            self.assertEqual(value, "101.010110 MTC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 10})
-        community.monetary_mass = CoroutineMock(return_value=500 * 1000 * 1000)
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = QuantitativeZSum(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=False)
-            self.assertEqual(value, "101,010,110")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.get_ud_block = CoroutineMock(return_value={'membersCount': 10})
-        community.monetary_mass = CoroutineMock(return_value=500 * 1000 * 1000)
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = QuantitativeZSum(101010110, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=True)
-            self.assertEqual(value, "101.010110 M")
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/core/money/test_relative.py b/src/sakia/tests/unit/core/money/test_relative.py
deleted file mode 100644
index ae542b05ae8f5f4110dfebd8370ed67297342b46..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/money/test_relative.py
+++ /dev/null
@@ -1,160 +0,0 @@
-import unittest
-from asynctest.mock import Mock, CoroutineMock, patch, PropertyMock
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core.money import Relative
-
-
-class TestRelative(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = Relative(0, community, app, None)
-        self.assertEqual(referential.units, "UD TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = Relative(0, community, app, None)
-        self.assertEqual(referential.units, "UD TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_value(self, app, community):
-        community.dividend = CoroutineMock(return_value=10000)
-        referential = Relative(10101011, community, app, None)
-        async def exec_test():
-            value = await referential.value()
-            self.assertAlmostEqual(value, 1010.10110)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_differential(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000)
-        referential = Relative(110, community, app, None)
-        async def exec_test():
-            value = await referential.value()
-            self.assertAlmostEqual(value, 0.11)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(101, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True)
-            self.assertEqual(value, "0.101000 UD TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(1011, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True, international_system=True)
-            self.assertEqual(value, "1.011000 mUD TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=10000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(1011, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=False)
-            self.assertEqual(value, "0.101100")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(1011, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=True)
-            self.assertEqual(value, "1.011000 mUD ")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=10000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(1011, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True)
-            self.assertEqual(value, "0.101100 UD TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(1011, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True, international_system=True)
-            self.assertEqual(value, "1.011000 mUD TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(1011, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=False)
-            self.assertEqual(value, "0.001011")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = Relative(1011, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=True)
-            self.assertEqual(value, "1.011000 mUD ")
-        self.lp.run_until_complete(exec_test())
\ No newline at end of file
diff --git a/src/sakia/tests/unit/core/money/test_relative_to_past.py b/src/sakia/tests/unit/core/money/test_relative_to_past.py
deleted file mode 100644
index 61d6b05518e040ed298fff6796e4a1fd4e7e26b1..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/money/test_relative_to_past.py
+++ /dev/null
@@ -1,192 +0,0 @@
-import unittest
-from asynctest.mock import Mock, CoroutineMock, patch, PropertyMock
-from PyQt5.QtCore import QLocale, QDateTime
-from sakia.tests import QuamashTest
-from sakia.core.money.relative_to_past import RelativeToPast
-
-
-class TestRelativeToPast(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = RelativeToPast(0, community, app, 100)
-        self.assertEqual(referential.units, "UD(t) TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = RelativeToPast(0, community, app, 100)
-        self.assertEqual(referential.units, "UD(t) TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_value(self, app, community):
-        community.dividend = CoroutineMock(return_value=10000)
-        referential = RelativeToPast(10101011, community, app, 100)
-        async def exec_test():
-            value = await referential.value()
-            self.assertAlmostEqual(value, 1010.10110)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_differential(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000)
-        referential = RelativeToPast(110, community, app, 100)
-        async def exec_test():
-            value = await referential.value()
-            self.assertAlmostEqual(value, 0.11)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(101, community, app, 100)
-        async def exec_test():
-            value = await referential.localized(units=True)
-            self.assertEqual(value, "0.101000 UD({0}) TC".format(QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(1452663088792).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        )))
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(1011, community, app, 100)
-        async def exec_test():
-            value = await referential.localized(units=True, international_system=True)
-            self.assertEqual(value, "1.011000 mUD({0}) TC".format(QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(1452663088792).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        )))
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=10000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(1011, community, app, 100)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=False)
-            self.assertEqual(value, "0.101100")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(1011, community, app, 100)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=True)
-            self.assertEqual(value, "1.011000 mUD({0}) ".format(QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(1452663088792).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        )))
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=10000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(1011, community, app, 100)
-        async def exec_test():
-            value = await referential.diff_localized(units=True)
-            self.assertEqual(value, "0.101100 UD({0}) TC".format(QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(1452663088792).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        )))
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(1011, community, app, 100)
-        async def exec_test():
-            value = await referential.diff_localized(units=True, international_system=True)
-            self.assertEqual(value, "1.011000 mUD({0}) TC".format(QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(1452663088792).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        )))
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_no_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=10000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(1011, community, app, 100)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=False)
-            self.assertEqual(value, "0.101100")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_with_si(self, app, community):
-        community.dividend = CoroutineMock(return_value=1000000)
-        community.get_ud_block = CoroutineMock(return_value={'medianTime': 1452663088792})
-        type(community).short_currency = PropertyMock(return_value="TC")
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeToPast(1011, community, app, 100)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=True)
-            self.assertEqual(value, "1.011000 mUD({0}) ".format(QLocale.toString(
-                            QLocale(),
-                            QDateTime.fromTime_t(1452663088792).date(),
-                            QLocale.dateFormat(QLocale(), QLocale.ShortFormat)
-                        )))
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/core/money/test_relative_zsum.py b/src/sakia/tests/unit/core/money/test_relative_zsum.py
deleted file mode 100644
index f840ca5b6e2ae7a4120a3bdad2e57cca62530c86..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/money/test_relative_zsum.py
+++ /dev/null
@@ -1,204 +0,0 @@
-import unittest
-from asynctest.mock import Mock, CoroutineMock, patch, PropertyMock
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core.money import RelativeZSum
-
-
-class TestRelativeZSum(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = RelativeZSum(0, community, app, None)
-        self.assertEqual(referential.units, "R0 TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_units(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        referential = RelativeZSum(0, community, app, None)
-        self.assertEqual(referential.units, "R0 TC")
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_value(self, app, community):
-        referential = RelativeZSum(110, community, app, None)
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        async def exec_test():
-            value = await referential.value()
-            self.assertAlmostEqual(value, 0.10)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_differential(self, app, community):
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        referential = RelativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.value()
-            self.assertAlmostEqual(value, 0.10)
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        referential = RelativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True)
-            self.assertEqual(value, "0.1 R0 TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=True, international_system=True)
-            self.assertEqual(value, "100.000000 mR0 TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=False)
-            self.assertEqual(value, "0.100000")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_localized_no_units_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeZSum(110, community, app, None)
-        async def exec_test():
-            value = await referential.localized(units=False, international_system=True)
-            self.assertEqual(value, "100.000000 mR0 ")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        referential = RelativeZSum(90, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True)
-            self.assertEqual(value, "0.9 UD TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeZSum(90, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=True, international_system=True)
-            self.assertEqual(value, "900.000000 mUD TC")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_no_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeZSum(90, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=False)
-            self.assertEqual(value, "0.900000")
-        self.lp.run_until_complete(exec_test())
-
-    @patch('sakia.core.Community')
-    @patch('sakia.core.Application')
-    def test_diff_localized_no_units_with_si(self, app, community):
-        type(community).short_currency = PropertyMock(return_value="TC")
-        community.dividend = CoroutineMock(return_value=100)
-        community.get_ud_block = CoroutineMock(side_effect=lambda *args, **kwargs: \
-                                                            {'membersCount': 5, "monetaryMass": 500,
-                                                             "dividend": 100, 'unitbase': 0} if 'x' in kwargs \
-                                                            else {'membersCount': 5, "monetaryMass": 1050,
-                                                                  "dividend": 100, 'unitbase': 0} )
-        app.preferences = {
-            'digits_after_comma': 6
-        }
-        referential = RelativeZSum(90, community, app, None)
-        async def exec_test():
-            value = await referential.diff_localized(units=False, international_system=True)
-            self.assertEqual(value, "900.000000 mUD ")
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/core/test_account.py b/src/sakia/tests/unit/core/test_account.py
deleted file mode 100644
index 6facbca9c59d069fab9802b5a85a5dc87750b42b..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_account.py
+++ /dev/null
@@ -1,157 +0,0 @@
-import unittest
-from asynctest import Mock, MagicMock, CoroutineMock
-from PyQt5.QtCore import QLocale
-from sakia.core.registry.identities import IdentitiesRegistry, Identity
-from sakia.core import Account
-from sakia.tests import QuamashTest
-from duniterpy.documents import BlockUID, SelfCertification
-from duniterpy.key import ScryptParams
-
-
-class TestAccount(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-        self.identities_registry = IdentitiesRegistry()
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_load_save_account(self):
-        account = Account("test_salt", "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-                           ScryptParams(4096, 16, 1),
-                          "test_uid", [], [], [], self.identities_registry)
-        json_data = account.jsonify()
-        account_from_json = Account.load(json_data, self.identities_registry)
-
-        self.assertEqual(account.name, account_from_json.name)
-        self.assertEqual(account.pubkey, account_from_json.pubkey)
-        self.assertEqual(len(account.communities), len(account_from_json.communities))
-        self.assertEqual(len(account.wallets), len(account.wallets))
-
-    def test_add_contact(self):
-        called = False
-
-        def signal_called():
-            nonlocal called
-            called = True
-        account = Account("test_salt", "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-                           ScryptParams(4096, 16, 1),
-                          "test_uid", [], [], [], self.identities_registry)
-        account.contacts_changed.connect(signal_called)
-        account.add_contact({"uid":"friend", "pubkey":"FFFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"})
-        self.assertEqual(len(account.contacts), 1)
-        self.assertEqual(account.contacts[0]["uid"], "friend")
-        self.assertEqual(account.contacts[0]["pubkey"], "FFFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk")
-        self.assertTrue(called)
-
-    def test_remove_contact(self):
-        called = False
-
-        def signal_called():
-            nonlocal called
-            called = True
-        contact = {"uid":"friend", "pubkey":"FFFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"}
-        account = Account("test_salt", "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-                          ScryptParams(4096, 16, 1),
-                          "test_uid", [], [], [contact],
-                          self.identities_registry)
-        account.contacts_changed.connect(signal_called)
-        account.remove_contact(contact)
-        self.assertEqual(len(account.contacts), 0)
-        self.assertTrue(called)
-
-    def test_edit_contact(self):
-        called = False
-
-        def signal_called():
-            nonlocal called
-            called = True
-        contact = {"uid":"friend", "pubkey":"FFFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"}
-        account = Account("test_salt", "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-                           ScryptParams(4096, 16, 1),
-                          "test_uid", [], [], [contact],
-                          self.identities_registry)
-        account.contacts_changed.connect(signal_called)
-        account.edit_contact(0, {"uid": "ennemy", "pubkey": "FFFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"})
-        self.assertEqual(len(account.contacts), 1)
-        self.assertEqual(account.contacts[0]["uid"], "ennemy")
-        self.assertEqual(account.contacts[0]["pubkey"], "FFFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk")
-        self.assertTrue(called)
-
-    def test_send_membership(self):
-        account = Account("test_salt", "H8uYXvyF6GWeCr8cwFJ6V5B8tNprwRdjepFNJBqivrzr",
-                           ScryptParams(4096, 16, 1),
-                          "test_account", [], [], [],
-                          self.identities_registry)
-        account_identity = MagicMock(autospec='sakia.core.registry.Identity')
-        account_identity.selfcert = CoroutineMock(return_value=SelfCertification(2, "meta_brouzouf",
-                                            "H8uYXvyF6GWeCr8cwFJ6V5B8tNprwRdjepFNJBqivrzr", "test_account", 1000000000, ""))
-        community = MagicMock(autospec='sakia.core.Community')
-        community.blockUID = CoroutineMock(return_value=BlockUID(3102, "0000C5336F0B64BFB87FF4BC858AE25726B88175"))
-        self.identities_registry.future_find = CoroutineMock(return_value=account_identity)
-        community.bma_access = MagicMock(autospec='sakia.core.net.api.bma.access.BmaAccess')
-        response = Mock()
-        response.json = CoroutineMock(return_value={
-  "signature": "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
-  "membership": {
-    "version": 2,
-    "currency": "beta_brouzouf",
-    "issuer": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
-    "membership": "IN",
-    "sigDate": 1390739944,
-    "uid": "superman63"
-  }
-})
-        response.status = 200
-        community.bma_access.broadcast = CoroutineMock(return_value=[response])
-        async def exec_test():
-            result = await account.send_membership("test_password", community, "IN")
-            self.assertTrue(result)
-
-        self.lp.run_until_complete(exec_test())
-
-    def test_send_certification(self):
-        cert_signal_sent = False
-        def check_certification_accepted():
-            nonlocal cert_signal_sent
-            cert_signal_sent = True
-
-        account = Account("test_salt", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                           ScryptParams(4096, 16, 1),
-                          "test_account", [], [], [],
-                          self.identities_registry)
-        account.certification_accepted.connect(check_certification_accepted)
-        account_identity = MagicMock(autospec='sakia.core.registry.Identity')
-        account_identity.selfcert = CoroutineMock(return_value=SelfCertification(2, "meta_brouzouf",
-                                            "H8uYXvyF6GWeCr8cwFJ6V5B8tNprwRdjepFNJBqivrzr", "test_account",
-                                            BlockUID(1000, "49E2A1D1131F1496FAD6EDAE794A9ADBFA8844029675E3732D3B027ABB780243"),
-                                            "82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw=="))
-
-        certified = MagicMock(autospec='sakia.core.registry.Identity')
-        certified.uid = "john"
-        certified.pubkey = "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
-        certified.sigdate = 1441130831
-        certified.selfcert = CoroutineMock(return_value=SelfCertification(2, "meta_brouzouf",
-                                            "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", "john",
-                                            BlockUID(1200, "49E2A1D1131F1496FAD6EDAE794A9ADBFA8844029675E3732D3B027ABB780243"),
-                                            "82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw=="))
-
-        community = MagicMock(autospec='sakia.core.Community')
-        community.blockUID = CoroutineMock(return_value=BlockUID(3102, "49E2A1D1131F1496FAD6EDAE794A9ADBFA8844029675E3732D3B027ABB780243"))
-        self.identities_registry.future_find = CoroutineMock(side_effect=lambda pubkey, community :account_identity \
-                        if pubkey == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" else certified)
-        community.bma_access = MagicMock(autospec='sakia.core.net.api.bma.access.BmaAccess')
-        response = Mock()
-        response.json = CoroutineMock(return_value={})
-        response.status = 200
-        community.bma_access.broadcast = CoroutineMock(return_value=[response])
-        async def exec_test():
-            result = await account.certify("test_password", community, "")
-            self.assertTrue(result)
-
-        self.lp.run_until_complete(exec_test())
-        self.assertTrue(cert_signal_sent)
-
-
-
diff --git a/src/sakia/tests/unit/core/test_application.py b/src/sakia/tests/unit/core/test_application.py
deleted file mode 100644
index 3065f89c792da2ad64f651751cc67999c8498f00..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_application.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import aiohttp
-import sys
-import unittest
-import asyncio
-from asynctest.mock import Mock, CoroutineMock, patch
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.core import Application
-
-
-class TestApplication(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.core.registry.IdentitiesRegistry')
-    @patch('aiohttp.get', CoroutineMock(side_effect=lambda *args, **kwargs: exec('raise aiohttp.errors.TimeoutError()')))
-    def test_get_last_version_timeout(self, identities_registry):
-        app = Application(self.qapplication, self.lp, identities_registry)
-
-        async def exec_test():
-            app.get_last_version()
-            asyncio.sleep(5)
-
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/core/test_bma_access.py b/src/sakia/tests/unit/core/test_bma_access.py
deleted file mode 100644
index b4a3257b403e5eb7447d3e61aee186e07435f295..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_bma_access.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import unittest
-from unittest.mock import Mock
-import time
-from PyQt5.QtCore import QLocale
-from sakia.core.registry.identities import Identity, IdentitiesRegistry, LocalState, BlockchainState
-
-from sakia.tests.mocks.bma import nice_blockchain, corrupted
-from sakia.tests import QuamashTest
-from sakia.core import Application, Community
-from sakia.core.net import Network, Node
-from duniterpy.documents.peer import Peer
-from sakia.core.net.api.bma.access import BmaAccess
-
-
-class TestBmaAccess(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.peer = Peer.from_signed_raw("""Version: 2
-Type: Peer
-Currency: meta_brouzouf
-PublicKey: 8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU
-Block: 48698-000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8
-Endpoints:
-BASIC_MERKLED_API duniter.inso.ovh 80
-82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw==
-""")
-        self.node = Node(self.peer,
-                         "", "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-                         None, Node.ONLINE,
-                         time.time(), {}, "duniter", "0.12.0", 0, Mock("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)
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_compare_json_with_nonetype(self):
-        res = self.bma_access._compare_json({}, corrupted.bma_null_data)
-        self.assertFalse(res)
-
-    def test_filter_nodes(self):
-        pass#TODO
diff --git a/src/sakia/tests/unit/core/test_community.py b/src/sakia/tests/unit/core/test_community.py
deleted file mode 100644
index 16d2fd66a49a39b449979190a7d9797eed863f74..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_community.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import unittest
-from unittest.mock import Mock
-from pkg_resources import parse_version
-from PyQt5.QtCore import QLocale
-from sakia.core.net.api.bma.access import BmaAccess
-from sakia.core.net.network import Network
-from sakia.core import Community
-from sakia.tests import QuamashTest
-
-
-class TestCommunity(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_load_save_community(self):
-        network = Network("test_currency", [], Mock("aiohttp.ClientSession"))
-        bma_access = BmaAccess([], network)
-        community = Community("test_currency", network, bma_access)
-
-        json_data = community.jsonify()
-        community_from_json = Community.load(json_data, parse_version('0.12.0'))
-        self.assertEqual(community.name, community_from_json.name)
-        self.assertEqual(len(community.network._nodes), len(community_from_json.network._nodes))
-        community_from_json.network.session.close()
-
diff --git a/src/sakia/tests/unit/core/test_identities.py b/src/sakia/tests/unit/core/test_identities.py
deleted file mode 100644
index f00d01847f6d5c886022d4e7dd5df5d985875d38..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_identities.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import sys
-import unittest
-from unittest import mock
-import asyncio
-import quamash
-import logging
-from PyQt5.QtCore import QLocale
-from sakia.core.registry.identities import Identity, IdentitiesRegistry, LocalState, BlockchainState
-from sakia.tests import QuamashTest
-
-
-class TestIdentity(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_identity_from_handled_data(self):
-        community = mock.MagicMock()
-        type(community).currency = mock.PropertyMock(return_value="test_currency")
-
-        identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", None,
-                            LocalState.COMPLETED, BlockchainState.VALIDATED)
-        test_instances = {
-            "test_currency": {"7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ": identity}
-        }
-        identities_registry = IdentitiesRegistry(test_instances)
-
-        identity_from_data = identities_registry.from_handled_data("john",
-                                                                    "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                                                                   None,
-                                                                   BlockchainState.VALIDATED,
-                                                                   community)
-        self.assertEqual(identity, identity_from_data)
-
diff --git a/src/sakia/tests/unit/core/test_identity.py b/src/sakia/tests/unit/core/test_identity.py
deleted file mode 100644
index 5336bb43f7a6c870ded17517bc4da4d74603f0a3..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_identity.py
+++ /dev/null
@@ -1,127 +0,0 @@
-import unittest
-from asynctest import Mock, CoroutineMock, patch
-from PyQt5.QtCore import QLocale
-from sakia.core.registry.identities import Identity, LocalState, BlockchainState
-
-from sakia.tests.mocks.bma import nice_blockchain, corrupted
-from sakia.tests import QuamashTest
-from duniterpy.api import bma
-from duniterpy.documents import BlockUID
-from sakia.tools.exceptions import MembershipNotFoundError
-
-
-class TestIdentity(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-        self.identities_registry = Mock(spec='sakia.core.registry.IdentitiesRegistry')
-        self.community = Mock(spec='sakia.core.Community')
-        self.community.name = "test_brouzouf"
-        self.community.bma_access = Mock(spec='sakia.core.net.api.bma.BmaAccess')
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_identity_certifiers_of(self):
-        def bma_access(request, *args):
-            if request is bma.wot.CertifiersOf:
-                return nice_blockchain.bma_certifiers_of_john
-            if request is bma.wot.Lookup:
-                return nice_blockchain.bma_lookup_john
-            if request is bma.blockchain.Block:
-                return nice_blockchain.bma_blockchain_current
-
-        def block_to_time(block_number=None):
-            if block_number == 15:
-                return 1200000200
-            else:
-                return 1500000400
-
-        identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                            BlockUID(20, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"),
-                            LocalState.COMPLETED, BlockchainState.VALIDATED)
-        id_doe = Identity("doe", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                            BlockUID(101, "BAD49448A1AD73C978CEDCB8F137D20A5715EBAA739DAEF76B1E28EE67B2C00C"),
-                            LocalState.COMPLETED, BlockchainState.VALIDATED)
-
-        self.community.bma_access.future_request = CoroutineMock(side_effect=bma_access)
-        self.identities_registry.from_handled_data = Mock(return_value=id_doe)
-        self.community.time = CoroutineMock(side_effect=block_to_time)
-        async def exec_test():
-            certifiers = await identity.certifiers_of(self.identities_registry, self.community)
-
-            self.assertEqual(len(certifiers), 2)
-            self.assertEqual(certifiers[0]['identity'].uid, "doe")
-            self.assertEqual(certifiers[1]['identity'].uid, "doe")
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('time.time', Mock(return_value=1500000400))
-    def test_identity_cert_delay(self):
-        def bma_access(request, *args):
-            if request is bma.wot.CertifiedBy:
-                return nice_blockchain.bma_certified_by_doe
-            if request is bma.wot.Lookup:
-                return nice_blockchain.bma_lookup_doe
-            if request is bma.blockchain.Block:
-                return nice_blockchain.bma_blockchain_current
-
-        def block_to_time(block_number=None):
-            if block_number == 38580:
-                return 1500000200
-            else:
-                return 1500000400
-
-        identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                            BlockUID(20, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"),
-                            LocalState.COMPLETED, BlockchainState.VALIDATED)
-        id_doe = Identity("doe", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                            BlockUID(101, "BAD49448A1AD73C978CEDCB8F137D20A5715EBAA739DAEF76B1E28EE67B2C00C"),
-                            LocalState.COMPLETED, BlockchainState.VALIDATED)
-
-        self.community.bma_access.future_request = CoroutineMock(side_effect=bma_access)
-        self.community.parameters = CoroutineMock(side_effect=lambda: nice_blockchain.bma_parameters)
-        self.community.time = CoroutineMock(side_effect=block_to_time)
-        self.identities_registry.from_handled_data = Mock(return_value=id_doe)
-        async def exec_test():
-            cert_delay = await identity.cert_issuance_delay(self.identities_registry, self.community)
-            self.assertEqual(cert_delay, 200)
-
-        self.lp.run_until_complete(exec_test())
-
-    def test_identity_membership(self):
-        def bma_access(request, *args):
-            if request is bma.blockchain.Membership:
-                return nice_blockchain.bma_membership_john
-
-        identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                            BlockUID(20, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"),
-                            LocalState.COMPLETED, BlockchainState.VALIDATED)
-
-        self.community.bma_access.future_request = CoroutineMock(side_effect=bma_access)
-
-        async def exec_test():
-            ms = await identity.membership(self.community)
-            self.assertEqual(ms["blockNumber"], 0)
-            self.assertEqual(ms["blockHash"], "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855")
-            self.assertEqual(ms["membership"], "IN")
-            self.assertEqual(ms["currency"], "test_currency")
-            self.assertEqual(ms["written"], 10000)
-
-        self.lp.run_until_complete(exec_test())
-
-    def test_identity_corrupted_membership(self):
-        def bma_access(request, *args):
-            if request is bma.blockchain.Membership:
-                return corrupted.bma_memberships_empty_array
-
-        identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                            BlockUID(20, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"),
-                            LocalState.COMPLETED, BlockchainState.VALIDATED)
-        self.community.bma_access.future_request = CoroutineMock(side_effect=bma_access)
-        async def exec_test():
-            with self.assertRaises(MembershipNotFoundError):
-                await identity.membership(self.community)
-
-        self.lp.run_until_complete(exec_test())
-
diff --git a/src/sakia/tests/unit/core/test_network.py b/src/sakia/tests/unit/core/test_network.py
deleted file mode 100644
index 175062c5f3255ca4d711aceb8efd80405cb172d0..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_network.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import aiohttp
-import unittest
-from unittest.mock import PropertyMock
-from asynctest import Mock, patch
-from duniterpy.documents.block import BlockUID
-from PyQt5.QtCore import QLocale
-from sakia.core.net import Network
-from sakia.tests import QuamashTest
-
-
-class TestCommunity(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
diff --git a/src/sakia/tests/unit/core/test_node.py b/src/sakia/tests/unit/core/test_node.py
deleted file mode 100644
index fd07ca043bd73ee0fc14ee9bcb4741439655a825..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_node.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import unittest
-from unittest.mock import Mock
-from asynctest import CoroutineMock, patch
-from duniterpy.documents import Peer, BlockUID
-from PyQt5.QtCore import QLocale
-from sakia.core.net import Node
-from sakia.tests import QuamashTest
-from sakia.tests.mocks.bma import nice_blockchain
-from pkg_resources import parse_version
-
-
-class TestNode(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_from_peer(self):
-        peer = Peer.from_signed_raw("""Version: 2
-Type: Peer
-Currency: meta_brouzouf
-PublicKey: 8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU
-Block: 48698-000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8
-Endpoints:
-BASIC_MERKLED_API duniter.inso.ovh 80
-82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw==
-""")
-        node = Node.from_peer('meta_brouzouf', peer, Mock("aiohttp.ClientSession"))
-        self.assertEqual(node.pubkey, "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU")
-        self.assertEqual(node.endpoint.inline(), "BASIC_MERKLED_API duniter.inso.ovh 80")
-        self.assertEqual(node.currency, "meta_brouzouf")
-
-    @patch('duniterpy.api.bma.network.Peering')
-    def test_from_address(self, peering):
-        peering.return_value.get = CoroutineMock(return_value={
-            "version": 2,
-            "currency": "meta_brouzouf",
-            "endpoints": [
-                "BASIC_MERKLED_API duniter.inso.ovh 80"
-            ],
-            "block": "48698-000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8",
-            "signature": "82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw==",
-            "raw": "Version: 2\nType: Peer\nCurrency: meta_brouzouf\nPublicKey: 8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU\nBlock: 48698-000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8\nEndpoints:\nBASIC_MERKLED_API duniter.inso.ovh 80\n",
-            "pubkey": "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU"
-        })
-
-        async def exec_test():
-            node = await Node.from_address("meta_brouzouf", "127.0.0.1", 9000, Mock("aiohttp.ClientSession"))
-            self.assertEqual(node.pubkey, "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU")
-            self.assertEqual(node.endpoint.inline(), "BASIC_MERKLED_API duniter.inso.ovh 80")
-            self.assertEqual(node.currency, "meta_brouzouf")
-
-        self.lp.run_until_complete(exec_test())
-
-    def test_from_json_to_json(self):
-        json_data = {"version": "0.12.0", "state": 1, "fork_window": 0, "uid": "inso",
-                      "block": nice_blockchain.bma_blockchain_current,
-                      "peer": """Version: 2
-Type: Peer
-Currency: meta_brouzouf
-PublicKey: 8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU
-Block: 48698-000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8
-Endpoints:
-BASIC_MERKLED_API duniter.inso.ovh 80
-82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw==
-""",
-                      "pubkey": "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU",
-                      "last_change": 1448199706.6561477, "software": "duniter"}
-        node = Node.from_json("meta_brouzouf", json_data, parse_version('0.12.0'), Mock("aiohttp.ClientSession"))
-        self.assertEqual(node.version, "0.12.0")
-        self.assertEqual(node.state, 1)
-        self.assertEqual(node.fork_window, 0)
-        self.assertEqual(node.uid, "inso")
-        self.assertEqual(node.block, nice_blockchain.bma_blockchain_current)
-        self.assertEqual(node.endpoint.inline(), "BASIC_MERKLED_API duniter.inso.ovh 80")
-        self.assertEqual(node.pubkey, "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU")
-        self.assertEqual(node.last_change, 1448199706.6561477)
-        self.assertEqual(node.currency, "meta_brouzouf")
-        self.assertEqual(node.peer.pubkey, "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU")
-        self.assertEqual(node.peer.blockUID.number, 48698)
-        self.assertEqual(node.peer.blockUID.sha_hash, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8")
-
-        result = node.jsonify()
-        for key in result:
-            self.assertEqual(result[key], json_data[key], "Error with key {0}".format(key))
-
-    def test_jsonify_root_node(self):
-        peer = Peer.from_signed_raw("""Version: 2
-Type: Peer
-Currency: meta_brouzouf
-PublicKey: 8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU
-Block: 48698-000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8
-Endpoints:
-BASIC_MERKLED_API duniter.inso.ovh 80
-82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw==
-""")
-        node = Node(peer, "inso", "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU", nice_blockchain.bma_blockchain_current,
-                 Node.ONLINE, 1111111111, {}, "duniter", "0.12", 0, Mock("aiohttp.ClientSession"))
-        result = node.jsonify_root_node()
-        self.assertEqual(result['pubkey'], "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU")
-        self.assertEqual(result['uid'], "inso")
-        self.assertEqual(result['peer'], peer.signed_raw())
\ No newline at end of file
diff --git a/src/sakia/tests/unit/core/test_wallet.py b/src/sakia/tests/unit/core/test_wallet.py
deleted file mode 100644
index e485c5723045970dc31fd8190c0ef2355f63bf31..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/test_wallet.py
+++ /dev/null
@@ -1,217 +0,0 @@
-import unittest
-import pypeg2
-from unittest.mock import MagicMock, PropertyMock
-from asynctest import CoroutineMock
-from duniterpy.grammars import output
-from duniterpy.documents import BlockUID
-from PyQt5.QtCore import QLocale
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.core import Wallet
-from sakia.tests import QuamashTest
-
-
-class TestWallet(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-        self.identities_registry = IdentitiesRegistry({})
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_load_save_wallet(self):
-        wallet = Wallet(0, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                             "Wallet 1", self.identities_registry)
-
-        json_data = wallet.jsonify()
-        wallet_from_json = Wallet.load(json_data, self.identities_registry)
-        self.assertEqual(wallet.walletid, wallet_from_json.walletid)
-        self.assertEqual(wallet.pubkey, wallet_from_json.pubkey)
-        self.assertEqual(wallet.name, wallet_from_json.name)
-        self.assertEqual(wallet._identities_registry, wallet_from_json._identities_registry)
-
-    def test_prepare_tx_base_0(self):
-        community = MagicMock("sakia.core.Community")
-        community.currency = "test_currency"
-        cache = MagicMock("sakia.core.txhistory.TxHistory")
-        cache.available_sources = [{
-            "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-            "type": "D",
-            "noffset": 2,
-            "identifier": "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365",
-            "amount": 15,
-            "base": 0
-        },
-            {
-                "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                "type": "D",
-                "noffset": 4,
-                "identifier": "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5",
-                "amount": 85,
-                "base": 0
-            },
-            {
-                "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                "type": "T",
-                "noffset": 4,
-                "identifier": "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
-                "amount": 11,
-                "base": 1
-            }]
-        wallet = Wallet(0, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                        "Wallet 1", self.identities_registry)
-        wallet.caches["test_currency"] = cache
-        tx = wallet.prepare_tx("FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                               BlockUID(32, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8"),
-                               99, "", community)
-        self.assertEqual(tx.version, 3)
-        self.assertEqual(tx.blockstamp.number, 32)
-        self.assertEqual(tx.blockstamp.sha_hash, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8")
-        self.assertEqual(len(tx.issuers), 1)
-        self.assertEqual(tx.issuers[0], "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ")
-        self.assertEqual(len(tx.inputs), 2)
-        self.assertEqual(tx.inputs[0].origin_id, "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365")
-        self.assertEqual(tx.inputs[0].source, "D")
-        self.assertEqual(tx.inputs[0].index, 2)
-        self.assertEqual(tx.inputs[1].origin_id, "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5")
-        self.assertEqual(tx.inputs[1].source, "D")
-        self.assertEqual(tx.inputs[1].index, 4)
-        self.assertEqual(len(tx.outputs), 2)
-        self.assertEqual(tx.outputs[0].amount, 99)
-        self.assertEqual(tx.outputs[0].base, 0)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition),
-                         "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)")
-        self.assertEqual(tx.outputs[1].amount, 1)
-        self.assertEqual(tx.outputs[1].base, 0)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition),
-                         "SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)")
-        self.assertEqual(len(tx.unlocks), 2)
-        self.assertEqual(tx.unlocks[0].index, 0)
-        self.assertEqual(tx.unlocks[0].parameters[0].index, 0)
-        self.assertEqual(tx.unlocks[1].index, 1)
-        self.assertEqual(tx.unlocks[0].parameters[0].index, 0)
-
-    def test_prepare_tx_base_1(self):
-        community = MagicMock("sakia.core.Community")
-        community.currency = "test_currency"
-        cache = MagicMock("sakia.core.txhistory.TxHistory")
-        cache.available_sources = [{
-                "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                "type": "D",
-                "noffset": 2,
-                "identifier": "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365",
-                "amount": 15,
-                "base": 0
-            },
-            {
-                "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                "type": "D",
-                "noffset": 4,
-                "identifier": "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5",
-                "amount": 85,
-                "base": 0
-            },
-            {
-                "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                "type": "T",
-                "noffset": 4,
-                "identifier": "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
-                "amount": 11,
-                "base": 1
-            }]
-        wallet = Wallet(0, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                             "Wallet 1", self.identities_registry)
-        wallet.caches["test_currency"] = cache
-        tx = wallet.prepare_tx("FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                               BlockUID(32, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8"),
-                               100, "", community)
-        self.assertEqual(tx.version, 3)
-        self.assertEqual(tx.blockstamp.number, 32)
-        self.assertEqual(tx.blockstamp.sha_hash, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8")
-        self.assertEqual(len(tx.issuers), 1)
-        self.assertEqual(tx.issuers[0], "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ")
-        self.assertEqual(len(tx.inputs), 1)
-        self.assertEqual(tx.inputs[0].origin_id, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
-        self.assertEqual(tx.inputs[0].source, "T")
-        self.assertEqual(tx.inputs[0].index, 4)
-        self.assertEqual(len(tx.outputs), 2)
-        self.assertEqual(tx.outputs[0].amount, 10)
-        self.assertEqual(tx.outputs[0].base, 1)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition),
-                         "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)")
-        self.assertEqual(tx.outputs[1].amount, 1)
-        self.assertEqual(tx.outputs[1].base, 1)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition),
-                         "SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)")
-        self.assertEqual(len(tx.unlocks), 1)
-        self.assertEqual(tx.unlocks[0].index, 0)
-        self.assertEqual(tx.unlocks[0].parameters[0].index, 0)
-
-    def test_prepare_tx_base_1_overheads(self):
-        community = MagicMock("sakia.core.Community")
-        community.currency = "test_currency"
-        cache = MagicMock("sakia.core.txhistory.TxHistory")
-        cache.available_sources = [{
-                "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                "type": "D",
-                "noffset": 2,
-                "identifier": "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365",
-                "amount": 15,
-                "base": 0
-            },
-            {
-                "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                "type": "D",
-                "noffset": 4,
-                "identifier": "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5",
-                "amount": 85,
-                "base": 0
-            },
-            {
-                "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                "type": "T",
-                "noffset": 4,
-                "identifier": "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
-                "amount": 11,
-                "base": 1
-            }]
-        wallet = Wallet(0, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                             "Wallet 1", self.identities_registry)
-        wallet.caches["test_currency"] = cache
-        tx = wallet.prepare_tx("FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
-                               BlockUID(32, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8"),
-                               101, "", community)
-        self.assertEqual(tx.version, 3)
-        self.assertEqual(tx.blockstamp.number, 32)
-        self.assertEqual(tx.blockstamp.sha_hash, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8")
-        self.assertEqual(len(tx.issuers), 1)
-        self.assertEqual(tx.issuers[0], "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ")
-        self.assertEqual(len(tx.inputs), 2)
-        self.assertEqual(tx.inputs[0].origin_id, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
-        self.assertEqual(tx.inputs[0].source, "T")
-        self.assertEqual(tx.inputs[0].index, 4)
-        self.assertEqual(tx.inputs[1].origin_id, "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365")
-        self.assertEqual(tx.inputs[1].source, "D")
-        self.assertEqual(tx.inputs[1].index, 2)
-        self.assertEqual(len(tx.outputs), 4)
-        self.assertEqual(tx.outputs[0].amount, 1)
-        self.assertEqual(tx.outputs[0].base, 0)
-        self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition),
-                         "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)")
-        self.assertEqual(tx.outputs[1].amount, 10)
-        self.assertEqual(tx.outputs[1].base, 1)
-        self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition),
-                         "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)")
-        self.assertEqual(tx.outputs[2].amount, 14)
-        self.assertEqual(tx.outputs[2].base, 0)
-        self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition),
-                         "SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)")
-        self.assertEqual(tx.outputs[3].amount, 1)
-        self.assertEqual(tx.outputs[3].base, 1)
-        self.assertEqual(pypeg2.compose(tx.outputs[3].conditions, output.Condition),
-                         "SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)")
-        self.assertEqual(len(tx.unlocks), 2)
-        self.assertEqual(tx.unlocks[0].index, 0)
-        self.assertEqual(tx.unlocks[0].parameters[0].index, 0)
-        self.assertEqual(tx.unlocks[1].index, 1)
-        self.assertEqual(tx.unlocks[1].parameters[0].index, 0)
diff --git a/src/sakia/tests/unit/core/txhistory/__init__.py b/src/sakia/tests/unit/core/txhistory/__init__.py
deleted file mode 100644
index 39ab2a0b56350baad834cb7fb0cfecb8223e1fcd..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/txhistory/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__author__ = 'inso'
diff --git a/src/sakia/tests/unit/core/txhistory/test_txhistory_loading.py b/src/sakia/tests/unit/core/txhistory/test_txhistory_loading.py
deleted file mode 100644
index 9f4ef8c0618dfc6d002da9bc615ed2f144986a12..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/core/txhistory/test_txhistory_loading.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import sys
-import unittest
-import asyncio
-import quamash
-import time
-import logging
-from PyQt5.QtCore import QLocale, Qt
-from sakia.tests.mocks.bma import nice_blockchain
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.core.app import Application
-from sakia.core import Account, Community, Wallet
-from sakia.core.net import Network, Node
-from sakia.core.net.api.bma.access import BmaAccess
-from sakia.tests import QuamashTest
-from duniterpy.documents.peer import BMAEndpoint
-
-
-class TestTxHistory(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.endpoint = BMAEndpoint("", "127.0.0.1", "", 50005)
-        self.node = Node("test_currency", [self.endpoint],
-                         "", "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-                         nice_blockchain.bma_blockchain_current, Node.ONLINE,
-                         time.time(), {}, "duniter", "0.14.0", 0)
-        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)
-        self.wallet.init_cache(self.application, self.community)
-
-        # Salt/password : "testsakia/testsakia"
-        # Pubkey : 7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ
-        self.account = Account("testsakia", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                               "john", [self.community], [self.wallet], [], self.identities_registry)
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    # this test fails with old algorithm
-    def notest_txhistory_reload(self):
-        mock = nice_blockchain.get_mock()
-        time.sleep(2)
-        logging.debug(mock.pretend_url)
-
-        received_list = []
-        self.lp.run_until_complete(self.wallet.caches[self.community.currency].
-                                   refresh(self.community, received_list))
-        self.assertEquals(len(received_list), 2)
-        received_value = sum([r.metadata['amount'] for r in received_list])
-        self.assertEqual(received_value, 60)
-        self.assertEqual(len(self.wallet.dividends(self.community)), 2)
-        dividends_value = sum([ud['amount'] for ud in self.wallet.dividends(self.community)])
-        self.assertEqual(dividends_value, 15)
-        mock.delete_mock()
diff --git a/src/sakia/tests/unit/gui/test_context_menu.py b/src/sakia/tests/unit/gui/test_context_menu.py
deleted file mode 100644
index c1ee0b9fa17d4cbf9f96afd36cf6dcaeebf515e6..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/test_context_menu.py
+++ /dev/null
@@ -1,164 +0,0 @@
-import unittest
-from unittest.mock import patch, MagicMock, Mock
-from asynctest.mock import CoroutineMock
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.tests.mocks.bma import nice_blockchain
-from sakia.gui.widgets.context_menu import ContextMenu
-from duniterpy.documents import Membership, BlockUID
-from sakia.tools.exceptions import MembershipNotFoundError
-
-
-class TestContextMenu(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        self.identity = Mock(specs='sakia.core.registry.Identity')
-        self.identity.pubkey = "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
-        self.identity.uid = "A"
-
-        self.app = MagicMock(specs='sakia.core.Application')
-        self.account = MagicMock(specs='sakia.core.Account')
-        self.community = MagicMock(specs='sakia.core.Community')
-        self.password_asker = MagicMock(specs='sakia.gui.password_asker.PasswordAsker')
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('PyQt5.QtWidgets.QMenu', create=True)
-    def test_view_in_wot(self, qmenu):
-        wot_refreshed = False
-
-        def refresh_wot(identity):
-            nonlocal wot_refreshed
-            self.assertEqual(identity, self.identity)
-            wot_refreshed = True
-
-        async def exec_test():
-            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
-            context_menu.view_identity_in_wot.connect(refresh_wot)
-            context_menu.view_wot(self.identity)
-
-        self.lp.run_until_complete(exec_test())
-        self.assertTrue(wot_refreshed)
-
-    @patch('PyQt5.QtWidgets.QMenu', create=True)
-    def test_copy_pubkey_to_clipboard(self, qmenu):
-        app = Mock('sakia.core.Application')
-
-        async def exec_test():
-            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
-            context_menu.copy_pubkey_to_clipboard(self.identity)
-
-        self.lp.run_until_complete(exec_test())
-        self.assertEqual(self.qapplication.clipboard().text(), "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk")
-
-    @patch('PyQt5.QtWidgets.QMenu', create=True)
-    def test_copy_block_to_clipboard(self, qmenu):
-        self.community.get_block = CoroutineMock(side_effect=lambda n: nice_blockchain.bma_blockchain_current if n == 15 \
-            else nice_blockchain.bma_blockchain_0)
-        self.qapplication.clipboard().clear()
-
-        async def exec_test():
-            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
-            context_menu.community = self.community
-            context_menu.copy_block_to_clipboard(15)
-
-        self.lp.run_until_complete(exec_test())
-        raw_block = "{0}{1}\n".format(nice_blockchain.bma_blockchain_current["raw"],
-                                      nice_blockchain.bma_blockchain_current["signature"])
-        self.assertEqual(self.qapplication.clipboard().text(), raw_block)
-
-    @patch('PyQt5.QtWidgets.QMenu', create=True)
-    def test_copy_membership_to_clipboard(self, qmenu):
-        ms_data = {
-                    "version": 2,
-                    "currency": "meta_brouzouf",
-                    "membership": "IN",
-                    "blockNumber": 49116,
-                    "blockHash": "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
-                    "written": 49119
-                }
-        ms_document = Membership(ms_data["version"], ms_data["currency"], self.identity.pubkey,
-                                 BlockUID(ms_data["blockNumber"], ms_data["blockHash"]),
-                                 ms_data["membership"], self.identity.uid, "49116-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
-                                 "znWiWP7Sy9gg9pZq4YWKNpel8MM16VBM1lgBg2gWjSonnc+KVRCtQng5JB4JD0PgJJ0F8jdITuggFrRwqRfzAA==")
-        self.identity.membership = CoroutineMock(return_value=ms_data)
-        self.community.get_block = CoroutineMock(return_value={
-  "version": 2,
-  "nonce": 127424,
-  "number": 49119,
-  "powMin": 5,
-  "time": 1453921638,
-  "medianTime": 1453912797,
-  "membersCount": 18,
-  "monetaryMass": 14028534972234185000,
-  "currency": "meta_brouzouf",
-  "issuer": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-  "signature": "ZmjhoRubftJ/T2WYQ3gaDeTGGUJ3beUshtlWn1k/r5opk0vt48KG3w+9JU0T9YFR5uezllaek9efoNwAHRBLDw==",
-  "hash": "49E2A1D1131F1496FAD6EDAE794A9ADBFA8844029675E3732D3B027ABB780243",
-  "innerhash": "273DE1845F8A63677D69DD427E00DAD73D9AEDBA80356A2E0D2152939D9DAF0C",
-  "parameters": "",
-  "previousHash": "000005C27A1636FE07AB01766FBA060565142D79",
-  "previousIssuer": "HBSSmqZjT4UQKsCntTSmZbu7iRP14HYtifLE6mW1PsBD",
-  "dividend": None,
-  "identities": [],
-  "joiners": [
-    "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:znWiWP7Sy9gg9pZq4YWKNpel8MM16VBM1lgBg2gWjSonnc+KVRCtQng5JB4JD0PgJJ0F8jdITuggFrRwqRfzAA==:49116-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67:49116-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67:A"
-  ],
-  "actives": [],
-  "leavers": [],
-  "excluded": [],
-  "revoked": [],
-  "certifications": [],
-  "transactions": [],
-  "raw": """Version: 2
-Type: Block
-Currency: meta_brouzouf
-Number: 49119
-PoWMin: 5
-Time: 1453921638
-MedianTime: 1453912797
-Issuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk
-PreviousHash: 000005C27A1636FE07AB01766FBA060565142D79
-PreviousIssuer: HBSSmqZjT4UQKsCntTSmZbu7iRP14HYtifLE6mW1PsBD
-MembersCount: 18
-Identities:
-Joiners:
-HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:znWiWP7Sy9gg9pZq4YWKNpel8MM16VBM1lgBg2gWjSonnc+KVRCtQng5JB4JD0PgJJ0F8jdITuggFrRwqRfzAA==:49116-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67:49116-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67:A
-Actives:
-Leavers:
-Revoked:
-Excluded:
-Certifications:
-Transactions:
-InnerHash: 273DE1845F8A63677D69DD427E00DAD73D9AEDBA80356A2E0D2152939D9DAF0C
-Nonce: 127424
-"""
-})
-        self.qapplication.clipboard().clear()
-
-        async def exec_test():
-            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
-            context_menu.community = self.community
-            context_menu.copy_membership_to_clipboard(self.identity)
-
-        self.lp.run_until_complete(exec_test())
-        self.assertEqual(self.qapplication.clipboard().text(), ms_document.signed_raw())
-
-    @patch('PyQt5.QtWidgets.QMenu', create=True)
-    def test_copy_membership_to_clipboard_not_found(self, qmenu):
-        def raiser():
-            raise MembershipNotFoundError("inso", "meta_brouzouf")
-        self.identity.membership = CoroutineMock(side_effect=lambda c: raiser())
-
-        self.qapplication.clipboard().clear()
-
-        async def exec_test():
-            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
-            context_menu.community = self.community
-            context_menu.copy_membership_to_clipboard(self.identity)
-
-        self.lp.run_until_complete(exec_test())
-        self.assertEqual(self.qapplication.clipboard().text(), "")
diff --git a/src/sakia/tests/unit/gui/test_main_window.py b/src/sakia/tests/unit/gui/test_main_window.py
deleted file mode 100644
index 0227edd5086c265d2ca1846af40cd21b8c68238c..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/test_main_window.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import unittest
-from unittest.mock import patch, MagicMock, Mock, PropertyMock
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.gui.mainwindow import MainWindow
-
-
-class TestMainWindow(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        self.identity = Mock(spec='sakia.core.registry.Identity')
-        self.identity.pubkey = "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
-        self.identity.uid = "A"
-
-        self.app = MagicMock(autospec='sakia.core.Application')
-        self.account_joe = Mock(spec='sakia.core.Account')
-        self.account_joe.contacts_changed = Mock()
-        self.account_joe.contacts_changed.disconnect = Mock()
-        self.account_joe.contacts_changed.connect = Mock()
-        self.account_doe = Mock(spec='sakia.core.Account')
-        self.account_doe.contacts_changed = Mock()
-        self.account_doe.contacts_changed.disconnect = Mock()
-        self.account_doe.contacts_changed.connect = Mock()
-
-        def change_current_account(account_name):
-            type(self.app).current_account = PropertyMock(return_value=self.account_doe)
-        self.app.get_account = Mock(side_effect=lambda name: self.app.accounts[name])
-        self.app.change_current_account = Mock(side_effect=change_current_account)
-        type(self.app).current_account = PropertyMock(return_value=self.account_joe)
-        self.app.accounts = {'joe':self.account_joe,
-                             'doe': self.account_doe}
-        self.homescreen = MagicMock(autospec='sakia.gui.homescreen.Homescreen')
-        self.community_view = MagicMock(autospec='sakia.gui.community_view.CommunityView')
-        self.password_asker = MagicMock(autospec='sakia.gui.password_asker.PasswordAsker')
-        self.node_manager = MagicMock(autospec='sakia.gui.node_manager.NodeManager')
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_change_account(self):
-        widget = Mock(spec='PyQt5.QtWidgets.QMainWindow', create=True)
-        widget.installEventFilter = Mock()
-        ui = Mock(spec='sakia.gen_resources.mainwindow_uic.Ui_MainWindow', create=True)
-        ui.setupUi = Mock()
-        label_icon = Mock()
-        label_status = Mock()
-        label_time = Mock()
-        combo_referentials = Mock()
-        combo_referentials.currentIndexChanged = {int: Mock()}
-        mainwindow = MainWindow(self.app, self.account_joe,
-                                self.homescreen, self.community_view, self.node_manager,
-                                widget, ui, label_icon,
-                                label_status, label_time, combo_referentials, self.password_asker)
-        mainwindow.refresh = Mock()
-        mainwindow.action_change_account("doe")
-        self.app.change_current_account.assert_called_once_with(self.account_doe)
-        mainwindow.change_account()
-
-        self.community_view.change_account.assert_called_once_with(self.account_doe, self.password_asker)
-        self.password_asker.change_account.assert_called_once_with(self.account_doe)
-        self.account_joe.contacts_changed.disconnect.assert_called_once_with(mainwindow.refresh_contacts)
-        self.account_doe.contacts_changed.connect.assert_called_once_with(mainwindow.refresh_contacts)
-        mainwindow.refresh.assert_called_once_with()
-
-    def test_change_account_from_none(self):
-        widget = Mock(spec='PyQt5.QtWidgets.QMainWindow', create=True)
-        widget.installEventFilter = Mock()
-        ui = Mock(spec='sakia.gen_resources.mainwindow_uic.Ui_MainWindow', create=True)
-        ui.setupUi = Mock()
-        label_icon = Mock()
-        label_status = Mock()
-        label_time = Mock()
-        combo_referentials = Mock()
-        combo_referentials.currentIndexChanged = {int: Mock()}
-
-        type(self.app).current_account = PropertyMock(return_value=None)
-        mainwindow = MainWindow(self.app, None, self.homescreen, self.community_view, self.node_manager,
-                                widget, ui, label_icon,
-                                label_status, label_time, combo_referentials, self.password_asker)
-        mainwindow.refresh = Mock()
-        mainwindow.action_change_account("doe")
-        self.app.change_current_account.assert_called_once_with(self.account_doe)
-        mainwindow.change_account()
-
-        self.community_view.change_account.assert_called_once_with(self.account_doe, self.password_asker)
-        self.password_asker.change_account.assert_called_once_with(self.account_doe)
-        self.account_joe.contacts_changed.disconnect.assert_not_called()
-        self.account_doe.contacts_changed.connect.assert_called_once_with(mainwindow.refresh_contacts)
-        mainwindow.refresh.assert_called_once_with()
\ No newline at end of file
diff --git a/src/sakia/tests/unit/gui/views/test_base_edge.py b/src/sakia/tests/unit/gui/views/test_base_edge.py
deleted file mode 100644
index f05353bd653e425dc05be4f841960b810d3e39f0..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/views/test_base_edge.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import unittest
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.gui.views.edges.base_edge import BaseEdge
-from sakia.core.graph.constants import EdgeStatus
-
-
-class TestBaseEdge(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_create_edge(self):
-        metadata = {
-            'status': EdgeStatus.STRONG
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            edge = BaseEdge("A", "B", metadata, nx_pos)
-            self.assertEqual(edge.source, "A")
-            self.assertEqual(edge.destination, "B")
-            self.assertEqual(edge.destination_point.x(), 10)
-            self.assertEqual(edge.destination_point.y(), 20)
-            self.assertEqual(edge.source_point.x(), 0)
-            self.assertEqual(edge.source_point.y(), 5)
-            self.assertEqual(edge.status, EdgeStatus.STRONG)
-
-        self.lp.run_until_complete(exec_test())
\ No newline at end of file
diff --git a/src/sakia/tests/unit/gui/views/test_base_node.py b/src/sakia/tests/unit/gui/views/test_base_node.py
deleted file mode 100644
index 3e2016837f242d1235d7658f932158a809e93606..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/views/test_base_node.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import unittest
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.gui.views.nodes.base_node import BaseNode
-from sakia.core.graph.constants import NodeStatus
-
-
-class TestBaseNode(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_create_edge(self):
-        metadata = {
-            'status': NodeStatus.NEUTRAL,
-            'text': "UserA",
-            'tooltip': "TestTooltip"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            node = BaseNode(("A", metadata), nx_pos)
-            self.assertEqual(node.id, "A")
-            self.assertEqual(node.metadata['status'], NodeStatus.NEUTRAL)
-            self.assertEqual(node.x(), 0)
-            self.assertEqual(node.y(), 5)
-            self.assertEqual(node.status_wallet, False)
-            self.assertEqual(node.status_member, True)
-            self.assertEqual(node.text, "UserA")
-            self.assertEqual(node.toolTip(), "UserA - TestTooltip")
-
-        self.lp.run_until_complete(exec_test())
\ No newline at end of file
diff --git a/src/sakia/tests/unit/gui/views/test_explorer_edge.py b/src/sakia/tests/unit/gui/views/test_explorer_edge.py
deleted file mode 100644
index 5732190cbca2a56b3f0e6cfa14cf4eb520f3ccdd..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/views/test_explorer_edge.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import unittest
-from unittest.mock import patch
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.gui.views.edges import ExplorerEdge
-from sakia.core.graph.constants import EdgeStatus
-
-
-class TestExplorerEdge(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_create_wot_edge(self):
-        metadata = {
-            'status': EdgeStatus.STRONG,
-            'confirmation_text': "0/6"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            edge = ExplorerEdge("A", "B", metadata, nx_pos, 0, 0)
-            self.assertEqual(edge.source, "A")
-            self.assertEqual(edge.destination, "B")
-            self.assertAlmostEqual(edge.destination_point.x(), 10.0, delta=5)
-            self.assertAlmostEqual(edge.destination_point.y(), 20.0, delta=5)
-            self.assertAlmostEqual(edge.source_point.x(), 10.0, delta=5)
-            self.assertAlmostEqual(edge.source_point.y(), 20.0, delta=5)
-            self.assertEqual(edge.status, EdgeStatus.STRONG)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('PyQt5.QtGui.QPainter')
-    @patch('PyQt5.QtWidgets.QWidget')
-    def test_paint(self, painter, widget):
-        metadata = {
-            'status': EdgeStatus.STRONG,
-            'confirmation_text': "0/6"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-
-        async def exec_test():
-            edge = ExplorerEdge("A", "B", metadata, nx_pos, 0, 1)
-            edge.paint(painter, 0, widget)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('PyQt5.QtGui.QPainter')
-    @patch('PyQt5.QtWidgets.QWidget')
-    def test_bounding_rect(self, painter, widget):
-        metadata = {
-            'status': EdgeStatus.STRONG,
-            'confirmation_text': "0/6"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-
-        async def exec_test():
-            edge = ExplorerEdge("A", "B", metadata, nx_pos, 0, 0)
-            bounding_rect = edge.boundingRect()
-            self.assertAlmostEqual(bounding_rect.x(), 7.0, delta=5)
-            self.assertAlmostEqual(bounding_rect.y(), 17.0, delta=5)
-            self.assertAlmostEqual(bounding_rect.width(), 6.0, delta=5)
-            self.assertAlmostEqual(bounding_rect.height(), 6.0, delta=5)
-
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/gui/views/test_explorer_node.py b/src/sakia/tests/unit/gui/views/test_explorer_node.py
deleted file mode 100644
index a27c75e1cc1a7724f518af2ee3c0f352e00b7b1a..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/views/test_explorer_node.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import unittest
-from unittest.mock import patch
-from PyQt5.QtCore import QLocale, QPointF
-from PyQt5.QtGui import QPainter
-from PyQt5.QtWidgets import QStyleOptionGraphicsItem, QWidget
-from sakia.tests import QuamashTest
-from sakia.gui.views.nodes import ExplorerNode
-from sakia.core.graph.constants import NodeStatus
-
-
-class TestExplorerNode(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_create_explorer_node(self):
-        metadata = {
-            'status': NodeStatus.NEUTRAL,
-            'text': "UserA",
-            'tooltip': "TestTooltip"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            node = ExplorerNode(("A", metadata), QPointF(0, 0), nx_pos, 0, 1, False)
-            self.assertEqual(node.id, "A")
-            self.assertEqual(node.metadata['status'], NodeStatus.NEUTRAL)
-            self.assertEqual(node.x(), 0)
-            self.assertEqual(node.y(), 0)
-            self.assertEqual(node.status_wallet, False)
-            self.assertEqual(node.status_member, True)
-            self.assertEqual(node.text, "UserA")
-            self.assertEqual(node.toolTip(), "UserA - TestTooltip")
-
-        self.lp.run_until_complete(exec_test())
-
-    def test_paint(self):
-        painter = QPainter()
-        widget = QWidget()
-        metadata = {
-            'status': NodeStatus.NEUTRAL,
-            'text': "UserA",
-            'tooltip': "TestTooltip"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            node = ExplorerNode(("A", metadata), QPointF(0, 0), nx_pos, 0, 1, False)
-            node.paint(painter, QStyleOptionGraphicsItem(), widget)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('PyQt5.QtGui.QPainter', spec=QPainter)
-    @patch('PyQt5.QtWidgets.QWidget')
-    def test_bounding_rect(self, painter, widget):
-        metadata = {
-            'status': NodeStatus.NEUTRAL,
-            'text': "A",
-            'tooltip': "TestTooltip"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            node = ExplorerNode(("A", metadata), QPointF(0, 0), nx_pos, 0, 1, False)
-            bounding_rect = node.boundingRect()
-            self.assertAlmostEqual(bounding_rect.x(), -0.5, delta=15)
-            self.assertAlmostEqual(bounding_rect.y(), -0.5, delta=15)
-            self.assertAlmostEqual(bounding_rect.width(), 19.59375, delta=15)
-            self.assertAlmostEqual(bounding_rect.height(), 37.0, delta=15)
-
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/gui/views/test_explorer_scene.py b/src/sakia/tests/unit/gui/views/test_explorer_scene.py
deleted file mode 100644
index 7ba890170be0b25a011347b4d1a7973b3cddd3da..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/views/test_explorer_scene.py
+++ /dev/null
@@ -1,181 +0,0 @@
-import unittest
-import networkx
-import math
-from unittest.mock import patch, Mock
-from sakia.tests import QuamashTest
-from sakia.gui.views.scenes import ExplorerScene
-from sakia.core.graph.constants import NodeStatus
-
-
-class TestExplorerScene(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        self.identities_uids = ['A', 'B', 'C', 'D', 'E']
-        self.identities_pubkeys = ['pbkA', 'pbkB', 'pbkC', 'pbkD', 'pbkE']
-        self.certifications = [('pbkA', 'pbkB'),
-                               ('pbkB', 'pbkC'),
-                               ('pbkD', 'pbkA'),
-                               ('pbkA', 'pbkE')]
-        # Graph :
-        #  A -> B -> C
-        #    <- D
-        #    -> E
-        self.identity_status = [NodeStatus.SELECTED, NodeStatus.NEUTRAL, NodeStatus.NEUTRAL,
-                                NodeStatus.OUT, NodeStatus.NEUTRAL]
-        self.test_graph = networkx.MultiDiGraph()
-        self.test_graph.add_nodes_from(self.identities_pubkeys)
-        self.test_graph.add_edges_from(self.certifications)
-        for index, node in enumerate(self.test_graph.nodes(data=True)):
-            node[1]['text'] = self.identities_uids[index]
-            node[1]['tooltip'] = self.identities_pubkeys[index]
-            node[1]['status'] = self.identity_status[index]
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_init_layout(self):
-        data_layout = ExplorerScene._init_layout(self.test_graph)
-        for pubkey in self.identities_pubkeys:
-            self.assertEqual(data_layout[pubkey]['theta'], None)
-            self.assertEqual(data_layout[pubkey]['scenter'], 25)
-            self.assertEqual(data_layout[pubkey]['nchild'], 0)
-            self.assertEqual(data_layout[pubkey]['sparent'], None)
-            self.assertEqual(data_layout[pubkey]['stsize'], 0.0)
-            self.assertEqual(data_layout[pubkey]['span'], 0.0)
-
-    def test_set_parent_nodes(self):
-        data_layout = ExplorerScene._init_layout(self.test_graph)
-        ExplorerScene._set_parent_nodes(self.test_graph, data_layout, 'pbkA')
-        self.assertEqual(data_layout['pbkA']['scenter'], 0)
-        self.assertEqual(data_layout['pbkB']['scenter'], 1)
-        self.assertEqual(data_layout['pbkC']['scenter'], 2)
-        self.assertEqual(data_layout['pbkD']['scenter'], 1)
-        self.assertEqual(data_layout['pbkE']['scenter'], 1)
-
-        self.assertEqual(data_layout['pbkA']['sparent'], None)
-        self.assertEqual(data_layout['pbkB']['sparent'], 'pbkA')
-        self.assertEqual(data_layout['pbkC']['sparent'], 'pbkB')
-        self.assertEqual(data_layout['pbkD']['sparent'], 'pbkA')
-        self.assertEqual(data_layout['pbkE']['scenter'], 1)
-
-        self.assertEqual(data_layout['pbkA']['nchild'], 3)
-        self.assertEqual(data_layout['pbkB']['nchild'], 1)
-        self.assertEqual(data_layout['pbkC']['nchild'], 0)
-        self.assertEqual(data_layout['pbkD']['nchild'], 0)
-        self.assertEqual(data_layout['pbkE']['nchild'], 0)
-
-    def test_set_subtree_size(self):
-        data_layout = ExplorerScene._init_layout(self.test_graph)
-
-        data_layout['pbkA']['sparent'] = None
-        data_layout['pbkB']['sparent'] = 'pbkA'
-        data_layout['pbkC']['sparent'] = 'pbkB'
-        data_layout['pbkD']['sparent'] = 'pbkA'
-        data_layout['pbkE']['sparent'] = 'pbkA'
-
-        data_layout['pbkA']['nchild'] = 2
-        data_layout['pbkB']['nchild'] = 1
-        data_layout['pbkC']['nchild'] = 0
-        data_layout['pbkD']['nchild'] = 0
-        data_layout['pbkE']['nchild'] = 0
-
-        ExplorerScene._set_subtree_size(self.test_graph, data_layout)
-        self.assertAlmostEqual(data_layout['pbkA']['stsize'], 3.0)
-        self.assertAlmostEqual(data_layout['pbkB']['stsize'], 1.0)
-        self.assertAlmostEqual(data_layout['pbkC']['stsize'], 1.0)
-        self.assertAlmostEqual(data_layout['pbkD']['stsize'], 1.0)
-        self.assertAlmostEqual(data_layout['pbkE']['stsize'], 1.0)
-
-    def test_set_subtree_span(self):
-        data_layout = ExplorerScene._init_layout(self.test_graph)
-
-        data_layout['pbkA']['sparent'] = None
-        data_layout['pbkB']['sparent'] = 'pbkA'
-        data_layout['pbkC']['sparent'] = 'pbkB'
-        data_layout['pbkD']['sparent'] = 'pbkA'
-        data_layout['pbkE']['sparent'] = 'pbkA'
-
-        data_layout['pbkA']['nchild'] = 2
-        data_layout['pbkB']['nchild'] = 1
-        data_layout['pbkC']['nchild'] = 0
-        data_layout['pbkD']['nchild'] = 0
-        data_layout['pbkE']['nchild'] = 0
-
-        data_layout['pbkA']['stsize'] = 3.0
-        data_layout['pbkB']['stsize'] = 1.0
-        data_layout['pbkC']['stsize'] = 1.0
-        data_layout['pbkD']['stsize'] = 1.0
-        data_layout['pbkE']['stsize'] = 1.0
-
-        data_layout['pbkA']['span'] = 2 * math.pi
-
-        ExplorerScene._set_subtree_spans(self.test_graph, data_layout, 'pbkA')
-        self.assertAlmostEqual(data_layout['pbkA']['span'], 2 * math.pi)
-        self.assertAlmostEqual(data_layout['pbkB']['span'], 2 / 3 * math.pi)
-        self.assertAlmostEqual(data_layout['pbkC']['span'], 2 / 3 * math.pi)
-        self.assertAlmostEqual(data_layout['pbkD']['span'], 2 / 3 * math.pi)
-        self.assertAlmostEqual(data_layout['pbkE']['span'], 2 / 3 * math.pi)
-
-    @patch('networkx.MultiDiGraph')
-    def test_set_subtree_position(self, mock_graph):
-        # We mock the edges generator to ensure the order in which they appear
-        undirected = Mock('networkx.MultiDiGraph')
-        undirected.edges = Mock(return_value=self.certifications)
-        mock_graph.to_undirected = Mock(return_value=undirected)
-        data_layout = {}
-
-        for pubkey in self.identities_pubkeys:
-            data_layout[pubkey] = {
-                'theta': None
-            }
-
-        data_layout['pbkA']['sparent'] = None
-        data_layout['pbkB']['sparent'] = 'pbkA'
-        data_layout['pbkC']['sparent'] = 'pbkB'
-        data_layout['pbkD']['sparent'] = 'pbkA'
-        data_layout['pbkE']['sparent'] = 'pbkA'
-
-        data_layout['pbkA']['nchild'] = 2
-        data_layout['pbkB']['nchild'] = 1
-        data_layout['pbkC']['nchild'] = 0
-        data_layout['pbkD']['nchild'] = 0
-        data_layout['pbkE']['nchild'] = 0
-
-        data_layout['pbkA']['span'] = 2 * math.pi
-        data_layout['pbkB']['span'] = 2 / 3 * math.pi
-        data_layout['pbkC']['span'] = 2 / 3 * math.pi
-        data_layout['pbkD']['span'] = 2 / 3 * math.pi
-        data_layout['pbkE']['span'] = 2 / 3 * math.pi
-
-        data_layout['pbkA']['theta'] = 0.0
-        ExplorerScene._set_positions(mock_graph, data_layout, 'pbkA')
-        self.assertAlmostEqual(data_layout['pbkA']['theta'], 0.0)
-        self.assertAlmostEqual(data_layout['pbkB']['theta'], 1 / 3 * math.pi)
-        self.assertAlmostEqual(data_layout['pbkC']['theta'], 1 / 3 * math.pi)
-        self.assertAlmostEqual(data_layout['pbkD']['theta'], math.pi)
-        self.assertAlmostEqual(data_layout['pbkE']['theta'], 5 / 3 * math.pi)
-
-    @patch('networkx.MultiDiGraph')
-    @patch('networkx.MultiGraph')
-    @patch('networkx.shortest_path_length', return_value={'pbkA': 0, 'pbkB': 1, 'pbkC': 2, 'pbkD': 1, 'pbkE': 1})
-    def test_twopi_layout(self, mock_graph, mock_undirected, mock_paths):
-        # We mock the edges generator to ensure the order in which they appear
-        mock_graph.edges = Mock(return_value=self.certifications)
-        mock_graph.nodes = Mock(return_value=self.identities_pubkeys)
-        mock_graph.to_undirected = Mock(return_value=mock_undirected)
-        mock_undirected.nodes = Mock(return_value=self.identities_pubkeys)
-        mock_undirected.edges = Mock(return_value=self.certifications)
-
-        pos = ExplorerScene.twopi_layout(mock_graph, 'pbkA')
-
-        self.assertAlmostEqual(pos['pbkA'][0], 1 * math.cos(0.0) * 100)
-        self.assertAlmostEqual(pos['pbkB'][0], 2 * math.cos(1 / 3 * math.pi) * 100)
-        self.assertAlmostEqual(pos['pbkC'][0], 3 * math.cos(1 / 3 * math.pi) * 100)
-        self.assertAlmostEqual(pos['pbkD'][0], 2 * math.cos(math.pi) * 100)
-        self.assertAlmostEqual(pos['pbkE'][0], 2 * math.cos(5 / 3 * math.pi) * 100)
-
-        self.assertAlmostEqual(pos['pbkA'][1], 1 * math.sin(0.0) * 100)
-        self.assertAlmostEqual(pos['pbkB'][1], 2 * math.sin(1 / 3 * math.pi) * 100)
-        self.assertAlmostEqual(pos['pbkC'][1], 3 * math.sin(1 / 3 * math.pi) * 100)
-        self.assertAlmostEqual(pos['pbkD'][1], 2 * math.sin(math.pi) * 100)
-        self.assertAlmostEqual(pos['pbkE'][1], 2 * math.sin(5 / 3 * math.pi) * 100)
\ No newline at end of file
diff --git a/src/sakia/tests/unit/gui/views/test_wot_edge.py b/src/sakia/tests/unit/gui/views/test_wot_edge.py
deleted file mode 100644
index 6c0632b97e15f9e5eace680549297129cec79ba9..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/views/test_wot_edge.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import unittest
-from unittest.mock import patch
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.gui.views.edges import WotEdge
-from sakia.core.graph.constants import EdgeStatus
-
-
-class TestWotEdge(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_create_wot_edge(self):
-        metadata = {
-            'status': EdgeStatus.STRONG,
-            'confirmation_text': "0/6"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            edge = WotEdge("A", "B", metadata, nx_pos)
-            self.assertEqual(edge.source, "A")
-            self.assertEqual(edge.destination, "B")
-            self.assertEqual(edge.destination_point.x(), 10)
-            self.assertEqual(edge.destination_point.y(), 20)
-            self.assertEqual(edge.source_point.x(), 0)
-            self.assertEqual(edge.source_point.y(), 5)
-            self.assertEqual(edge.status, EdgeStatus.STRONG)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('PyQt5.QtGui.QPainter')
-    @patch('PyQt5.QtWidgets.QWidget')
-    def test_paint(self, painter, widget):
-        metadata = {
-            'status': EdgeStatus.STRONG,
-            'confirmation_text': "0/6"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-
-        async def exec_test():
-            edge = WotEdge("A", "B", metadata, nx_pos)
-            edge.paint(painter, 0, widget)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('PyQt5.QtGui.QPainter')
-    @patch('PyQt5.QtWidgets.QWidget')
-    def test_bounding_rect(self, painter, widget):
-        metadata = {
-            'status': EdgeStatus.STRONG,
-            'confirmation_text': "0/6"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-
-        async def exec_test():
-            edge = WotEdge("A", "B", metadata, nx_pos)
-            bounding_rect = edge.boundingRect()
-            self.assertAlmostEqual(bounding_rect.x(), -3.0, delta=5)
-            self.assertAlmostEqual(bounding_rect.y(), 2.0, delta=5)
-            self.assertAlmostEqual(bounding_rect.width(), 16.0, delta=5)
-            self.assertAlmostEqual(bounding_rect.height(), 21.0, delta=5)
-
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/gui/views/test_wot_node.py b/src/sakia/tests/unit/gui/views/test_wot_node.py
deleted file mode 100644
index 9577447d7408b2b06cdc323781bc56919c1e5ff7..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/gui/views/test_wot_node.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import unittest
-from unittest.mock import patch
-from PyQt5.QtCore import QLocale
-from PyQt5.QtGui import QPainter
-from PyQt5.QtWidgets import QStyleOptionGraphicsItem, QWidget
-from sakia.tests import QuamashTest
-from sakia.gui.views.nodes import WotNode
-from sakia.core.graph.constants import NodeStatus
-
-
-class TestWotNode(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_create_wot_node(self):
-        metadata = {
-            'status': NodeStatus.NEUTRAL,
-            'text': "UserA",
-            'tooltip': "TestTooltip"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            node = WotNode(("A", metadata), nx_pos)
-            self.assertEqual(node.id, "A")
-            self.assertEqual(node.metadata['status'], NodeStatus.NEUTRAL)
-            self.assertEqual(node.x(), 0)
-            self.assertEqual(node.y(), 5)
-            self.assertEqual(node.status_wallet, False)
-            self.assertEqual(node.status_member, True)
-            self.assertEqual(node.text, "UserA")
-            self.assertEqual(node.toolTip(), "UserA - TestTooltip")
-
-        self.lp.run_until_complete(exec_test())
-
-    def test_paint(self):
-        painter = QPainter()
-        widget = QWidget()
-        metadata = {
-            'status': NodeStatus.NEUTRAL,
-            'text': "UserA",
-            'tooltip': "TestTooltip"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            node = WotNode(("A", metadata), nx_pos)
-            node.paint(painter, QStyleOptionGraphicsItem(), widget)
-
-        self.lp.run_until_complete(exec_test())
-
-    @patch('PyQt5.QtGui.QPainter', spec=QPainter)
-    @patch('PyQt5.QtWidgets.QWidget')
-    def test_bounding_rect(self, painter, widget):
-        metadata = {
-            'status': NodeStatus.NEUTRAL,
-            'text': "A",
-            'tooltip': "TestTooltip"
-        }
-        nx_pos = {
-            "A": (0, 5),
-            "B": (10, 20)
-        }
-        async def exec_test():
-            node = WotNode(("A", metadata), nx_pos)
-            bounding_rect = node.boundingRect()
-            self.assertAlmostEqual(bounding_rect.x(), -0.5, delta=1)
-            self.assertAlmostEqual(bounding_rect.y(), -0.5, delta=1)
-            self.assertAlmostEqual(bounding_rect.width(), 19.59375, delta=15)
-            self.assertAlmostEqual(bounding_rect.height(), 37.0, delta=15)
-
-        self.lp.run_until_complete(exec_test())
diff --git a/src/sakia/tests/unit/tools/test_decorators.py b/src/sakia/tests/unit/tools/test_decorators.py
deleted file mode 100644
index 60a5eb33c64ddce10f015a2d7ac6ef6ecb169d30..0000000000000000000000000000000000000000
--- a/src/sakia/tests/unit/tools/test_decorators.py
+++ /dev/null
@@ -1,114 +0,0 @@
-import unittest
-import asyncio
-from PyQt5.QtCore import QLocale
-from sakia.tests import QuamashTest
-from sakia.tools.decorators import asyncify, once_at_a_time, cancel_once_task
-
-
-class TestDecorators(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_run_only_once(self):
-        class TaskRunner:
-            def __init__(self):
-                pass
-
-            @once_at_a_time
-            @asyncify
-            async def some_long_task(self, name, callback):
-                await asyncio.sleep(1)
-                callback(name)
-
-        task_runner = TaskRunner()
-        calls = {'A': 0, 'B': 0, 'C': 0}
-
-        def incrementer(name):
-            nonlocal calls
-            calls[name] += 1
-
-        async def exec_test():
-            await asyncio.sleep(3)
-
-        self.lp.call_soon(lambda: task_runner.some_long_task("A", incrementer))
-        self.lp.call_soon(lambda: task_runner.some_long_task("B", incrementer))
-        self.lp.call_soon(lambda: task_runner.some_long_task("C", incrementer))
-        self.lp.run_until_complete(exec_test())
-        self.assertEqual(calls["A"], 0)
-        self.assertEqual(calls["B"], 0)
-        self.assertEqual(calls["C"], 1)
-
-    def test_cancel_once(self):
-        class TaskRunner:
-            def __init__(self):
-                pass
-
-            @once_at_a_time
-            @asyncify
-            async def some_long_task(self, name, callback):
-                await asyncio.sleep(1)
-                callback(name)
-                await asyncio.sleep(1)
-                callback(name)
-
-            def cancel_long_task(self):
-                cancel_once_task(self, self.some_long_task)
-
-        task_runner = TaskRunner()
-        calls = {'A': 0, 'B': 0}
-
-        def incrementer(name):
-            nonlocal calls
-            calls[name] += 1
-
-        async def exec_test():
-            await asyncio.sleep(3)
-
-        self.lp.call_soon(lambda: task_runner.some_long_task("A", incrementer))
-        self.lp.call_soon(lambda: task_runner.some_long_task("B", incrementer))
-        self.lp.call_later(1.5, lambda: task_runner.cancel_long_task())
-        self.lp.run_until_complete(exec_test())
-        self.assertEqual(calls["A"], 0)
-        self.assertEqual(calls["B"], 1)
-
-    def test_cancel_once_two_times(self):
-        class TaskRunner:
-            def __init__(self):
-                pass
-
-            @once_at_a_time
-            @asyncify
-            async def some_long_task(self, name, callback):
-                await asyncio.sleep(1)
-                callback(name)
-                await asyncio.sleep(1)
-                callback(name)
-
-            def cancel_long_task(self):
-                cancel_once_task(self, self.some_long_task)
-
-        task_runner = TaskRunner()
-        calls = {'A': 0, 'B': 0, 'C': 0, 'D': 0}
-
-        def incrementer(name):
-            nonlocal calls
-            calls[name] += 1
-
-        async def exec_test():
-            await asyncio.sleep(6)
-
-        self.lp.call_soon(lambda: task_runner.some_long_task("A", incrementer))
-        self.lp.call_soon(lambda: task_runner.some_long_task("B", incrementer))
-        self.lp.call_later(1.5, lambda: task_runner.cancel_long_task())
-        self.lp.call_later(2, lambda: task_runner.some_long_task("C", incrementer))
-        self.lp.call_later(2.1, lambda: task_runner.some_long_task("D", incrementer))
-        self.lp.call_later(3.5, lambda: task_runner.cancel_long_task())
-        self.lp.run_until_complete(exec_test())
-        self.assertEqual(calls["A"], 0)
-        self.assertEqual(calls["B"], 1)
-        self.assertEqual(calls["C"], 0)
-        self.assertEqual(calls["D"], 1)
diff --git a/src/sakia/tools/exceptions.py b/src/sakia/tools/exceptions.py
deleted file mode 100644
index 6044c9b6a354c40bc7444303ebd5f91adf8ebcd6..0000000000000000000000000000000000000000
--- a/src/sakia/tools/exceptions.py
+++ /dev/null
@@ -1,190 +0,0 @@
-"""
-Created on 9 févr. 2014
-
-@author: inso
-"""
-
-
-class Error(Exception):
-
-    def __init__(self, message):
-        """
-        Constructor
-        """
-        self.message = "Error : " + message
-
-    def __str__(self):
-        return self.message
-
-
-class NotMemberOfCommunityError(Error):
-
-    """
-    Exception raised when adding a community the account is not a member of
-    """
-
-    def __init__(self, account, community):
-        """
-        Constructor
-        """
-        super() \
-            .__init__(account + " is not a member of " + community)
-
-
-class LookupFailureError(Error):
-
-    """
-    Exception raised when looking for a person in a community
-    who isnt present in key list
-    """
-
-    def __init__(self, value, community):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "Person looked by {0} in {1} not found ".format(value, community))
-
-
-class MembershipNotFoundError(Error):
-
-    """
-    Exception raised when looking for a person in a community
-    who isnt present in key list
-    """
-
-    def __init__(self, value, community):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "Membership searched by " +
-            value +
-            " in " +
-            community +
-            " not found ")
-
-
-class AlgorithmNotImplemented(Error):
-
-    """
-    Exception raised when a coin uses an algorithm not known
-    """
-
-    def __init__(self, algo_name):
-        """
-        Constructor
-        """
-        super() \
-            .__init__("Algorithm " + algo_name + " not implemented.")
-
-
-class KeyAlreadyUsed(Error):
-
-    """
-    Exception raised trying to add an account using
-    a key already used for another account.
-    """
-
-    def __init__(self, new_account, keyid, found_account):
-        """
-        Constructor
-        """
-        super() .__init__(
-"""Cannot add account {0} :
-the key {1} is already used by {2}""".format(new_account,
-                                             keyid,
-                                             found_account)
-            )
-
-
-class NameAlreadyExists(Error):
-
-    """
-    Exception raised trying to add an account using
-    a key already used for another account.
-    """
-
-    def __init__(self, account_name):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "Cannot add account " +
-            account_name +
-            " the name already exists")
-
-
-class BadAccountFile(Error):
-
-    """
-    Exception raised trying to add an account using
-    a key already used for another account.
-    """
-
-    def __init__(self, path):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "File " + path + " is not an account file")
-
-
-class NotEnoughMoneyError(Error):
-
-    """
-    Exception raised trying to add an account using
-    a key already used for another account.
-    """
-
-    def __init__(self, available, currency, nb_inputs, requested):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "Only {0} {1} available in {2} sources, needs {3}"
-            .format(available,
-                    currency,
-                    nb_inputs,
-                    requested))
-
-
-class NoPeerAvailable(Error):
-    """
-    Exception raised when a community doesn't have any
-    peer available.
-    """
-    def __init__(self, currency, nbpeers):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "No peer answered in {0} community ({1} peers available)"
-            .format(currency, nbpeers))
-
-
-class InvalidNodeCurrency(Error):
-    """
-    Exception raised when a node doesn't use the intended currency
-    """
-    def __init__(self, currency, node_currency):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "Node is working for {0} currency, but should be {1}"
-            .format(node_currency, currency))
-
-
-class ContactAlreadyExists(Error):
-    """
-    Exception raised when a community doesn't have any
-    peer available.
-    """
-    def __init__(self, new_contact, already_contact):
-        """
-        Constructor
-        """
-        super() .__init__(
-            "Cannot add {0}, he/she has the same pubkey as {1} contact"
-            .format(new_contact, already_contact))
diff --git a/src/sakia/tools/__init__.py b/tests/__init__.py
similarity index 100%
rename from src/sakia/tools/__init__.py
rename to tests/__init__.py
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2cbb2e651204380e57f24d099c3f106faa4f130
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,223 @@
+import pytest
+import asyncio
+import quamash
+import sqlite3
+import mirage
+import sys
+import os
+
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
+
+from duniterpy.documents import BlockUID
+from sakia.app import Application
+from sakia.options import SakiaOptions
+from sakia.data.files import AppDataFile
+from sakia.data.entities import *
+from sakia.data.repositories import *
+from sakia.services import DocumentsService
+
+_application_ = []
+
+
+@pytest.yield_fixture
+def event_loop():
+    qapplication = get_application()
+    loop = quamash.QSelectorEventLoop(qapplication)
+    exceptions = []
+    loop.set_exception_handler(lambda l, c: unitttest_exception_handler(exceptions, l, c))
+    yield loop
+    try:
+        loop.close()
+    finally:
+        asyncio.set_event_loop(None)
+
+    for exc in exceptions:
+        raise exc
+
+
+@pytest.fixture
+def meta_repo():
+    sqlite3.register_adapter(BlockUID, str)
+    sqlite3.register_adapter(bool, int)
+    sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v)))
+    con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)
+    meta_repo = SakiaDatabase(con,
+                              ConnectionsRepo(con), IdentitiesRepo(con),
+                              BlockchainsRepo(con), CertificationsRepo(con), TransactionsRepo(con),
+                              NodesRepo(con), SourcesRepo(con), DividendsRepo(con))
+    meta_repo.prepare()
+    meta_repo.upgrade_database()
+    return meta_repo
+
+
+@pytest.fixture
+def sakia_options(tmpdir):
+    return SakiaOptions(tmpdir.dirname)
+
+
+@pytest.fixture
+def app_data(sakia_options):
+    return AppDataFile.in_config_path(sakia_options.config_path).load_or_init()
+
+
+@pytest.fixture
+def user_parameters():
+    return UserParameters()
+
+@pytest.fixture
+def application(event_loop, meta_repo, sakia_options, app_data, user_parameters):
+    app = Application(qapp=get_application(),
+                       loop=event_loop,
+                       options=sakia_options,
+                       app_data=app_data,
+                       parameters=user_parameters,
+                       db=meta_repo)
+    app.documents_service = DocumentsService.instanciate(app)
+    return app
+
+
+@pytest.fixture
+def fake_server(event_loop):
+    return event_loop.run_until_complete(mirage.Node.start(None, "test_currency", "12356", "123456", event_loop))
+
+
+@pytest.fixture
+def alice():
+    return mirage.User.create("test_currency", "alice", "alicesalt", "alicepassword", BlockUID.empty())
+
+
+@pytest.fixture
+def bob():
+    return mirage.User.create("test_currency", "bob", "bobsalt", "bobpassword", BlockUID.empty())
+
+
+@pytest.fixture
+def wrong_bob_uid():
+    return mirage.User.create("test_currency", "wrong_bob", "bobsalt", "bobpassword", BlockUID.empty())
+
+
+@pytest.fixture
+def wrong_bob_pubkey():
+    return mirage.User.create("test_currency", "bob", "wrongbobsalt", "bobpassword", BlockUID.empty())
+
+
+@pytest.fixture
+def simple_fake_server(fake_server, alice, bob):
+    fake_server.forge.push(alice.identity())
+    fake_server.forge.push(bob.identity())
+    fake_server.forge.push(alice.join(BlockUID.empty()))
+    fake_server.forge.push(bob.join(BlockUID.empty()))
+    fake_server.forge.push(alice.certify(bob, BlockUID.empty()))
+    fake_server.forge.push(bob.certify(alice, BlockUID.empty()))
+    fake_server.forge.forge_block()
+    fake_server.forge.set_member(alice.key.pubkey, True)
+    fake_server.forge.set_member(bob.key.pubkey, True)
+    for i in range(0, 10):
+        new_user = mirage.User.create("test_currency", "user{0}".format(i),
+                                       "salt{0}".format(i), "password{0}".format(i),
+                                      fake_server.forge.blocks[-1].blockUID)
+        fake_server.forge.push(new_user.identity())
+        fake_server.forge.push(new_user.join(fake_server.forge.blocks[-1].blockUID))
+        fake_server.forge.forge_block()
+        fake_server.forge.set_member(new_user.key.pubkey, True)
+        fake_server.forge.generate_dividend()
+        fake_server.forge.forge_block()
+    return fake_server
+
+
+@pytest.fixture
+def application_with_one_connection(application, simple_fake_server, bob):
+    current_block = simple_fake_server.forge.blocks[-1]
+    last_ud_block = [b for b in simple_fake_server.forge.blocks if b.ud][-1]
+    previous_ud_block = [b for b in simple_fake_server.forge.blocks if b.ud][-2]
+    origin_block = simple_fake_server.forge.blocks[0]
+    connection = Connection(currency="test_currency",
+                      pubkey=bob.key.pubkey,
+                      salt=bob.salt, uid=bob.uid,
+                      scrypt_N=4096, scrypt_r=4, scrypt_p=2,
+                      blockstamp=bob.blockstamp)
+    application.db.connections_repo.insert(connection)
+    blockchain_parameters = BlockchainParameters(*origin_block.parameters)
+    blockchain = Blockchain(parameters=blockchain_parameters,
+                            current_buid=current_block.blockUID,
+                            current_members_count=current_block.members_count,
+                            current_mass=simple_fake_server.forge.monetary_mass(current_block.number),
+                            median_time=current_block.mediantime,
+                            last_members_count=previous_ud_block.members_count,
+                            last_ud=last_ud_block.ud,
+                            last_ud_base=last_ud_block.unit_base,
+                            last_ud_time=last_ud_block.mediantime,
+                            previous_mass=simple_fake_server.forge.monetary_mass(previous_ud_block.number),
+                            previous_members_count=previous_ud_block.members_count,
+                            previous_ud=previous_ud_block.ud,
+                            previous_ud_base=previous_ud_block.unit_base,
+                            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]
+    bob_identity = Identity(currency=simple_fake_server.forge.currency,
+                            pubkey=bob.key.pubkey,
+                            uid=bob.uid,
+                            blockstamp=bob_blockstamp,
+                            signature=bob_user_identity.signature,
+                            timestamp=simple_fake_server.forge.blocks[bob_blockstamp.number].mediantime,
+                            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,
+                            membership_type=bob_ms.type,
+                            membership_written_on=simple_fake_server.forge.blocks[bob_ms.written_on].number)
+    application.db.identities_repo.insert(bob_identity)
+    application.instanciate_services()
+    application.db.nodes_repo.insert(Node(currency=simple_fake_server.forge.currency,
+                                          pubkey=simple_fake_server.forge.key.pubkey,
+                                          endpoints=simple_fake_server.peer_doc().endpoints,
+                                          peer_blockstamp=simple_fake_server.peer_doc().blockUID,
+                                          uid="",
+                                          current_buid=BlockUID(current_block.number, current_block.sha_hash),
+                                          current_ts=current_block.mediantime,
+                                          state=Node.ONLINE,
+                                          software="duniter",
+                                          version="0.40.2"))
+    application.switch_language()
+
+    return application
+
+
+def unitttest_exception_handler(exceptions, loop, context):
+    """
+    An exception handler which exists the program if the exception
+    was not catch
+    :param loop: the asyncio loop
+    :param context: the exception context
+    """
+    if 'exception' in context:
+        exception = context['exception']
+    else:
+        exception = BaseException(context['message'])
+    exceptions.append(exception)
+
+
+def get_application():
+    """Get the singleton QApplication"""
+    from quamash import QApplication
+    if not len(_application_):
+        application = QApplication.instance()
+        if not application:
+            import sys
+            application = QApplication(sys.argv)
+        application.setQuitOnLastWindowClosed(False)
+        _application_.append(application)
+    return _application_[0]
+
diff --git a/res/ui/tx_lifecycle.png b/tests/functional/__init__.py
similarity index 100%
rename from res/ui/tx_lifecycle.png
rename to tests/functional/__init__.py
diff --git a/tests/functional/test_certification_dialog.py b/tests/functional/test_certification_dialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..0dbd1e0cefa39ba431dc128ee147e76f9b4f5e97
--- /dev/null
+++ b/tests/functional/test_certification_dialog.py
@@ -0,0 +1,42 @@
+import asyncio
+import pytest
+from duniterpy.documents import Certification
+from PyQt5.QtCore import QLocale, Qt, QEvent
+from PyQt5.QtTest import QTest
+from PyQt5.QtWidgets import QDialogButtonBox, QApplication, QMessageBox
+from sakia.gui.dialogs.certification.controller import CertificationController
+
+
+@pytest.mark.asyncio
+async def test_certification_init_community(application_with_one_connection, fake_server, bob, alice):
+    certification_dialog = CertificationController.create(None, application_with_one_connection)
+
+    def close_dialog():
+        if certification_dialog.view.isVisible():
+            certification_dialog.view.close()
+
+    async def exec_test():
+        certification_dialog.model.connection.password = bob.password
+        QTest.keyClicks(certification_dialog.view.search_user.combobox_search.lineEdit(), "nothing")
+        await asyncio.sleep(1)
+        certification_dialog.search_user.view.search()
+        await asyncio.sleep(1)
+        assert certification_dialog.user_information.model.identity is None
+        assert not certification_dialog.view.button_box.button(QDialogButtonBox.Ok).isEnabled()
+        certification_dialog.view.search_user.combobox_search.lineEdit().clear()
+        QTest.keyClicks(certification_dialog.view.search_user.combobox_search.lineEdit(), alice.key.pubkey)
+        await asyncio.sleep(0.1)
+        certification_dialog.search_user.view.search()
+        await asyncio.sleep(0.1)
+        certification_dialog.search_user.view.node_selected.emit(0)
+        await asyncio.sleep(1)
+        assert certification_dialog.user_information.model.identity.uid == "alice"
+        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 isinstance(fake_server.forge.pool[0], Certification)
+
+    application_with_one_connection.loop.call_later(10, close_dialog)
+    asyncio.ensure_future(exec_test())
+    await certification_dialog.async_exec()
+    await fake_server.close()
diff --git a/tests/functional/test_connection_cfg_dialog.py b/tests/functional/test_connection_cfg_dialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb831912765a57585bb20ae5ebb7d66c01afd3bc
--- /dev/null
+++ b/tests/functional/test_connection_cfg_dialog.py
@@ -0,0 +1,142 @@
+import asyncio
+import pytest
+from PyQt5.QtCore import Qt
+from PyQt5.QtTest import QTest
+from sakia.data.processors import ConnectionsProcessor
+from sakia.gui.dialogs.connection_cfg import ConnectionConfigController
+
+
+def assert_key_parameters_behaviour(connection_config_dialog, user):
+    QTest.keyClicks(connection_config_dialog.view.edit_uid, user.uid)
+    QTest.keyClicks(connection_config_dialog.view.edit_salt, user.salt)
+    QTest.keyClicks(connection_config_dialog.view.edit_salt_bis, user.salt)
+    assert connection_config_dialog.view.button_next.isEnabled() is False
+    assert connection_config_dialog.view.button_generate.isEnabled() is False
+    QTest.keyClicks(connection_config_dialog.view.edit_password, user.password)
+    connection_config_dialog.view.button_next.isEnabled() is False
+    connection_config_dialog.view.button_generate.isEnabled() is False
+    QTest.keyClicks(connection_config_dialog.view.edit_password_repeat, user.password + "wrong")
+    assert connection_config_dialog.view.button_next.isEnabled() is False
+    assert connection_config_dialog.view.button_generate.isEnabled() is False
+    connection_config_dialog.view.edit_password_repeat.setText("")
+    QTest.keyClicks(connection_config_dialog.view.edit_password_repeat, user.password)
+    assert connection_config_dialog.view.button_next.isEnabled() is True
+    assert connection_config_dialog.view.button_generate.isEnabled() is True
+    QTest.mouseClick(connection_config_dialog.view.button_generate, Qt.LeftButton)
+    assert connection_config_dialog.view.label_info.text() == user.key.pubkey
+
+
+@pytest.mark.asyncio
+async def test_register_empty_blockchain(application, fake_server, bob):
+    connection_config_dialog = ConnectionConfigController.create_connection(None, application)
+
+    def close_dialog():
+        if connection_config_dialog.view.isVisible():
+            connection_config_dialog.view.close()
+
+    async def exec_test():
+        QTest.keyClicks(connection_config_dialog.view.edit_server, fake_server.peer_doc().endpoints[0].ipv4)
+        connection_config_dialog.view.spinbox_port.setValue(fake_server.peer_doc().endpoints[0].port)
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_node
+        await asyncio.sleep(0.6)
+        QTest.mouseClick(connection_config_dialog.view.button_register, Qt.LeftButton)
+        await asyncio.sleep(0.6)
+
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_connection
+        assert_key_parameters_behaviour(connection_config_dialog, bob)
+        QTest.mouseClick(connection_config_dialog.view.button_next, Qt.LeftButton)
+        connection_config_dialog.model.connection.password = bob.password
+        await asyncio.sleep(1)
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_services
+        assert len(ConnectionsProcessor.instanciate(application).connections(fake_server.forge.currency)) == 1
+
+    application.loop.call_later(10, close_dialog)
+    asyncio.ensure_future(exec_test())
+    await connection_config_dialog.async_exec()
+    await fake_server.close()
+
+
+@pytest.mark.asyncio
+async def test_connect(application, simple_fake_server, bob):
+    connection_config_dialog = ConnectionConfigController.create_connection(None, application)
+
+    def close_dialog():
+        if connection_config_dialog.view.isVisible():
+            connection_config_dialog.view.close()
+
+    async def exec_test():
+        QTest.keyClicks(connection_config_dialog.view.edit_server, simple_fake_server.peer_doc().endpoints[0].ipv4)
+        connection_config_dialog.view.spinbox_port.setValue(simple_fake_server.peer_doc().endpoints[0].port)
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_node
+        QTest.mouseClick(connection_config_dialog.view.button_connect, Qt.LeftButton)
+        await asyncio.sleep(1)
+
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_connection
+        assert_key_parameters_behaviour(connection_config_dialog, bob)
+        QTest.mouseClick(connection_config_dialog.view.button_next, Qt.LeftButton)
+        await asyncio.sleep(1)
+
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_services
+        assert len(ConnectionsProcessor.instanciate(application).connections(simple_fake_server.forge.currency)) == 1
+
+    application.loop.call_later(10, close_dialog)
+    asyncio.ensure_future(exec_test())
+    await connection_config_dialog.async_exec()
+    await simple_fake_server.close()
+
+
+@pytest.mark.asyncio
+async def test_connect_wrong_uid(application, simple_fake_server, wrong_bob_uid, bob):
+    connection_config_dialog = ConnectionConfigController.create_connection(None, application)
+
+    def close_dialog():
+        if connection_config_dialog.view.isVisible():
+            connection_config_dialog.view.close()
+
+    async def exec_test():
+        await asyncio.sleep(1)
+        QTest.keyClicks(connection_config_dialog.view.edit_server, simple_fake_server.peer_doc().endpoints[0].ipv4)
+        connection_config_dialog.view.spinbox_port.setValue(simple_fake_server.peer_doc().endpoints[0].port)
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_node
+        QTest.mouseClick(connection_config_dialog.view.button_connect, Qt.LeftButton)
+        await asyncio.sleep(1)
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_connection
+        assert_key_parameters_behaviour(connection_config_dialog, wrong_bob_uid)
+        QTest.mouseClick(connection_config_dialog.view.button_next, Qt.LeftButton)
+        assert connection_config_dialog.view.label_info.text(), """Your pubkey or UID is different on the network.
+Yours : {0}, the network : {1}""".format(wrong_bob_uid.uid, bob.uid)
+        connection_config_dialog.view.close()
+
+    application.loop.call_later(10, close_dialog)
+    asyncio.ensure_future(exec_test())
+    await connection_config_dialog.async_exec()
+    await simple_fake_server.close()
+
+
+@pytest.mark.asyncio
+async def test_connect_wrong_pubkey(application, simple_fake_server, wrong_bob_pubkey, bob):
+    connection_config_dialog = ConnectionConfigController.create_connection(None, application)
+
+    def close_dialog():
+        if connection_config_dialog.view.isVisible():
+            connection_config_dialog.view.close()
+
+    async def exec_test():
+        await asyncio.sleep(1)
+        QTest.keyClicks(connection_config_dialog.view.edit_server, simple_fake_server.peer_doc().endpoints[0].ipv4)
+        connection_config_dialog.view.spinbox_port.setValue(simple_fake_server.peer_doc().endpoints[0].port)
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_node
+        QTest.mouseClick(connection_config_dialog.view.button_connect, Qt.LeftButton)
+        await asyncio.sleep(1)
+        assert connection_config_dialog.view.stacked_pages.currentWidget() == connection_config_dialog.view.page_connection
+        assert_key_parameters_behaviour(connection_config_dialog, wrong_bob_pubkey)
+        QTest.mouseClick(connection_config_dialog.view.button_next, Qt.LeftButton)
+        assert connection_config_dialog.view.label_info.text(), """Your pubkey or UID is different on the network.
+Yours : {0}, the network : {1}""".format(wrong_bob_pubkey.pubkey, bob.pubkey)
+        connection_config_dialog.view.close()
+
+    application.loop.call_later(10, close_dialog)
+    asyncio.ensure_future(exec_test())
+    await connection_config_dialog.async_exec()
+    await simple_fake_server.close()
+
diff --git a/tests/functional/test_preferences_dialog.py b/tests/functional/test_preferences_dialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9e69371427dd063cae795b6dab7c202c0df523d
--- /dev/null
+++ b/tests/functional/test_preferences_dialog.py
@@ -0,0 +1,13 @@
+from sakia.gui.preferences import PreferencesDialog
+
+
+def test_preferences_default(application):
+    preferences_dialog = PreferencesDialog(application)
+    assert preferences_dialog.combo_language.currentText() == application.parameters.lang
+    assert preferences_dialog.combo_referential.currentIndex() == application.parameters.referential
+    assert preferences_dialog.checkbox_expertmode.isChecked() == application.parameters.expert_mode
+    assert preferences_dialog.checkbox_maximize.isChecked() == application.parameters.maximized
+    assert preferences_dialog.checkbox_notifications.isChecked() == application.parameters.notifications
+    assert preferences_dialog.checkbox_proxy.isChecked() == application.parameters.enable_proxy
+    assert preferences_dialog.edit_proxy_address.text() == application.parameters.proxy_address
+    assert preferences_dialog.spinbox_proxy_port.value() == application.parameters.proxy_port
diff --git a/tests/functional/test_transfer_dialog.py b/tests/functional/test_transfer_dialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..74a63db9cb32b88eb6c3ce43f8dad0abac573b2f
--- /dev/null
+++ b/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/tests/technical/__init__.py b/tests/technical/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/technical/test_identities_service.py b/tests/technical/test_identities_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2e2bb9cf26ae6a4fa92d9828edf8a379cb02209
--- /dev/null
+++ b/tests/technical/test_identities_service.py
@@ -0,0 +1,6 @@
+import pytest
+
+
+@pytest.mark.asyncio
+async def test_new_block_with_unknown_identities(application_with_one_connection, fake_server, bob, alice):
+    pass
\ No newline at end of file
diff --git a/tests/technical/test_transactions_service.py b/tests/technical/test_transactions_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..47bd10bda8b2e0085b22976ae2a67ced55b7d675
--- /dev/null
+++ b/tests/technical/test_transactions_service.py
@@ -0,0 +1,56 @@
+import pytest
+from sakia.data.entities import Transaction
+
+
+@pytest.mark.asyncio
+async def test_send_tx_then_validate(application_with_one_connection, fake_server, bob, alice):
+    tx_before_send = application_with_one_connection.transactions_services[fake_server.forge.currency].transfers(bob.key.pubkey)
+    bob_connection = application_with_one_connection.db.connections_repo.get_one(pubkey=bob.key.pubkey)
+    await application_with_one_connection.documents_service.send_money(bob_connection,
+                                                                 bob.password,
+                                                                 alice.key.pubkey, 10, 0, "Test comment")
+    tx_after_send = application_with_one_connection.transactions_services[fake_server.forge.currency].transfers(bob.key.pubkey)
+    assert len(tx_before_send) + 1 == len(tx_after_send)
+    assert tx_after_send[-1].state is Transaction.AWAITING
+    fake_server.forge.forge_block()
+    fake_server.forge.forge_block()
+    fake_server.forge.forge_block()
+    new_blocks = fake_server.forge.blocks[-3:]
+    await application_with_one_connection.transactions_services[fake_server.forge.currency].handle_new_blocks(new_blocks)
+    tx_after_parse = application_with_one_connection.transactions_services[fake_server.forge.currency].transfers(bob.key.pubkey)
+    assert tx_after_parse[-1].state is Transaction.VALIDATED
+    await fake_server.close()
+
+
+@pytest.mark.asyncio
+async def test_receive_tx(application_with_one_connection, fake_server, bob, alice):
+    tx_before_send = application_with_one_connection.transactions_services[fake_server.forge.currency].transfers(bob.key.pubkey)
+    fake_server.forge.push(alice.send_money(10, fake_server.forge.user_identities[alice.key.pubkey].sources, bob,
+                                            fake_server.forge.blocks[-1].blockUID, "Test receive"))
+    fake_server.forge.forge_block()
+    fake_server.forge.forge_block()
+    fake_server.forge.forge_block()
+    new_blocks = fake_server.forge.blocks[-3:]
+    await application_with_one_connection.transactions_services[fake_server.forge.currency].handle_new_blocks(new_blocks)
+    tx_after_parse = application_with_one_connection.transactions_services[fake_server.forge.currency].transfers(bob.key.pubkey)
+    assert tx_after_parse[-1].state is Transaction.VALIDATED
+    assert len(tx_before_send) + 1 == len(tx_after_parse)
+    await fake_server.close()
+
+
+@pytest.mark.asyncio
+async def test_issue_dividend(application_with_one_connection, fake_server, bob):
+    dividends_before_send = application_with_one_connection.transactions_services[fake_server.forge.currency].dividends(bob.key.pubkey)
+    fake_server.forge.forge_block()
+    fake_server.forge.generate_dividend()
+    fake_server.forge.forge_block()
+    fake_server.forge.forge_block()
+    fake_server.forge.generate_dividend()
+    fake_server.forge.forge_block()
+    fake_server.forge.forge_block()
+    new_blocks = fake_server.forge.blocks[-5:]
+    await application_with_one_connection.transactions_services[fake_server.forge.currency].handle_new_blocks(new_blocks)
+    dividends_after_parse = application_with_one_connection.transactions_services[fake_server.forge.currency].dividends(bob.key.pubkey)
+    assert len(dividends_before_send) + 2 == len(dividends_after_parse)
+    await fake_server.close()
+
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/unit/data/__init__.py b/tests/unit/data/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/unit/data/test_appdata_file.py b/tests/unit/data/test_appdata_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..99912fc5f0c3befb719c954adcb1d36aa6b66f7d
--- /dev/null
+++ b/tests/unit/data/test_appdata_file.py
@@ -0,0 +1,16 @@
+from sakia.data.entities import AppData
+from sakia.data.files import AppDataFile
+import tempfile
+import unittest
+import os
+
+
+class TestAppDataFile(unittest.TestCase):
+    def test_init_save_load(self):
+        file = os.path.join(tempfile.mkdtemp(), "params.json")
+        app_data = AppData()
+        app_data_file = AppDataFile(file)
+        app_data.profiles.append("default")
+        app_data_file.save(app_data)
+        app_data_2 = app_data_file.load_or_init()
+        self.assertEqual(app_data, app_data_2)
diff --git a/tests/unit/data/test_blockchains_repo.py b/tests/unit/data/test_blockchains_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..869683a6a33d9097bfec4bc26f4437c59becfaf2
--- /dev/null
+++ b/tests/unit/data/test_blockchains_repo.py
@@ -0,0 +1,187 @@
+from duniterpy.documents import BlockUID
+
+from sakia.data.entities import Blockchain, BlockchainParameters
+from sakia.data.repositories import BlockchainsRepo
+
+
+def test_add_get_drop_blockchain(meta_repo):
+    blockchains_repo = BlockchainsRepo(meta_repo.conn)
+    blockchains_repo.insert(Blockchain(
+        parameters=BlockchainParameters(
+            0.1,
+            86400,
+            100000,
+            10800,
+            40,
+            2629800,
+            31557600,
+            1,
+            0.9,
+            604800,
+            5,
+            12,
+            300,
+            25,
+            10,
+            0.66),
+        current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+        current_members_count = 10,
+        current_mass = 1000000,
+        median_time = 86400,
+        last_members_count = 5,
+        last_ud = 100000,
+        last_ud_base = 0,
+        last_ud_time = 86400,
+        previous_mass = 999999,
+        previous_members_count = 10,
+        previous_ud = 6543,
+        previous_ud_base = 0,
+        previous_ud_time = 86400,
+        currency = "testcurrency"
+    ))
+    blockchain = blockchains_repo.get_one(currency="testcurrency")
+    assert blockchain.parameters == BlockchainParameters(
+            0.1,
+            86400,
+            100000,
+            10800,
+            40,
+            2629800,
+            31557600,
+            1,
+            0.9,
+            604800,
+            5,
+            12,
+            300,
+            25,
+            10,
+            0.66)
+    assert blockchain.currency == "testcurrency"
+    assert blockchain.current_buid == BlockUID(20, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
+    assert blockchain.current_members_count == 10
+
+    blockchains_repo.drop(blockchain)
+    blockchain = blockchains_repo.get_one(currency="testcurrency")
+    assert blockchain is None
+
+def test_add_get_multiple_blockchain(meta_repo):
+    blockchains_repo = BlockchainsRepo(meta_repo.conn)
+    blockchains_repo.insert(Blockchain(
+        parameters=BlockchainParameters(
+            0.1,
+            86400,
+            100000,
+            10800,
+            40,
+            2629800,
+            31557600,
+            1,
+            0.9,
+            604800,
+            5,
+            12,
+            300,
+            25,
+            10,
+            0.66),
+
+        current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+        current_members_count = 10,
+        current_mass = 1000000,
+        median_time = 86400,
+        last_members_count = 5,
+        last_ud = 100000,
+        last_ud_base = 0,
+        last_ud_time = 86400,
+        previous_mass = 999999,
+        previous_members_count = 10,
+        previous_ud = 6543,
+        previous_ud_base = 0,
+        previous_ud_time = 86400,
+        currency = "testcurrency"
+    ))
+    blockchains_repo.insert(Blockchain(
+        BlockchainParameters(
+            0.1,
+            86400 * 365,
+            100000,
+            10800,
+            40,
+            2629800,
+            31557600,
+            1,
+            0.9,
+            604800,
+            5,
+            12,
+            300,
+            25,
+            10,
+            0.66),
+        current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+        current_members_count = 20,
+        current_mass = 1000000,
+        median_time = 86400,
+        last_members_count = 5,
+        last_ud = 100000,
+        last_ud_base = 0,
+        last_ud_time = 86400,
+        previous_mass = 999999,
+        previous_members_count = 10,
+        previous_ud = 6543,
+        previous_ud_base = 0,
+        previous_ud_time = 86400,
+        currency = "testcurrency2"
+    ))
+
+    blockchains = blockchains_repo.get_all()
+    # result sorted by currency name by default
+    assert 86400 == blockchains[0].parameters.dt
+    assert "testcurrency" == blockchains[0].currency
+    assert 10 == blockchains[0].current_members_count
+
+    assert 86400*365 == blockchains[1].parameters.dt
+    assert "testcurrency2" == blockchains[1].currency
+    assert 20 == blockchains[1].current_members_count
+
+def test_add_update_blockchain(meta_repo):
+    blockchains_repo = BlockchainsRepo(meta_repo.conn)
+    blockchain = Blockchain(
+        BlockchainParameters(
+            0.1,
+            86400,
+            100000,
+            10800,
+            40,
+            2629800,
+            31557600,
+            1,
+            0.9,
+            604800,
+            5,
+            12,
+            300,
+            25,
+            10,
+            0.66),
+        current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+        current_members_count = 10,
+        current_mass = 1000000,
+        median_time = 86400,
+        last_members_count = 5,
+        last_ud = 100000,
+        last_ud_base = 0,
+        last_ud_time = 86400,
+        previous_mass = 999999,
+        previous_members_count = 10,
+        previous_ud = 6543,
+        previous_ud_base = 0,
+        previous_ud_time = 86400,
+        currency = "testcurrency"
+    )
+    blockchains_repo.insert(blockchain)
+    blockchain.current_members_count = 30
+    blockchains_repo.update(blockchain)
+    blockchain2 = blockchains_repo.get_one(currency="testcurrency")
+    assert 30 == blockchain2.current_members_count
diff --git a/tests/unit/data/test_certifications_repo.py b/tests/unit/data/test_certifications_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e76e40213f5d55894eeeeec067d7a50ffe9124c
--- /dev/null
+++ b/tests/unit/data/test_certifications_repo.py
@@ -0,0 +1,73 @@
+from sakia.data.repositories import CertificationsRepo
+from sakia.data.entities import Certification
+
+
+def test_add_get_drop_blockchain(meta_repo):
+    certifications_repo = CertificationsRepo(meta_repo.conn)
+    certifications_repo.insert(Certification("testcurrency",
+                                             "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                             "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                             20,
+                                             1473108382,
+                                             "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                             0))
+    certification = certifications_repo.get_one(currency="testcurrency",
+                                                certifier="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                                certified="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                                block=20)
+    assert certification.currency == "testcurrency"
+    assert certification.certifier == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
+    assert certification.certified == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
+    assert certification.block == 20
+    assert certification.timestamp == 1473108382
+    assert certification.signature == "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw=="
+    assert certification.written_on == 0
+    certifications_repo.drop(certification)
+    certification = certifications_repo.get_one(currency="testcurrency",
+                                       certifier="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                       certified="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                       block=20)
+    assert certification is None
+
+
+def test_add_get_multiple_certification(meta_repo):
+    certifications_repo = CertificationsRepo(meta_repo.conn)
+    certifications_repo.insert(Certification("testcurrency",
+                                             "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                             "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                             20, 1473108382,
+                                             "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                             22))
+    certifications_repo.insert(Certification("testcurrency",
+                                             "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                             "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                             101, 1473108382,
+                                             "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                             105))
+    certifications = certifications_repo.get_all(currency="testcurrency")
+    assert "testcurrency" in [i.currency for i in certifications]
+    assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in [i.certifier for i in certifications]
+    assert "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" in [i.certifier for i in certifications]
+    assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in [i.certified for i in certifications]
+    assert "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" in [i.certified for i in certifications]
+
+
+def test_add_update_certification(meta_repo):
+    certifications_repo = CertificationsRepo(meta_repo.conn)
+    certification = Certification("testcurrency",
+                             "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                             "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                             20,
+                             1473108382,
+                             "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                             0)
+
+    certifications_repo.insert(certification)
+    certification.written_on = 22
+    certifications_repo.update(certification)
+    cert2 = certifications_repo.get_one(currency="testcurrency",
+                                        certifier="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                        certified="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                        block=20)
+    assert cert2.written_on == 22
+
diff --git a/tests/unit/data/test_connections_repo.py b/tests/unit/data/test_connections_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f4911312a22f0519e3038e45d8652d9340b64b9
--- /dev/null
+++ b/tests/unit/data/test_connections_repo.py
@@ -0,0 +1,19 @@
+from sakia.data.repositories import ConnectionsRepo
+from sakia.data.entities import Connection
+
+def test_add_get_drop_connection(meta_repo):
+    connections_repo = ConnectionsRepo(meta_repo.conn)
+    connections_repo.insert(Connection("testcurrency",
+                                             "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                             "somesalt"))
+    connection = connections_repo.get_one(currency="testcurrency",
+                                       pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                       salt="somesalt")
+    assert connection.currency == "testcurrency"
+    assert connection.pubkey == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
+    assert connection.salt == "somesalt"
+    connections_repo.drop(connection)
+    connection = connections_repo.get_one(currency="testcurrency",
+                                       pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                       salt="somesalt")
+    assert connection is None
diff --git a/tests/unit/data/test_dividends_repo.py b/tests/unit/data/test_dividends_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..d211b08f288e0d35e32bf707eaefba14c1947e3d
--- /dev/null
+++ b/tests/unit/data/test_dividends_repo.py
@@ -0,0 +1,37 @@
+from sakia.data.repositories import DividendsRepo
+from sakia.data.entities import Dividend
+
+
+def test_add_get_drop_dividend(meta_repo):
+    dividends_repo = DividendsRepo(meta_repo.conn)
+    dividends_repo.insert(Dividend("testcurrency",
+                               "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                               3, 1346543453, 1565, 1))
+    dividend = dividends_repo.get_one(pubkey="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
+    assert dividend.currency == "testcurrency"
+    assert dividend.pubkey == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
+    assert dividend.timestamp == 1346543453
+    assert dividend.block_number == 3
+    assert dividend.base == 1
+    assert dividend.amount == 1565
+
+    dividends_repo.drop(dividend)
+    source = dividends_repo.get_one(pubkey="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
+    assert source is None
+
+
+def test_add_get_multiple_dividends(meta_repo):
+    dividends_repo = DividendsRepo(meta_repo.conn)
+    dividends_repo.insert(Dividend("testcurrency",
+                               "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                               3, 1346543453, 1565, 1))
+    dividends_repo.insert(Dividend("testcurrency",
+                               "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                               243, 4235252353, 45565, 2))
+    dividends = dividends_repo.get_all(currency="testcurrency", pubkey="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
+    assert "testcurrency" in [s.currency for s in dividends]
+    assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in [s.pubkey for s in dividends]
+    assert 4235252353 in [s.timestamp for s in dividends]
+    assert 1346543453 in [s.timestamp for s in dividends]
+    assert 45565 in [s.amount for s in dividends]
+    assert 1565 in [s.amount for s in dividends]
diff --git a/tests/unit/data/test_identies_repo.py b/tests/unit/data/test_identies_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..42fa6ad057b9ab7d2a4ac79d24087c67b951d89d
--- /dev/null
+++ b/tests/unit/data/test_identies_repo.py
@@ -0,0 +1,70 @@
+from sakia.data.repositories import IdentitiesRepo
+from sakia.data.entities import Identity
+from duniterpy.documents import BlockUID
+
+
+def test_add_get_drop_identity(meta_repo):
+    identities_repo = IdentitiesRepo(meta_repo.conn)
+    identities_repo.insert(Identity("testcurrency", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                    "john",
+                                    "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                                    "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                    1473108382))
+    identity = identities_repo.get_one(currency="testcurrency",
+                                       pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                       uid="john",
+                                       blockstamp=BlockUID(20,
+                                                "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
+                                       )
+    assert identity.currency == "testcurrency"
+    assert identity.pubkey == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
+    assert identity.uid == "john"
+    assert identity.blockstamp.number == 20
+    assert identity.blockstamp.sha_hash == "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"
+    assert identity.timestamp == 1473108382
+    assert identity.signature == "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw=="
+    assert identity.member == False
+    assert identity.membership_buid == BlockUID.empty()
+    assert identity.membership_timestamp == 0
+    assert identity.membership_written_on == 0
+    identities_repo.drop(identity)
+    identity = identities_repo.get_one(currency="testcurrency",
+                                       pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                       uid="john",
+                                       blockstamp=BlockUID(20,
+                                                "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
+                                        )
+    assert identity is None
+
+
+def test_add_get_multiple_identity(meta_repo):
+    identities_repo = IdentitiesRepo(meta_repo.conn)
+    identities_repo.insert(Identity("testcurrency", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                    "john",
+                                    "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                                    "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                    1473108382))
+    identities_repo.insert(Identity("testcurrency", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                    "doe",
+                                    "101-BAD49448A1AD73C978CEDCB8F137D20A5715EBAA739DAEF76B1E28EE67B2C00C",
+                                    "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                    1455433535))
+    identities = identities_repo.get_all(currency="testcurrency")
+    assert "testcurrency" in [i.currency for i in identities]
+    assert "john" in [i.uid for i in identities]
+    assert "doe" in [i.uid for i in identities]
+
+
+def test_add_update_identity(meta_repo):
+    identities_repo = IdentitiesRepo(meta_repo.conn)
+    identity = Identity("testcurrency", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                    "john",
+                                    "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                                    "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                    1473108382)
+    identities_repo.insert(identity)
+    identity.member = True
+    identities_repo.update(identity)
+    identity2 = identities_repo.get_one(currency="testcurrency",
+                                        pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ")
+    assert identity2.member is True
diff --git a/tests/unit/data/test_node_connector.py b/tests/unit/data/test_node_connector.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f0515aaf6f07c11484c3fbfac07d63490889d6f
--- /dev/null
+++ b/tests/unit/data/test_node_connector.py
@@ -0,0 +1,18 @@
+from duniterpy.documents import Peer
+from sakia.data.connectors import NodeConnector
+
+
+def test_from_peer():
+    peer = Peer.from_signed_raw("""Version: 2
+Type: Peer
+Currency: meta_brouzouf
+PublicKey: 8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU
+Block: 48698-000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8
+Endpoints:
+BASIC_MERKLED_API duniter.inso.ovh 80
+82o1sNCh1bLpUXU6nacbK48HBcA9Eu2sPkL1/3c2GtDPxBUZd2U2sb7DxwJ54n6ce9G0Oy7nd1hCxN3fS0oADw==
+""")
+    connector = NodeConnector.from_peer('meta_brouzouf', peer, None)
+    assert connector.node.pubkey == "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU"
+    assert connector.node.endpoints[0].inline() == "BASIC_MERKLED_API duniter.inso.ovh 80"
+    assert connector.node.currency == "meta_brouzouf"
diff --git a/tests/unit/data/test_nodes_repo.py b/tests/unit/data/test_nodes_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac83284f2442f52db2fd4be24cf4ff86fc36f85d
--- /dev/null
+++ b/tests/unit/data/test_nodes_repo.py
@@ -0,0 +1,94 @@
+from sakia.data.repositories import NodesRepo
+from sakia.data.entities import Node
+from duniterpy.documents import BlockUID, BMAEndpoint, UnknownEndpoint, block_uid
+
+
+def test_add_get_drop_node(meta_repo):
+    nodes_repo = NodesRepo(meta_repo.conn)
+    inserted = Node(currency="testcurrency",
+                    pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                    endpoints="""BASIC_MERKLED_API test-net.duniter.fr 13.222.11.22 9201
+BASIC_MERKLED_API testnet.duniter.org 80
+UNKNOWNAPI some useless information""",
+                     peer_blockstamp=BlockUID.empty(),
+                     uid="doe",
+                     current_buid="15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                     current_ts=12376543345,
+                     previous_buid="14-AEFFCB00E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                     state=Node.ONLINE,
+                     software="duniter",
+                     version="0.30.17")
+    nodes_repo.insert(inserted)
+    node = nodes_repo.get_one(currency="testcurrency",
+                              pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ")
+    assert node.currency == "testcurrency"
+    assert node.pubkey == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
+    assert node.endpoints[0] == BMAEndpoint("test-net.duniter.fr", "13.222.11.22", None, 9201)
+    assert node.endpoints[1] == BMAEndpoint("testnet.duniter.org", None, None, 80)
+    assert node.endpoints[2] == UnknownEndpoint("UNKNOWNAPI", ["some", "useless", "information"])
+    assert node.previous_buid == block_uid("14-AEFFCB00E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
+    assert node.current_buid == block_uid("15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
+    assert node.state == Node.ONLINE
+    assert node.software == "duniter"
+    assert node.version == "0.30.17"
+    assert node.merkle_peers_root == Node.MERKLE_EMPTY_ROOT
+    assert node.merkle_peers_leaves == tuple()
+
+    nodes_repo.drop(node)
+    node = nodes_repo.get_one(pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ")
+    assert node is None
+
+
+def test_add_get_multiple_node(meta_repo):
+    nodes_repo = NodesRepo(meta_repo.conn)
+    nodes_repo.insert(Node("testcurrency",
+                           "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                           """BASIC_MERKLED_API test-net.duniter.fr 13.222.11.22 9201
+BASIC_MERKLED_API testnet.duniter.org 80
+UNKNOWNAPI some useless information""",
+                           BlockUID.empty(),
+                           "doe",
+                           "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                           12376543345,
+                           "14-AEFFCB00E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                           Node.ONLINE,
+                           "duniter",
+                           "0.30.17"))
+    nodes_repo.insert(Node("testcurrency",
+                           "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                           "BASIC_MERKLED_API test-net.duniter.org 22.22.22.22 9201",
+                           BlockUID.empty(),
+                           "doe",
+                           "18-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                           12376543345,
+                           "12-AEFFCB00E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                           Node.ONLINE,
+                           "duniter",
+                           "0.30.2a5"))
+    nodes = nodes_repo.get_all(currency="testcurrency")
+    assert "testcurrency" in  [t.currency for t in nodes]
+    assert "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" in  [n.pubkey for n in nodes]
+    assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in  [n.pubkey for n in nodes]
+
+
+def test_add_update_node(meta_repo):
+    nodes_repo = NodesRepo(meta_repo.conn)
+    node = Node("testcurrency",
+                "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                """BASIC_MERKLED_API test-net.duniter.fr 13.222.11.22 9201
+BASIC_MERKLED_API testnet.duniter.org 80
+UNKNOWNAPI some useless information""",
+                BlockUID.empty(),
+                "doe",
+                "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                12376543345,
+                "14-AEFFCB00E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                Node.ONLINE,
+                "duniter")
+    nodes_repo.insert(node)
+    node.previous_buid = node.current_buid
+    node.current_buid = "16-77543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"
+    nodes_repo.update(node)
+    node2 = nodes_repo.get_one(pubkey="7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ")
+    assert node2.current_buid == block_uid("16-77543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
+    assert node2.previous_buid == block_uid("15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67")
diff --git a/tests/unit/data/test_sources_repo.py b/tests/unit/data/test_sources_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..89fadbfb6e2268396a2edab7562ca2a9af29953f
--- /dev/null
+++ b/tests/unit/data/test_sources_repo.py
@@ -0,0 +1,51 @@
+from sakia.data.repositories import SourcesRepo
+from sakia.data.entities import Source
+
+
+def test_add_get_drop_source( meta_repo):
+    sources_repo = SourcesRepo(meta_repo.conn)
+    sources_repo.insert(Source("testcurrency",
+                               "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                               "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843",
+                               3,
+                               "T",
+                               1565,
+                               1))
+    source = sources_repo.get_one(identifier="0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843")
+    assert source.currency == "testcurrency"
+    assert source.pubkey == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
+    assert source.type == "T"
+    assert source.amount == 1565
+    assert source.base == 1
+    assert source.noffset == 3
+
+    sources_repo.drop(source)
+    source = sources_repo.get_one(identifier="0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843")
+    assert source is None
+
+
+def test_add_get_multiple_source(meta_repo):
+    sources_repo = SourcesRepo(meta_repo.conn)
+    sources_repo.insert(Source("testcurrency",
+                               "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                               "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843",
+                               3,
+                               "T",
+                               1565,
+                               1))
+    sources_repo.insert(Source("testcurrency",
+                               "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                               "2pyPsXM8UCB88jP2NRM4rUHxb63qm89JMEWbpoRrhyDK",
+                               22635,
+                               "D",
+                               726946,
+                               1))
+    sources = sources_repo.get_all(currency="testcurrency", pubkey="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
+    assert "testcurrency" in [s.currency for s in sources]
+    assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in [s.pubkey for s in sources]
+    assert "2pyPsXM8UCB88jP2NRM4rUHxb63qm89JMEWbpoRrhyDK" in [s.identifier for s in sources]
+    assert "T" in [s.type for s in sources]
+    assert "D" in [s.type for s in sources]
+    assert 726946 in [s.amount for s in sources]
+    assert 1565 in [s.amount for s in sources]
+    assert "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843" in [s.identifier for s in sources]
diff --git a/tests/unit/data/test_transactions_repo.py b/tests/unit/data/test_transactions_repo.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b2010d5c31856c2bf6cc31f4173c15f0f537e13
--- /dev/null
+++ b/tests/unit/data/test_transactions_repo.py
@@ -0,0 +1,92 @@
+from sakia.data.repositories import TransactionsRepo
+from sakia.data.entities import Transaction
+
+
+def test_add_get_drop_transaction(meta_repo):
+    transactions_repo = TransactionsRepo(meta_repo.conn)
+    transactions_repo.insert(Transaction("testcurrency",
+                                         "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365",
+                                         20,
+                                         "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                                         1473108382,
+                                         "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                         "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                         "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                         1565,
+                                         1,
+                                         "",
+                                         0,
+                                         Transaction.TO_SEND))
+    transaction = transactions_repo.get_one(sha_hash="FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365")
+    assert transaction.currency == "testcurrency"
+    assert transaction.sha_hash == "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365"
+    assert transaction.written_block == 20
+    assert transaction.blockstamp.number == 15
+    assert transaction.blockstamp.sha_hash == "76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"
+    assert transaction.timestamp == 1473108382
+    assert transaction.signature == "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw=="
+    assert transaction.amount == 1565
+    assert transaction.amount_base == 1
+    assert transaction.issuer == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
+    assert transaction.receiver == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
+    assert transaction.comment == ""
+    assert transaction.txid == 0
+    transactions_repo.drop(transaction)
+    transaction = transactions_repo.get_one(sha_hash="FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365")
+    assert transaction is None
+
+
+def test_add_get_multiple_transaction(meta_repo):
+    transactions_repo = TransactionsRepo(meta_repo.conn)
+    transactions_repo.insert(Transaction("testcurrency",
+                                         "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5",
+                                         12,
+                                         "543-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                                         1473108382,
+                                         "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                         "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                         "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                         14,
+                                         2,
+                                         "Test",
+                                         2,
+                                         Transaction.TO_SEND))
+    transactions_repo.insert(Transaction("testcurrency",
+                                         "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365",
+                                         20,
+                                         "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                                         1473108382,
+                                         "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                                         "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                                         "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                                         1565,
+                                         1,
+                                         "",
+                                         0,
+                                         Transaction.TO_SEND))
+    transactions = transactions_repo.get_all(currency="testcurrency")
+    assert "testcurrency" in [t.currency for t in transactions]
+    assert "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" in [t.receiver for t in transactions]
+    assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in [t.issuer for t in transactions]
+
+
+def test_add_update_transaction(meta_repo):
+    transactions_repo = TransactionsRepo(meta_repo.conn)
+    transaction = Transaction("testcurrency",
+                              "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365",
+                              20,
+                              "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67",
+                              1473108382,
+                              "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+                              "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
+                              "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
+                              1565,
+                              1,
+                              "",
+                              0,
+                              Transaction.TO_SEND)
+    transactions_repo.insert(transaction)
+    transaction.written_on = None
+    transactions_repo.update(transaction)
+    transaction2 = transactions_repo.get_one(sha_hash="FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365")
+    assert transaction2.written_block == 20
diff --git a/tests/unit/data/test_user_parameters_file.py b/tests/unit/data/test_user_parameters_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..a001f1998c106de489924fa1e9e786436673bba7
--- /dev/null
+++ b/tests/unit/data/test_user_parameters_file.py
@@ -0,0 +1,14 @@
+from sakia.data.entities import UserParameters
+from sakia.data.files import UserParametersFile
+import tempfile
+import os
+
+
+def test_init_save_load():
+    file = os.path.join(tempfile.mkdtemp(), "params.json")
+    user_parameters = UserParameters()
+    user_parameters_file = UserParametersFile(file)
+    user_parameters.proxy_address = "test.fr"
+    user_parameters_file.save(user_parameters)
+    user_parameters_2 = user_parameters_file.load_or_init()
+    assert user_parameters == user_parameters_2
diff --git a/tests/unit/gui/__init__.py b/tests/unit/gui/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/unit/gui/test_generic_tree.py b/tests/unit/gui/test_generic_tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a462f5ffe6319a2fd57451e29f1b6056b1332d1
--- /dev/null
+++ b/tests/unit/gui/test_generic_tree.py
@@ -0,0 +1,56 @@
+from PyQt5.QtCore import QModelIndex
+from sakia.models.generic_tree import GenericTreeModel
+
+
+def test_generic_tree():
+    data = [
+        {
+            'node': {
+                'title': "Default Profile"
+            },
+            'children': [
+                {
+                    'node': {
+                        'title': "Test net (inso)"
+                    },
+                    'children': [
+                        {
+                            'node': {
+                                'title': "Transactions"
+                            },
+                            'children': []
+                        },
+                        {
+                            'node': {
+                                'title': "Network"
+                            },
+                            'children': []
+                        }
+                    ]
+                },
+                {
+                    'node': {
+                        'title': "Le sou"
+                    },
+                    'children': [
+                        {
+                            'node': {
+                                'title': "Transactions"
+                            },
+                            'children': {}
+                        },
+                        {
+                            'node': {
+                                'title': "Network"
+                            },
+                            'children': {
+                            }
+                        }
+                    ]
+                }
+            ],
+        }
+    ]
+    tree_model = GenericTreeModel.create("Test", data)
+    assert tree_model.columnCount(QModelIndex()) == 1
+    assert tree_model.rowCount(QModelIndex()) == 1
diff --git a/tests/unit/money/__init__.py b/tests/unit/money/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/unit/money/test_quantitative.py b/tests/unit/money/test_quantitative.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ae1f3e482068eb61b93dc6e595a019f799526f4
--- /dev/null
+++ b/tests/unit/money/test_quantitative.py
@@ -0,0 +1,90 @@
+from sakia.money import Quantitative
+
+
+def test_units(application_with_one_connection, bob):
+    referential = Quantitative(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "TC"
+
+
+def test_diff_units(application_with_one_connection, bob):
+    referential = Quantitative(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "TC"
+
+
+def test_value(application_with_one_connection, bob):
+    referential = Quantitative(101010110, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == 1010101.10
+
+
+def test_differential(application_with_one_connection, bob):
+    referential = Quantitative(110, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == 1.10
+
+
+def test_localized_no_si(application_with_one_connection, bob):
+    referential = Quantitative(101010110, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=True)
+    assert value == "1,010,101.10 TC"
+
+
+def test_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Quantitative(101010000, bob.currency, application_with_one_connection, None)
+    blockchain = application_with_one_connection.db.blockchains_repo.get_one(currency=bob.currency)
+    blockchain.last_ud_base = 3
+    application_with_one_connection.db.blockchains_repo.update(blockchain)
+    value = referential.localized(units=True, show_base=True)
+    assert value == "1,010.10 x10³ TC"
+
+
+def test_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Quantitative(101010110, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=False, show_base=False)
+    assert value == "1,010,101.10"
+
+
+def test_localized_no_units_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Quantitative(101010000, bob.currency, application_with_one_connection, None)
+    blockchain = application_with_one_connection.db.blockchains_repo.get_one(currency=bob.currency)
+    blockchain.last_ud_base = 3
+    application_with_one_connection.db.blockchains_repo.update(blockchain)
+    value = referential.localized(units=False, show_base=True)
+    assert value == "1,010.10 x10³"
+
+
+def test_diff_localized_no_si(application_with_one_connection, bob):
+    referential = Quantitative(101010110, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=True)
+    assert value == "1,010,101.10 TC"
+
+
+def test_diff_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Quantitative(101010000, bob.currency, application_with_one_connection, None)
+    blockchain = application_with_one_connection.db.blockchains_repo.get_one(currency=bob.currency)
+    blockchain.last_ud_base = 3
+    application_with_one_connection.db.blockchains_repo.update(blockchain)
+
+    value = referential.diff_localized(units=True, show_base=True)
+    assert value == "1,010.10 x10³ TC"
+
+
+def test_diff_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Quantitative(101010110, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=False, show_base=False)
+    assert value == "1,010,101.10"
+
+
+def test_diff_localized_no_units_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Quantitative(10100000000, bob.currency, application_with_one_connection, None)
+    blockchain = application_with_one_connection.db.blockchains_repo.get_one(currency=bob.currency)
+    blockchain.last_ud_base = 6
+    application_with_one_connection.db.blockchains_repo.update(blockchain)
+    value = referential.diff_localized(units=False, show_base=True)
+    assert value == "101.00 x10⁶"
diff --git a/tests/unit/money/test_quantitative_zsum.py b/tests/unit/money/test_quantitative_zsum.py
new file mode 100644
index 0000000000000000000000000000000000000000..2bbe25dc84a5baf65ac626a2bc7d2a5e0e8bcba3
--- /dev/null
+++ b/tests/unit/money/test_quantitative_zsum.py
@@ -0,0 +1,83 @@
+from sakia.money import QuantitativeZSum
+
+
+def test_units(application_with_one_connection, bob):
+    referential = QuantitativeZSum(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "Q0 TC"
+
+
+def test_diff_units(application_with_one_connection, bob):
+    referential = QuantitativeZSum(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "Q0 TC"
+
+
+def test_value(application_with_one_connection, bob):
+    referential = QuantitativeZSum(110, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == -10.79
+
+
+def test_differential(application_with_one_connection, bob):
+    referential = QuantitativeZSum(110, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == -10.79
+
+
+def test_localized_no_si(application_with_one_connection, bob):
+    referential = QuantitativeZSum(110, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=True)
+    assert value == "-10.79 Q0 TC"
+
+
+def test_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = QuantitativeZSum(110 * 1000, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=True, show_base=True)
+    assert value == "1,088.11 Q0 TC"
+
+
+def test_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = QuantitativeZSum(110, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=False, show_base=False)
+    assert value == "-10.79"
+
+
+def test_localized_no_units_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = QuantitativeZSum(110 * 1000, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=False, show_base=True)
+    assert value == "1,088.11 Q0"
+
+    
+def test_diff_localized_no_si(application_with_one_connection, bob):
+    referential = QuantitativeZSum(110 * 1000, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=True)
+    assert value == "1,100.00 TC"
+
+
+def test_diff_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = QuantitativeZSum(101000000, bob.currency, application_with_one_connection, None)
+    blockchain = application_with_one_connection.db.blockchains_repo.get_one(currency=bob.currency)
+    blockchain.last_ud_base = 3
+    application_with_one_connection.db.blockchains_repo.update(blockchain)
+    value = referential.diff_localized(units=True, show_base=True)
+    assert value == "1,010.00 x10³ TC"
+
+
+def test_diff_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = QuantitativeZSum(101010110, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=False, show_base=False)
+    assert value == "1,010,101.10"
+
+
+def test_diff_localized_no_units_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = QuantitativeZSum(101000000, bob.currency, application_with_one_connection, None)
+    blockchain = application_with_one_connection.db.blockchains_repo.get_one(currency=bob.currency)
+    blockchain.last_ud_base = 3
+    application_with_one_connection.db.blockchains_repo.update(blockchain)
+    value = referential.diff_localized(units=False, show_base=True)
+    assert value == "1,010.00 x10³"
diff --git a/tests/unit/money/test_relative.py b/tests/unit/money/test_relative.py
new file mode 100644
index 0000000000000000000000000000000000000000..0bb50bf24fdefbb07b782489eb1a8231c1335c7e
--- /dev/null
+++ b/tests/unit/money/test_relative.py
@@ -0,0 +1,80 @@
+import pytest
+from sakia.money import Relative
+
+
+def test_units(application_with_one_connection, bob):
+    referential = Relative(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "UD TC"
+
+
+def test_diff_units(application_with_one_connection, bob):
+    referential = Relative(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "UD TC"
+
+
+def test_value(application_with_one_connection, bob):
+    referential = Relative(13555300, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == pytest.approx(58177.253218)
+
+
+def test_differential(application_with_one_connection, bob):
+    referential = Relative(11, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == pytest.approx(0.0472103)
+
+
+def test_localized_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(11, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=True)
+    assert value == "0.047210 UD TC"
+
+
+def test_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(1, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=True, show_base=True)
+    assert value == "0.004292 UD TC"
+
+
+def test_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(11, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=False, show_base=False)
+    assert value == "0.047210"
+
+
+def test_localized_no_units_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(1, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=False, show_base=True)
+    assert value == "0.004292"
+
+
+def test_diff_localized_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(11, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=True)
+    assert value == "0.047210 UD TC"
+
+
+def test_diff_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(1, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=True, show_base=True)
+    assert value, "9.090909 x10⁻ UD TC"
+
+
+def test_diff_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(1, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=False, show_base=False)
+    assert value == "0.004292"
+
+
+def test_diff_localized_no_units_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = Relative(1, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=False, show_base=True)
+    assert value == "0.004292"
diff --git a/tests/unit/money/test_relative_zsum.py b/tests/unit/money/test_relative_zsum.py
new file mode 100644
index 0000000000000000000000000000000000000000..e25b410d6bbba7ddb59c4e63507cb3f50ff816cc
--- /dev/null
+++ b/tests/unit/money/test_relative_zsum.py
@@ -0,0 +1,82 @@
+from pytest import approx
+from sakia.money import RelativeZSum
+
+
+def test_units(application_with_one_connection, bob):
+    referential = RelativeZSum(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "R0 TC"
+
+
+def test_diff_units(application_with_one_connection, bob):
+    referential = RelativeZSum(0, bob.currency, application_with_one_connection, None)
+    assert referential.units == "R0 TC"
+
+
+def test_value(application_with_one_connection, bob):
+    referential = RelativeZSum(2702, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == approx(8.70007)
+
+
+def test_differential(application_with_one_connection, bob):
+    referential = RelativeZSum(111, bob.currency, application_with_one_connection, None)
+    value = referential.value()
+    assert value == approx(-3.521619496)
+
+
+def test_localized_no_si(application_with_one_connection, fake_server, bob):
+    referential = RelativeZSum(110, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=True)
+    assert value == "-3.53 R0 TC"
+
+
+def test_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+
+    referential = RelativeZSum(1, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=True, show_base=True)
+    assert value == "-4.040487 R0 TC"
+
+
+def test_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+
+    referential = RelativeZSum(110, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=False, show_base=False)
+    assert value == "-3.526336"
+
+
+def test_localized_no_units_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+
+    referential = RelativeZSum(1, bob.currency, application_with_one_connection, None)
+    value = referential.localized(units=False, show_base=True)
+    assert value == "-4.040487"
+
+
+def test_diff_localized_no_si(application_with_one_connection, bob):
+    referential = RelativeZSum(11, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=True)
+    assert value == "0.05 UD TC"
+
+
+def test_diff_localized_with_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+
+    referential = RelativeZSum(1, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=True, show_base=True)
+    assert value == "0.004292 UD TC"
+
+
+def test_diff_localized_no_units_no_si(application_with_one_connection, bob):
+    application_with_one_connection.parameters.digits_after_comma = 6
+    referential = RelativeZSum(90, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=False, show_base=False)
+    assert value == "0.386266"
+
+
+def test_diff_localized_no_units_with_si(application_with_one_connection, bob):
+
+    referential = RelativeZSum(90, bob.currency, application_with_one_connection, None)
+    value = referential.diff_localized(units=False, show_base=True)
+    assert value == "0.39"
diff --git a/tests/unit/test_decorators.py b/tests/unit/test_decorators.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca38c4332ca7f254d5e5e74edb9a4f960c657dd7
--- /dev/null
+++ b/tests/unit/test_decorators.py
@@ -0,0 +1,152 @@
+import asyncio
+import pytest
+from sakia.decorators import asyncify, once_at_a_time, cancel_once_task
+
+
+@pytest.mark.asyncio
+async def test_run_only_once():
+    class TaskRunner:
+        def __init__(self):
+            pass
+
+        @once_at_a_time
+        @asyncify
+        async def some_long_task(self, name, callback):
+            await asyncio.sleep(1)
+            callback(name)
+
+    task_runner = TaskRunner()
+    calls = {'A': 0, 'B': 0, 'C': 0}
+
+    def incrementer(name):
+        nonlocal calls
+        calls[name] += 1
+
+    async def exec_test():
+        await asyncio.sleep(3)
+
+    asyncio.ensure_future(task_runner.some_long_task("A", incrementer))
+    asyncio.ensure_future(task_runner.some_long_task("B", incrementer))
+    asyncio.ensure_future(task_runner.some_long_task("C", incrementer))
+    await exec_test()
+    assert calls["A"] == 0
+    assert calls["B"] == 0
+    assert calls["C"] == 1
+
+
+@pytest.mark.asyncio
+async def test_cancel_once(application):
+    class TaskRunner:
+        def __init__(self):
+            pass
+
+        @once_at_a_time
+        @asyncify
+        async def some_long_task(self, name, callback):
+            await asyncio.sleep(1)
+            callback(name)
+            await asyncio.sleep(1)
+            callback(name)
+
+        def cancel_long_task(self):
+            cancel_once_task(self, self.some_long_task)
+
+    task_runner = TaskRunner()
+    calls = {'A': 0, 'B': 0}
+
+    def incrementer(name):
+        nonlocal calls
+        calls[name] += 1
+
+    async def exec_test():
+        await asyncio.sleep(3)
+
+    application.loop.call_soon(lambda: task_runner.some_long_task("A", incrementer))
+    application.loop.call_soon(lambda: task_runner.some_long_task("B", incrementer))
+    application.loop.call_later(1.5, lambda: task_runner.cancel_long_task())
+    await exec_test()
+    assert calls["A"] == 0
+    assert calls["B"] == 1
+
+
+@pytest.mark.asyncio
+async def test_cancel_once_two_times(application):
+    class TaskRunner:
+        def __init__(self):
+            pass
+
+        @once_at_a_time
+        @asyncify
+        async def some_long_task(self, name, callback):
+            await asyncio.sleep(1)
+            callback(name)
+            await asyncio.sleep(1)
+            callback(name)
+
+        def cancel_long_task(self):
+            cancel_once_task(self, self.some_long_task)
+
+    task_runner = TaskRunner()
+    calls = {'A': 0, 'B': 0, 'C': 0, 'D': 0}
+
+    def incrementer(name):
+        nonlocal calls
+        calls[name] += 1
+
+    async def exec_test():
+        await asyncio.sleep(6)
+
+    application.loop.call_soon(lambda: task_runner.some_long_task("A", incrementer))
+    application.loop.call_soon(lambda: task_runner.some_long_task("B", incrementer))
+    application.loop.call_later(1.5, lambda: task_runner.cancel_long_task())
+    application.loop.call_later(2, lambda: task_runner.some_long_task("C", incrementer))
+    application.loop.call_later(2.1, lambda: task_runner.some_long_task("D", incrementer))
+    application.loop.call_later(3.5, lambda: task_runner.cancel_long_task())
+    await exec_test()
+    assert calls["A"] == 0
+    assert calls["B"] == 1
+    assert calls["C"] == 0
+    assert calls["D"] == 1
+
+
+@pytest.mark.asyncio
+async def test_two_runners():
+    class TaskRunner:
+        def __init__(self, name):
+            self.some_long_task(name, incrementer)
+
+        @classmethod
+        def create(cls, name):
+            return cls(name)
+
+        @once_at_a_time
+        @asyncify
+        async def some_long_task(self, name, callback):
+            await asyncio.sleep(1)
+            callback(name)
+            await asyncio.sleep(1)
+            callback(name)
+
+        def cancel_long_task(self):
+            cancel_once_task(self, self.some_long_task)
+
+    calls = {'A': 0, 'B': 0, 'C': 0}
+
+    def incrementer(name):
+        nonlocal calls
+        calls[name] += 1
+
+    async def exec_test():
+        tr1 = TaskRunner.create("A")
+        tr2 = TaskRunner.create("B")
+        tr3 = TaskRunner.create("C")
+        await asyncio.sleep(1.5)
+        tr1.some_long_task("A", incrementer)
+        tr2.some_long_task("B", incrementer)
+        tr3.some_long_task("C", incrementer)
+        await asyncio.sleep(1.5)
+
+    await exec_test()
+    assert calls["A"] == 2
+    assert calls["B"] == 2
+    assert calls["C"] == 2