diff --git a/lib/core/conf/src/keypairs/cli.rs b/lib/core/conf/src/keypairs/cli.rs
index 39ca0d3041641bc88e3cb09bcbe9a4fe8bdd2a1e..e1524a0cf611d5050b0ab25799a024cc004b911c 100644
--- a/lib/core/conf/src/keypairs/cli.rs
+++ b/lib/core/conf/src/keypairs/cli.rs
@@ -27,80 +27,63 @@
     unused_qualifications
 )]
 
+use crate::ui::ui_trait::*;
+use crate::ui::UiError;
 use crate::*;
-#[cfg(test)]
-use mockall::*;
-use std::io;
-
-#[cfg_attr(test, automock)]
-trait UserPasswordInput {
-    fn get_password(&self, prompt: &str) -> std::io::Result<String>;
-}
-
-impl UserPasswordInput for std::io::Stdin {
-    #[inline]
-    fn get_password(&self, prompt: &str) -> std::io::Result<String> {
-        Ok(rpassword::prompt_password_stdout(prompt)?)
-    }
-}
-
-#[derive(Debug, Copy, Clone)]
-/// Errors encountered by the user interaction
-pub enum CliError {
-    /// Canceled
-    Canceled,
-
-    /// Bad input
-    BadInput,
-}
-
-impl From<std::io::Error> for CliError {
-    fn from(_e: std::io::Error) -> Self {
-        CliError::BadInput
-    }
-}
 
 #[inline]
 /// Modify network keys command
