diff --git a/res/icons/icons.qrc b/res/icons/icons.qrc
index cfbe0e556023429890485c14b43b9779a4108ac8..1e3016df94ab4c91c9f41ed191f800230a3383a6 100644
--- a/res/icons/icons.qrc
+++ b/res/icons/icons.qrc
@@ -28,6 +28,7 @@
     <file alias="settings_display_icon">noun_38960_cc.svg</file>
     <file alias="settings_app_icon">noun_42425_cc.svg</file>
     <file alias="settings_network_icon">noun_62146_cc.svg</file>
+    <file alias="explorer_icon">noun_101791_cc.svg</file>
     <file alias="connected">connected.svg</file>
     <file alias="weak_connect">weak_connect.svg</file>
     <file alias="disconnected">disconnected.svg</file>
diff --git a/res/icons/noun_101791_cc.svg b/res/icons/noun_101791_cc.svg
new file mode 100644
index 0000000000000000000000000000000000000000..dabc0b049409edc3e5ae3670064c9aadcdf30d80
--- /dev/null
+++ b/res/icons/noun_101791_cc.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 100 125"
+   enable-background="new 0 0 100 100"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="noun_101791_cc.svg"><metadata
+     id="metadata14"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+     id="defs12" /><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="777"
+     inkscape:window-height="480"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="1.888"
+     inkscape:cx="50"
+     inkscape:cy="62.5"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2" /><path
+     d="M 77.891855,62.286039 C 83.30181,41.246432 70.585007,19.72692 49.546735,14.316965 28.504455,8.9056726 6.9862794,21.622477 1.5749866,42.662085 -3.8349685,63.703028 8.8804994,85.219867 29.921443,90.632496 50.959714,96.042452 72.480563,83.326984 77.891855,62.286039 Z M 32.585649,80.269764 C 17.258111,76.328931 7.9968867,60.651155 11.937719,45.324953 15.879889,29.998751 31.554991,20.736191 46.879857,24.677023 62.207396,28.617856 71.469956,44.294295 67.527786,59.620497 63.585617,74.946699 47.913188,84.210597 32.585649,80.269764 Z m 64.940859,27.323826 c 0.0021,-0.001 0.0043,-0.002 0.0054,-0.002 -0.01741,0.0241 -0.04009,0.0494 -0.0588,0.0722 -0.01602,0.0227 -0.03074,0.0414 -0.04676,0.0642 -0.0022,0.001 -0.0043,0.001 -0.0064,0.004 -0.201856,0.24597 -0.458522,0.54942 -0.660378,0.75528 l -0.815433,0.83148 c -2.364767,2.41156 -5.464764,3.2056 -6.927202,1.77257 L 65.622872,88.163459 c 1.263259,-1.046699 2.932899,-2.573304 3.693528,-3.272441 l -0.0027,-0.0013 c 2.116125,-1.914271 2.855365,-3.03182 3.265757,-3.420823 0.818111,-0.772661 2.018541,-2.439628 2.418239,-2.870071 l 23.393699,22.925756 c 1.285982,1.26326 0.864889,3.82855 -0.8649,6.069 z M 30.801046,36.40862 c -5.951352,4.030396 -10.322628,11.609948 -8.509952,18.799161 0.661707,2.618755 -3.222981,3.823195 -3.883351,1.209787 -2.344715,-9.29598 2.997065,-18.605328 10.394814,-23.616923 2.211037,-1.497195 4.201505,2.116126 1.998489,3.607975 z"
+     id="path4"
+     inkscape:connector-curvature="0" /></svg>
\ No newline at end of file
diff --git a/res/ui/explorer_tab.ui b/res/ui/explorer_tab.ui
new file mode 100644
index 0000000000000000000000000000000000000000..1fdfca6504d575258272ea6e605da2bb1dcae9d4
--- /dev/null
+++ b/res/ui/explorer_tab.ui
@@ -0,0 +1,86 @@
+<?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="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>
+ </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/src/sakia/core/graph/constants.py b/src/sakia/core/graph/constants.py
index 56930a665c8402aafce0971c6be666500b4ab827..d1e2cb9d279468d536e527db06f45a591b2cc5dd 100644
--- a/src/sakia/core/graph/constants.py
+++ b/src/sakia/core/graph/constants.py
@@ -4,6 +4,7 @@ from enum import Enum
 class ArcStatus(Enum):
     WEAK = 0
     STRONG = 1
+    ON_PATH = 2
 
 
 class NodeStatus:
diff --git a/src/sakia/core/graph/explorer_graph.py b/src/sakia/core/graph/explorer_graph.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bc01bf41f9ddbace0aa23dedba529683e5f9364
--- /dev/null
+++ b/src/sakia/core/graph/explorer_graph.py
@@ -0,0 +1,95 @@
+import logging
+import networkx
+import asyncio
+from PyQt5.QtCore import pyqtSignal
+from .base_graph import BaseGraph
+from sakia.core.graph.constants import ArcStatus, NodeStatus
+
+
+class ExplorerGraph(BaseGraph):
+
+    graph_changed = pyqtSignal()
+
+    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.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.graph_changed.emit()
+        for step in range(1, steps + 1):
+            explorable[step] = []
+
+        for step in range(0, steps):
+            while len(explorable[step]) > 0:
+                # for each pubkey connected...
+                if current_identity not in explored:
+                    self.add_identity(current_identity, NodeStatus.NEUTRAL)
+                    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]))
+                    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()
+                current_identity = explorable[step].pop()
\ No newline at end of file
diff --git a/src/sakia/gui/community_view.py b/src/sakia/gui/community_view.py
index ac2261d00ae5da048449210deb291041e0ca96ed..5db08b0bb898ec57593cb1ac92cd9937ce34e607 100644
--- a/src/sakia/gui/community_view.py
+++ b/src/sakia/gui/community_view.py
@@ -11,14 +11,15 @@ from PyQt5.QtCore import pyqtSlot, QDateTime, QLocale, QEvent, QT_TRANSLATE_NOOP
 from PyQt5.QtGui import QIcon
 from PyQt5.QtWidgets import QWidget, QMessageBox, QDialog, QPushButton, QTabBar, QAction
 
-from sakia.gui.graphs.wot_tab import WotTabWidget
-from sakia.gui.widgets import toast
-from sakia.gui.widgets.dialogs import QAsyncMessageBox
+from .graphs.wot_tab import WotTabWidget
+from .widgets import toast
+from .widgets.dialogs import QAsyncMessageBox
 from .certifications_tab import CertificationsTabWidget
 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
@@ -36,6 +37,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
     _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")
 
@@ -59,10 +61,12 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
         self.tab_informations = InformationsTabWidget(self.app)
         self.tab_certifications = CertificationsTabWidget(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)
 
@@ -94,6 +98,11 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
                                     QIcon(":/icons/informations_icon"), self.tr("Informations")))
         self.toolbutton_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,
+                                    QIcon(":/icons/explorer_icon"), self.tr("Explorer")))
+        self.toolbutton_menu.addAction(action_showexplorer)
+
         self.action_publish_uid.triggered.connect(self.publish_uid)
         self.toolbutton_menu.addAction(self.action_publish_uid)
 
@@ -127,6 +136,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
         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()
@@ -136,6 +146,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
         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)