-pub fn modify_network_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> {
-    inner_modify_network_keys(std::io::stdin(), key_pairs)
+pub fn modify_network_keys<UI: UserInteractionTrait>(
+    user_interaction: &UI,
+    key_pairs: DuniterKeyPairs,
+) -> Result<DuniterKeyPairs, UiError> {
+    inner_modify_network_keys(user_interaction, key_pairs)
 }
 
 /// Private function to modify network keys
-fn inner_modify_network_keys<T: UserPasswordInput>(
-    stdin: T,
+fn inner_modify_network_keys<UI: UserInteractionTrait>(
+    user_interaction: &UI,
     mut key_pairs: DuniterKeyPairs,
-) -> Result<DuniterKeyPairs, CliError> {
-    key_pairs.network_keypair = salt_password_prompt(stdin)?;
+) -> Result<DuniterKeyPairs, UiError> {
+    key_pairs.network_keypair = salt_password_prompt(user_interaction)?;
     Ok(key_pairs)
 }
 
 #[inline]
 /// Modify member keys command
-pub fn modify_member_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> {
-    inner_modify_member_keys(std::io::stdin(), key_pairs)
+pub fn modify_member_keys<UI: UserInteractionTrait>(
+    user_interaction: &UI,
+    key_pairs: DuniterKeyPairs,
+) -> Result<DuniterKeyPairs, UiError> {
+    inner_modify_member_keys(user_interaction, key_pairs)
 }
 
 /// Private function to modify network keys
-fn inner_modify_member_keys<T: UserPasswordInput>(
-    stdin: T,
+fn inner_modify_member_keys<UI: UserInteractionTrait>(
+    user_interaction: &UI,
     mut key_pairs: DuniterKeyPairs,
-) -> Result<DuniterKeyPairs, CliError> {
-    key_pairs.member_keypair = Some(salt_password_prompt(stdin)?);
+) -> Result<DuniterKeyPairs, UiError> {
+    key_pairs.member_keypair = Some(salt_password_prompt(user_interaction)?);
     Ok(key_pairs)
 }
 
 /// Ask user for confirmation and Clear keys command
-pub fn clear_keys(network: bool, member: bool, mut key_pairs: DuniterKeyPairs) -> DuniterKeyPairs {
+pub fn clear_keys<UI: UserInteractionTrait>(
+    user_interaction: &UI,
+    network: bool,
+    member: bool,
+    mut key_pairs: DuniterKeyPairs,
+) -> DuniterKeyPairs {
     if network {
-        if let Ok("y") = question_prompt("Clear your network keypair?", &["y", "n"]) {
-            println!("Generating a new network keypair!");
+        if let Ok(0) = user_interaction.question_prompt("Clear your network keypair?", &["y", "n"])
+        {
+            user_interaction.message("Generating a new network keypair!");
             clear_network_key(&mut key_pairs);
         }
     }
     if member {
-        if let Ok("y") = question_prompt("Clear your member keypair?", &["y", "n"]) {
-            println!("Deleting member keypair!");
+        if let Ok(0) = user_interaction.question_prompt("Clear your member keypair?", &["y", "n"]) {
+            user_interaction.message("Deleting member keypair!");
             clear_member_key(&mut key_pairs);
         }
     }
@@ -120,23 +103,29 @@ fn clear_member_key(key_pairs: &mut DuniterKeyPairs) {
 }
 
 /// Show keys command
-pub fn show_keys(key_pairs: DuniterKeyPairs) {
-    show_network_keys(&key_pairs);
-    show_member_keys(&key_pairs);
+pub fn show_keys<UI: UserInteractionTrait>(user_interaction: &UI, key_pairs: DuniterKeyPairs) {
+    show_network_keys(user_interaction, &key_pairs);
+    show_member_keys(user_interaction, &key_pairs);
 }
 
 #[inline]
 /// Show network keys
-pub fn show_network_keys(key_pairs: &DuniterKeyPairs) {
-    println!("Network key: {}", key_pairs.network_keypair);
+pub fn show_network_keys<UI: UserInteractionTrait>(
+    user_interaction: &UI,
+    key_pairs: &DuniterKeyPairs,
+) {
+    user_interaction.message(&format!("Network key: {}", key_pairs.network_keypair));
 }
 
 #[inline]
 /// Show member keys
-pub fn show_member_keys(key_pairs: &DuniterKeyPairs) {
+pub fn show_member_keys<UI: UserInteractionTrait>(
+    user_interaction: &UI,
+    key_pairs: &DuniterKeyPairs,
+) {
     match &key_pairs.member_keypair {
-        None => println!("No member key configured"),
-        Some(key) => println!("Member key: {}", key),
+        None => user_interaction.message("No member key configured"),
+        Some(key) => user_interaction.message(&format!("Member key: {}", key)),
     }
 }
 
@@ -156,27 +145,12 @@ pub fn save_keypairs(
     super::write_keypairs_file(&conf_keys_path, &key_pairs)
 }
 
-fn question_prompt<'a>(question: &str, answers: &[&'a str]) -> Result<&'a str, CliError> {
-    let mut buf = String::new();
-
-    println!("{} ({}):", question, answers.join("/"));
-    let res = io::stdin().read_line(&mut buf);
-
-    if res.is_ok() {
-        answers
-            .iter()
-            .find(|x| **x == buf.trim())
-            .copied()
-            .ok_or(CliError::Canceled)
-    } else {
-        Err(CliError::Canceled)
-    }
-}
-
-fn salt_password_prompt<T: UserPasswordInput>(stdin: T) -> Result<KeyPairEnum, CliError> {
-    let salt = stdin.get_password("Salt: ")?;
+fn salt_password_prompt<UI: UserInteractionTrait>(
+    user_interaction: &UI,
+) -> Result<KeyPairEnum, UiError> {
+    let salt = user_interaction.get_password("Salt: ")?;
     if !salt.is_empty() {
-        let password = stdin.get_password("Password: ")?;
+        let password = user_interaction.get_password("Password: ")?;
         if !password.is_empty() {
             let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters();
             let key_pairs = KeyPairEnum::Ed25519(
@@ -184,25 +158,29 @@ fn salt_password_prompt<T: UserPasswordInput>(stdin: T) -> Result<KeyPairEnum, C
             );
             Ok(key_pairs)
         } else {
-            Err(CliError::BadInput)
+            Err(UiError::BadInput)
         }
     } else {
-        Err(CliError::BadInput)
+        Err(UiError::BadInput)
     }
 }
 
 /// The wizard key function
-pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> {
-    let mut answer = question_prompt("Modify your network keypair?", &["y", "n"])?;
-    if answer == "y" {
-        key_pairs.network_keypair = salt_password_prompt(std::io::stdin())?;
+pub fn key_wizard<UI: UserInteractionTrait>(
+    user_interaction: &UI,
+    mut key_pairs: DuniterKeyPairs,
+) -> Result<DuniterKeyPairs, UiError> {
+    let mut answer =
+        user_interaction.question_prompt("Modify your network keypair?", &["y", "n"])?;
+    if answer == 0 {
+        key_pairs.network_keypair = salt_password_prompt(user_interaction)?;
     }
 
-    answer = question_prompt("Modify your member keypair?", &["y", "n", "d"])?;
-    if answer == "y" {
-        key_pairs.member_keypair = Some(salt_password_prompt(std::io::stdin())?);
-    } else if answer == "d" {
-        println!("Deleting member keypair!");
+    answer = user_interaction.question_prompt("Modify your member keypair?", &["y", "n", "d"])?;
+    if answer == 0 {
+        key_pairs.member_keypair = Some(salt_password_prompt(user_interaction)?);
+    } else if answer == 2 {
+        user_interaction.message("Deleting member keypair!");
         clear_member_key(&mut key_pairs);
     }
 
@@ -212,6 +190,7 @@ pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, Cli
 #[cfg(test)]
 mod tests {
     use super::*;
+    // use crate::ui::ui_trait::MockU;
 
     use unwrap::unwrap;
 
@@ -223,8 +202,8 @@ mod tests {
     static SALT_TEST: &str = "testsalt";
     static PASSWORD_TEST: &str = "testpassword";
 
-    fn setup_user_password_input() -> MockUserPasswordInput {
-        let mut stdin_mock = MockUserPasswordInput::new();
+    fn setup_user_password_input() -> MockUserInteractionTrait {
+        let mut stdin_mock = MockUserInteractionTrait::new();
         stdin_mock
             .expect_get_password()
             .returning(|prompt| {
@@ -233,10 +212,7 @@ mod tests {
                 } else if prompt.starts_with("Password:") {
                     Ok(PASSWORD_TEST.to_owned())
                 } else {
-                    Err(std::io::Error::new(
-                        std::io::ErrorKind::InvalidInput,
-                        format!("should not be called with {}", prompt),
-                    ))
+                    Err(UiError::BadInput)
                 }
             })
             .times(2);
@@ -270,7 +246,7 @@ mod tests {
         let key_pairs = setup_keys(false);
         let stdin_mock = setup_user_password_input();
         let result_key_pairs =
-            inner_modify_member_keys(stdin_mock, key_pairs).expect("Fail to read new member keys");
+            inner_modify_member_keys(&stdin_mock, key_pairs).expect("Fail to read new member keys");
         // We expect network key not to change
         assert_eq!(
             result_key_pairs.network_keypair.public_key(),
@@ -310,7 +286,7 @@ mod tests {
     fn test_modify_network_keys() {
         let key_pairs = setup_keys(false);
         let stdin_mock = setup_user_password_input();
-        let result_key_pairs = inner_modify_network_keys(stdin_mock, key_pairs)
+        let result_key_pairs = inner_modify_network_keys(&stdin_mock, key_pairs)
             .expect("Fail to read new network keys");
         // We expect network key to update
         assert_eq!(
diff --git a/lib/core/conf/src/lib.rs b/lib/core/conf/src/lib.rs
index 36a47b915b560efb1943935e342fe1a0f33d7bc1..66e964dfe36bc5c190954ed6c9b5dbdf9f45ae75 100644
--- a/lib/core/conf/src/lib.rs
+++ b/lib/core/conf/src/lib.rs
@@ -40,6 +40,7 @@ mod global_conf;
 pub mod keypairs;
 pub mod modules_conf;
 mod resources;
+pub mod ui;
 mod v1;
 
 pub use crate::errors::DursConfError;
diff --git a/lib/core/conf/src/ui/mod.rs b/lib/core/conf/src/ui/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..adc7bdcf0f0e1a70eb1e1315a10262ea00407b48
--- /dev/null
+++ b/lib/core/conf/src/ui/mod.rs
@@ -0,0 +1,19 @@
+//! Durs-core ui module.
+pub mod ui_cli;
+pub mod ui_trait;
+
+#[derive(Debug, Copy, Clone)]
+/// Errors encountered by the user interaction
+pub enum UiError {
+    /// Canceled
+    Canceled,
+
+    /// Bad input
+    BadInput,
+}
+
+impl From<std::io::Error> for UiError {
+    fn from(_e: std::io::Error) -> Self {
+        Self::BadInput
+    }
+}
diff --git a/lib/core/conf/src/ui/ui_cli.rs b/lib/core/conf/src/ui/ui_cli.rs
new file mode 100644
index 0000000000000000000000000000000000000000..32e616392646cec1d5b2a84f9a5ae7d7ae494e62
--- /dev/null
+++ b/lib/core/conf/src/ui/ui_cli.rs
@@ -0,0 +1,37 @@
+//! Durs-core ui: cli trait implementation.
+use crate::ui::ui_trait::*;
+use crate::ui::*;
+
+use std::io;
+
+/// An empty struct for cli user interaction
+#[derive(Debug, Clone, Copy)]
+pub struct CLI {}
+
+impl UserInteractionTrait for CLI {
+    #[inline]
+    fn message(&self, message: &str) {
+        println!("{}", message);
+    }
+
+    fn question_prompt<'a>(&self, question: &str, answers: &[&'a str]) -> Result<usize, UiError> {
+        let mut buf = String::new();
+
+        println!("{} ({}):", question, answers.join("/"));
+        let res = io::stdin().read_line(&mut buf);
+
+        if res.is_ok() {
+            answers
+                .iter()
+                .position(|x| *x == buf.trim())
+                .ok_or(UiError::Canceled)
+        } else {
+            Err(UiError::Canceled)
+        }
+    }
+
+    #[inline]
+    fn get_password(&self, prompt: &str) -> Result<String, UiError> {
+        rpassword::prompt_password_stdout(prompt).map_err(Into::into)
+    }
+}
diff --git a/lib/core/conf/src/ui/ui_trait.rs b/lib/core/conf/src/ui/ui_trait.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b03334395053003995103ee6d16ceed5d7efed2c
--- /dev/null
+++ b/lib/core/conf/src/ui/ui_trait.rs
@@ -0,0 +1,18 @@
+//! Durs-core ui: traits definition.
+use crate::ui::*;
+
+#[cfg(test)]
+use mockall::*;
+
+/// A trait for all user interaction somehow TODO rewrite
+#[cfg_attr(test, automock)]
+pub trait UserInteractionTrait {
+    /// Print a message to the user
+    fn message(&self, message: &str);
+
+    /// Ask a question to the user
+    fn question_prompt<'a>(&self, question: &str, answers: &[&'a str]) -> Result<usize, UiError>;
+
+    /// Get a password from the user
+    fn get_password(&self, prompt: &str) -> Result<String, UiError>;
+}
diff --git a/lib/core/core/src/commands/keys.rs b/lib/core/core/src/commands/keys.rs
index a1c49ed7a61fab1b5df2b6749077786e9a922dfb..c91a56d2db87b90662545dcf2ad8149683931e82 100644
--- a/lib/core/core/src/commands/keys.rs
+++ b/lib/core/core/src/commands/keys.rs
@@ -20,6 +20,7 @@ use crate::errors::DursCoreError;
 use crate::DursCore;
 use clap::arg_enum;
 use durs_conf::keypairs::cli::*;
+use durs_conf::ui::ui_cli::CLI;
 use durs_conf::DuRsConf;
 
 #[derive(StructOpt, Debug, Clone, Copy)]
@@ -130,42 +131,44 @@ pub struct ShowOpt {}
 
 impl DursExecutableCoreCommand for KeysOpt {
     fn execute(self, durs_core: DursCore<DuRsConf>) -> Result<(), DursCoreError> {
+        let cli = CLI {};
         let profile_path = durs_core.soft_meta_datas.profile_path;
         let keypairs_file = durs_core.options.keypairs_file;
         let keypairs = durs_core.keypairs;
 
         match self.subcommand {
             KeysSubCommand::Wizard(_) => {
-                let new_keypairs = key_wizard(keypairs)?;
+                let new_keypairs = key_wizard(&cli, keypairs)?;
                 save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                     .map_err(DursCoreError::FailWriteKeypairsFile)
                     .and_then(|_| {
-                        show_keys(new_keypairs);
+                        show_keys(&cli, new_keypairs);
                         Ok(())
                     })
             }
             KeysSubCommand::Modify(modify_opt) => match modify_opt.subcommand {
                 ModifySubCommand::NetworkSaltPassword => {
-                    let new_keypairs = modify_network_keys(keypairs)?;
+                    let new_keypairs = modify_network_keys(&cli, keypairs)?;
                     save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                         .map_err(DursCoreError::FailWriteKeypairsFile)
                         .and_then(|_| {
-                            show_network_keys(&new_keypairs);
+                            show_network_keys(&cli, &new_keypairs);
                             Ok(())
                         })
                 }
                 ModifySubCommand::MemberSaltPassword => {
-                    let new_keypairs = modify_member_keys(keypairs)?;
+                    let new_keypairs = modify_member_keys(&cli, keypairs)?;
                     save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                         .map_err(DursCoreError::FailWriteKeypairsFile)
                         .and_then(|_| {
-                            show_member_keys(&new_keypairs);
+                            show_member_keys(&cli, &new_keypairs);
                             Ok(())
                         })
                 }
             },
             KeysSubCommand::Clear(clear_opt) => {
                 let new_keypairs = clear_keys(
+                    &cli,
                     clear_opt.key.is_network(),
                     clear_opt.key.is_member(),
                     keypairs,
@@ -173,12 +176,12 @@ impl DursExecutableCoreCommand for KeysOpt {
                 save_keypairs(profile_path, &keypairs_file, &new_keypairs)
                     .map_err(DursCoreError::FailWriteKeypairsFile)
                     .and_then(|_| {
-                        show_keys(new_keypairs);
+                        show_keys(&cli, new_keypairs);
                         Ok(())
                     })
             }
             KeysSubCommand::Show(_) => {
-                show_keys(keypairs);
+                show_keys(&cli, keypairs);
                 Ok(())
             }
         }
diff --git a/lib/core/core/src/errors.rs b/lib/core/core/src/errors.rs
index dd9d85ab96beb228cf1d86c87b1bb11e38b40557..97faf814c707f51c19ab7b2bc19e4bec859c3032 100644
--- a/lib/core/core/src/errors.rs
+++ b/lib/core/core/src/errors.rs
@@ -17,7 +17,7 @@
 
 use crate::logger::InitLoggerError;
 use dubp_currency_params::db::CurrencyParamsDbError;
-use durs_conf::keypairs::cli::CliError;
+use durs_conf::ui::UiError;
 use durs_module::{ModuleStaticName, PlugModuleError};
 use failure::{Error, Fail};
 
@@ -67,7 +67,7 @@ pub enum DursCoreError {
     SyncWithoutSource,
     /// Error on keys sub-command
     #[fail(display = "Error on keys sub-command")]
-    WizardKeysError(CliError),
+    WizardKeysError(UiError),
 }
 
 impl From<InitLoggerError> for DursCoreError {
@@ -76,8 +76,8 @@ impl From<InitLoggerError> for DursCoreError {
     }
 }
 
-impl From<CliError> for DursCoreError {
-    fn from(e: CliError) -> Self {
+impl From<UiError> for DursCoreError {
+    fn from(e: UiError) -> Self {
         DursCoreError::WizardKeysError(e)
     }
 }