diff --git a/src/sakia/gui/graphs/explorer_tab.py b/src/sakia/gui/graphs/explorer_tab.py
new file mode 100644
index 0000000000000000000000000000000000000000..452e3fca8a1d8dcab5f84fc21e65cd7e56661581
--- /dev/null
+++ b/src/sakia/gui/graphs/explorer_tab.py
@@ -0,0 +1,111 @@
+import logging
+
+from PyQt5.QtCore import QEvent, pyqtSignal, QT_TRANSLATE_NOOP
+
+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()
+
+    _search_placeholder = QT_TRANSLATE_NOOP("ExplorerTabWidget", "Research a pubkey, an uid...")
+
+    def __init__(self, app):
+        """
+        :param sakia.core.app.Application app: Application instance
+        """
+        # construct from qtDesigner
+        super().__init__(app)
+        self.setupUi(self)
+        self.set_scene(self.graphicsView.scene())
+
+        self.account = None
+        self.community = None
+        self.password_asker = None
+        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.button_go.clicked.connect(self.go_clicked)
+
+    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.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.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.steps_slider.value())
+
+            # draw graph in qt scene
+            self.graphicsView.scene().update_wot(self.graph.nx_graph, identity, self.steps_slider.maximum())
+
+    def refresh(self):
+        """
+        Refresh graph scene to current metadata
+        """
+        if self._current_identity:
+            # draw graph in qt scene
+            self.graphicsView.scene().update_wot(self.graph.nx_graph, self._current_identity, self.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:
+            parameters = await self.community.parameters()
+            self.steps_slider.setMaximum(parameters['stepMax'])
+            self.steps_slider.setValue(int(0.33 * parameters['stepMax']))
+            identity = await self.account.identity(self.community)
+            self.draw_graph(identity)
+
+    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
new file mode 100644
index 0000000000000000000000000000000000000000..57afd78a4edf0892904867b36781bdca89d9a4bb
--- /dev/null
+++ b/src/sakia/gui/graphs/graph_tab.py
@@ -0,0 +1,232 @@
+from PyQt5.QtWidgets import QWidget, QDialog
+from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal
+
+from ...tools.exceptions import MembershipNotFoundError
+from ...tools.decorators import asyncify, once_at_a_time
+from ...core.registry import BlockchainState
+from ...gui.member import MemberDialog
+from ...gui.certification import CertificationDialog
+from ...gui.transfer import TransferMoneyDialog
+from ...gui.contact import ConfigureContactDialog
+
+
+class GraphTabWidget(QWidget):
+
+    money_sent = pyqtSignal()
+    def __init__(self, app):
+        """
+        :param sakia.core.app.Application app: Application instance
+        """
+        super().__init__()
+
+        self.password_asker = None
+        self.app = app
+
+    def set_scene(self, scene):
+        # add scene events
+        scene.node_clicked.connect(self.handle_node_click)
+        scene.node_signed.connect(self.sign_node)
+        scene.node_transaction.connect(self.send_money_to_node)
+        scene.node_contact.connect(self.add_node_as_contact)
+        scene.node_member.connect(self.identity_informations)
+        scene.node_copy_pubkey.connect(self.copy_node_pubkey)
+
+    @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
+
+    def select_node(self, index):
+        """
+        Select node in graph when item is selected in combobox
+        """
+        if index < 0 or index >= len(self.nodes):
+            return False
+        node = self.nodes[index]
+        metadata = {'id': node['pubkey'], 'text': node['uid']}
+        self.draw_graph(
+            self.app.identities_registry.from_handled_data(
+                metadata['text'],
+                metadata['id'],
+                None,
+                BlockchainState.VALIDATED,
+                self.community
+            )
+        )
+
+    def identity_informations(self, pubkey, metadata):
+        identity = self.app.identities_registry.from_handled_data(
+            metadata['text'],
+            pubkey,
+            None,
+            BlockchainState.VALIDATED,
+            self.community
+        )
+        dialog = MemberDialog(self.app, self.account, self.community, identity)
+        dialog.exec_()
+
+    @asyncify
+    async def sign_node(self, pubkey, metadata):
+        identity = self.app.identities_registry.from_handled_data(
+            metadata['text'],
+            pubkey,
+            None,
+            BlockchainState.VALIDATED,
+            self.community
+        )
+        await CertificationDialog.certify_identity(self.app, self.account, self.password_asker,
+                                             self.community, identity)
+
+    @asyncify
+    async def send_money_to_node(self, pubkey, metadata):
+        identity = self.app.identities_registry.from_handled_data(
+            metadata['text'],
+            pubkey,
+            None,
+            BlockchainState.VALIDATED,
+            self.community
+        )
+        result = await TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker,
+                                                            self.community, identity)
+        if result == QDialog.Accepted:
+            self.money_sent.emit()
+
+    def copy_node_pubkey(self, pubkey):
+        cb = self.app.qapp.clipboard()
+        cb.clear(mode=cb.Clipboard)
+        cb.setText(pubkey, mode=cb.Clipboard)
+
+    def add_node_as_contact(self, pubkey, metadata):
+        # check if contact already exists...
+        if pubkey == self.account.pubkey \
+                or pubkey in [contact['pubkey'] for contact in self.account.contacts]:
+            return False
+        dialog = ConfigureContactDialog(self.account, self.window(), {'name': metadata['text'],
+                                                                      'pubkey': pubkey,
+                                                                      })
+        result = dialog.exec_()
+        if result == QDialog.Accepted:
+            self.window().refresh_contacts()
+
+    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().changeEvent(event)
diff --git a/src/sakia/gui/graphs/wot_tab.py b/src/sakia/gui/graphs/wot_tab.py
index 311af103d0374774c2462a80ea99f7b0f3b1899a..5c54dee2ba34156e24fb9f4f8ebb34128a0dc5d6 100644
--- a/src/sakia/gui/graphs/wot_tab.py
+++ b/src/sakia/gui/graphs/wot_tab.py
@@ -1,24 +1,19 @@
 import logging
 import asyncio
 
-from PyQt5.QtWidgets import QWidget, QComboBox, QDialog
-from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal, QT_TRANSLATE_NOOP
+from PyQt5.QtWidgets import QComboBox
+from PyQt5.QtCore import QEvent, pyqtSignal, QT_TRANSLATE_NOOP
 from ucoinpy.api import bma
 
-from sakia.tools.exceptions import MembershipNotFoundError
-from sakia.tools.decorators import asyncify, once_at_a_time, cancel_once_task
-from sakia.core.graph import WoTGraph
-from sakia.core.registry import BlockchainState
-from sakia.gui.member import MemberDialog
-from sakia.gui.certification import CertificationDialog
-from sakia.gui.transfer import TransferMoneyDialog
-from sakia.gui.contact import ConfigureContactDialog
-from sakia.gen_resources.wot_tab_uic import Ui_WotTabWidget
-from sakia.gui.widgets.busy import Busy
-from sakia.tools.exceptions import NoPeerAvailable
+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 ...tools.exceptions import NoPeerAvailable
+from .graph_tab import GraphTabWidget
 
 
-class WotTabWidget(QWidget, Ui_WotTabWidget):
+class WotTabWidget(GraphTabWidget, Ui_WotTabWidget):
 
     money_sent = pyqtSignal()
     _search_placeholder = QT_TRANSLATE_NOOP("WotTabWidget", "Research a pubkey, an uid...")
@@ -27,7 +22,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         """
         :param sakia.core.app.Application app: Application instance
         """
-        super().__init__()
+        super().__init__(app)
         # construct from qtDesigner
         self.setupUi(self)
 
@@ -42,13 +37,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         self.busy = Busy(self.graphicsView)
         self.busy.hide()
 
-        # add scene events
-        self.graphicsView.scene().node_clicked.connect(self.handle_node_click)
-        self.graphicsView.scene().node_signed.connect(self.sign_node)
-        self.graphicsView.scene().node_transaction.connect(self.send_money_to_node)
-        self.graphicsView.scene().node_contact.connect(self.add_node_as_contact)
-        self.graphicsView.scene().node_member.connect(self.identity_informations)
-        self.graphicsView.scene().node_copy_pubkey.connect(self.copy_node_pubkey)
+        self.set_scene(self.graphicsView.scene())
 
         self.account = None
         self.community = None
@@ -91,105 +80,13 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
             elif self.community:
                 self.community.network.new_block_mined.connect(self.refresh)
 
-    @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(dict)
-    def handle_node_click(self, metadata):
-        self.draw_graph(
-            self.app.identities_registry.from_handled_data(
-                metadata['text'],
-                metadata['id'],
-                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
+        :param sakia.core.registry.Identity identity: Center identity
         """
         logging.debug("Draw graph - " + identity.uid)
         self.busy.show()
@@ -199,7 +96,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
 
             # create empty graph instance
             graph = WoTGraph(self.app, self.community)
-            await graph.initialize(identity_account, identity_account)
+            await graph.initialize(identity, identity_account)
             # draw graph in qt scene
             self.graphicsView.scene().update_wot(graph.nx_graph, identity)
 
@@ -208,7 +105,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
                 # add path from selected member to account member
                 path = await graph.get_shortest_path_to_identity(identity_account, identity)
                 if path:
-                    self.graphicsView.scene().update_path(path)
+                    self.graphicsView.scene().update_path(graph.nx_graph, path)
         self.busy.hide()
 
     @once_at_a_time
@@ -257,78 +154,6 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         except NoPeerAvailable:
             pass
 
-    def select_node(self, index):
-        """
-        Select node in graph when item is selected in combobox
-        """
-        if index < 0 or index >= len(self.nodes):
-            return False
-        node = self.nodes[index]
-        metadata = {'id': node['pubkey'], 'text': node['uid']}
-        self.draw_graph(
-            self.app.identities_registry.from_handled_data(
-                metadata['text'],
-                metadata['id'],
-                None,
-                BlockchainState.VALIDATED,
-                self.community
-            )
-        )
-
-    def identity_informations(self, metadata):
-        identity = self.app.identities_registry.from_handled_data(
-            metadata['text'],
-            metadata['id'],
-            None,
-            BlockchainState.VALIDATED,
-            self.community
-        )
-        dialog = MemberDialog(self.app, self.account, self.community, identity)
-        dialog.exec_()
-
-    @asyncify
-    async def sign_node(self, metadata):
-        identity = self.app.identities_registry.from_handled_data(
-            metadata['text'],
-            metadata['id'],
-            None,
-            BlockchainState.VALIDATED,
-            self.community
-        )
-        await CertificationDialog.certify_identity(self.app, self.account, self.password_asker,
-                                             self.community, identity)
-
-    @asyncify
-    async def send_money_to_node(self, metadata):
-        identity = self.app.identities_registry.from_handled_data(
-            metadata['text'],
-            metadata['id'],
-            None,
-            BlockchainState.VALIDATED,
-            self.community
-        )
-        result = await TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker,
-                                                            self.community, identity)
-        if result == QDialog.Accepted:
-            self.money_sent.emit()
-
-    def copy_node_pubkey(self, metadata):
-        cb = self.app.qapp.clipboard()
-        cb.clear(mode=cb.Clipboard)
-        cb.setText(metadata['id'], mode=cb.Clipboard)
-
-    def add_node_as_contact(self, metadata):
-        # check if contact already exists...
-        if metadata['id'] == self.account.pubkey \
-                or metadata['id'] in [contact['pubkey'] for contact in self.account.contacts]:
-            return False
-        dialog = ConfigureContactDialog(self.account, self.window(), {'name': metadata['text'],
-                                                                      'pubkey': metadata['id'],
-                                                                      })
-        result = dialog.exec_()
-        if result == QDialog.Accepted:
-            self.window().refresh_contacts()
-
     def retranslateUi(self, widget):
         """
         Retranslate missing widgets from generated code
diff --git a/src/sakia/gui/views/__init__.py b/src/sakia/gui/views/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3ca1e9f7ff0ac7bd3d3e7c9e4d8344dd36a87a54 100644
--- a/src/sakia/gui/views/__init__.py
+++ b/src/sakia/gui/views/__init__.py
@@ -0,0 +1,2 @@
+from .explorer import ExplorerView
+from .wot import WotView
\ No newline at end of file
diff --git a/src/sakia/gui/views/arc.py b/src/sakia/gui/views/arc.py
index 6282581119a9076500df2ca39548765d01bdc3c9..45b292382f73b69660cb1423f4a9e0470756257e 100644
--- a/src/sakia/gui/views/arc.py
+++ b/src/sakia/gui/views/arc.py
@@ -29,6 +29,8 @@ class Arc(QGraphicsLineItem):
 
         self.setAcceptedMouseButtons(Qt.NoButton)
 
+        self.paint_pen = self.pen_from_status()
+
         #  cursor change on hover
         self.setAcceptHoverEvents(True)
         self.setZValue(0)
@@ -57,6 +59,21 @@ class Arc(QGraphicsLineItem):
             extra
         )
 
+    def pen_from_status(self):
+        """
+        Get a pen from current status
+        :return:
+        """
+        # Draw the line itself
+        color = QColor()
+        style = Qt.SolidLine
+        if self.status == ArcStatus.STRONG:
+            color.setNamedColor('blue')
+        if self.status == ArcStatus.WEAK:
+            color.setNamedColor('salmon')
+            style = Qt.DashLine
+        return QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin)
+
     def paint(self, painter, option, widget):
         """
         Customize line adding an arrow head
@@ -71,18 +88,9 @@ class Arc(QGraphicsLineItem):
         if qFuzzyCompare(line.length(), 0):
             return
 
-        # Draw the line itself
-        color = QColor()
-        style = Qt.SolidLine
-        if self.status == ArcStatus.STRONG:
-            color.setNamedColor('blue')
-        if self.status == ArcStatus.WEAK:
-            color.setNamedColor('salmon')
-            style = Qt.DashLine
-
-        painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin))
+        painter.setPen(self.paint_pen)
         painter.drawLine(line)
-        painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
+        painter.setPen(QPen(self.paint_pen.color(), 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
 
         # Draw the arrows
         angle = math.acos(line.dx() / line.length())
@@ -94,7 +102,7 @@ class Arc(QGraphicsLineItem):
         hpy = line.p1().y() + (line.dy() / 2.0)
         head_point = QPointF(hpx, hpy)
 
-        painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
+        painter.setPen(QPen(self.paint_pen.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)
@@ -102,7 +110,7 @@ class Arc(QGraphicsLineItem):
             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.setBrush(self.paint_pen.color())
         painter.drawPolygon(QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2]))
 
         if self.metadata["confirmation_text"]:
diff --git a/src/sakia/gui/views/explorer.py b/src/sakia/gui/views/explorer.py
new file mode 100644
index 0000000000000000000000000000000000000000..26331f377e0e28f02bf4238f7dfb14ea432e05b4
--- /dev/null
+++ b/src/sakia/gui/views/explorer.py
@@ -0,0 +1,139 @@
+import networkx
+from PyQt5.QtGui import QPainter, QWheelEvent
+from PyQt5.QtCore import Qt, QPoint, pyqtSignal
+from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene
+from PyQt5.QtGui import QTransform, QColor, QPen, QBrush, QMouseEvent, QRadialGradient
+
+from .node import Node
+from .arc import Arc
+
+import logging
+
+
+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(Scene(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)
+
+
+class Scene(QGraphicsScene):
+    # This defines signals taking string arguments
+    node_hovered = pyqtSignal(str, name='nodeHovered')
+    node_clicked = pyqtSignal(str, dict, name='nodeClicked')
+    node_signed = pyqtSignal(str, dict, name='nodeSigned')
+    node_transaction = pyqtSignal(str, dict, name='nodeTransaction')
+    node_contact = pyqtSignal(str, dict, name='nodeContact')
+    node_member = pyqtSignal(str, dict, name='nodeMember')
+    node_copy_pubkey = pyqtSignal(str, name='nodeCopyPubkey')
+
+    def __init__(self, parent=None):
+        """
+        Create scene of the graph
+
+        :param parent: [Optional, default=None] Parent view
+        """
+        super(Scene, self).__init__(parent)
+
+        self.lastDragPos = QPoint()
+        self.setItemIndexMethod(QGraphicsScene.NoIndex)
+        self.node_hovered.connect(self.display_path_to)
+
+        # list of nodes in scene
+        self.nodes = dict()
+        self.arcs = dict()
+        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)
+
+    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.clear()
+        self.nodes.clear()
+        self.arcs.clear()
+        self.identity = identity
+        self.nx_graph = nx_graph
+        # Programs available : neato, twopi, circo, fdp,
+        # Nice programs : twopi
+        try:
+            graph_pos = networkx.pygraphviz_layout(nx_graph, prog="twopi")
+        except OSError:
+            logging.debug("Twopi not found, fallback mode...")
+            graph_pos = networkx.spring_layout(nx_graph, scale=len(nx_graph.nodes())*12)
+
+        # create networkx graph
+        for node in nx_graph.nodes(data=True):
+            v = Node(node, graph_pos, scale=2)
+            distance = networkx.shortest_path_length(nx_graph.to_undirected(), identity.pubkey, node[0])
+            factor = int((dist_max + 1) / (distance + 1) * 100)
+            color = QColor('light grey').darker(factor)
+            v.setBrush(QBrush(color))
+            v.text_item.setBrush(QBrush(QColor('dark grey').lighter(factor)))
+            self.addItem(v)
+            self.nodes[node[0]] = v
+
+        for edge in nx_graph.edges(data=True):
+            edge[2]["confirmation_text"] = ""
+            arc = Arc(edge[0], edge[1], edge[2], graph_pos, scale=2)
+            self.addItem(arc)
+            self.arcs[(edge[0], edge[1])] = arc
+
+        self.update()
+
+    def display_path_to(self, id):
+        if id != self.identity.pubkey:
+            path = networkx.shortest_path(self.nx_graph.to_undirected(), self.identity.pubkey, id)
+            for arc in self.arcs.values():
+                arc.paint_pen = arc.pen_from_status()
+                self.update(arc.boundingRect())
+
+            for node, next_node in zip(path[:-1], path[1:]):
+                if (node, next_node) in self.arcs:
+                    arc = self.arcs[(node, next_node)]
+                elif (next_node, node) in self.arcs:
+                    arc = self.arcs[(next_node, node)]
+                if arc:
+                    arc.paint_pen = QPen(QColor('black'), 3, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
+                    logging.debug("Update arc between {0} and {1}".format(node, next_node))
+                    self.update(arc.boundingRect())
diff --git a/src/sakia/gui/views/node.py b/src/sakia/gui/views/node.py
index 3c9944bfc6bd5d3850f30183bf7998fa2568125f..4f6b110ef92de411dcfb2d0cab262aa9a9e59563 100644
--- a/src/sakia/gui/views/node.py
+++ b/src/sakia/gui/views/node.py
@@ -99,6 +99,7 @@ class Node(QGraphicsEllipseItem):
         :param event: scene hover event
         """
         self.setCursor(Qt.ArrowCursor)
+        self.scene().node_hovered.emit(self.id)
 
     def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent):
         """
diff --git a/src/sakia/tests/unit/core/graph/test_explorer_graph.py b/src/sakia/tests/unit/core/graph/test_explorer_graph.py
new file mode 100644
index 0000000000000000000000000000000000000000..5147f404073f72bf26bb0ee63524843cddcf31a7
--- /dev/null
+++ b/src/sakia/tests/unit/core/graph/test_explorer_graph.py
@@ -0,0 +1,190 @@
+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.certifierd_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.Application')
+    @patch('sakia.core.Community')
+    @patch('time.time', Mock(return_value=50000))
+    def test_explore_full(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}
+
+        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.Application')
+    @patch('sakia.core.Community')
+    @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)
+        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.Application')
+    @patch('sakia.core.Community')
+    @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())