From badf52ce4bc3eb9429fe769d43a23292009efe30 Mon Sep 17 00:00:00 2001
From: Nicolas80 <nicolas.pmail@protonmail.com>
Date: Sat, 28 Dec 2024 18:32:19 +0100
Subject: [PATCH] Adding db persistence for all SecretFormat of vault keys as
 well as supporting derivations

* Added "/.idea" exclusion in .gitignore (for when using JetBrains IDEs)
* Added dialoguer dependency for easier user input handling (see in inputs.rs)
* Added sea-orm dependency to allow having DB entity mappings and use a local sqlite file database
* Added rstest test dependency for parameterized tests support
* Added derivation tests for each SecretFormat (including cesium v1 key derivation, using sp_core::ed25519::Pair)
* Made a lot of changes to add vault_account and vault_derivation db tables to persist vault keys & derivations
* Added support for KeyPair::Ed25519 linking to sp_core::ed25519::Pair which can be created from secret seed retrieved from nacl::sign::Keypair (which is created from cesium id + secret)
** This was necessary to allow deriving keys from "cesium v1" keys (to be reviewed - it might be a bad idea to permit that from a security point of view)
* Only kept original (substrate) keyfiles support for migration (use "vault list-files" and "vault migrate")
* Added possibility to give either "-a" Address or "-v" Vault Name as general option
* Added extra commands in Vault
** list-files: (deprecated)List available key files (needs to be migrated with command "vault migrate" in order to use them)
** migrate: (deprecated)Migrate old key files into db (will have to provide password for each key)
** 'list' now has sub-commands 'all' or 'root' to show all keys or only root keys (without derivation path)
** use: "Use specific vault key (changes the config address)", which will have the same behaviour as `gcli <-a <Address>|-v <VaultName>> config save` (left a FIXME in there to review)
** derivation: Add a derivation to an existing (root) vault key
** rename: Give a meaningful vault name to a vault key or derivation
** remove: Remove a vault key (and potential derivations if it's a root key)
* Had to bubble up "await" and "async" in a lot of places
* ...
---
 .gitignore                       |    1 +
 Cargo.lock                       | 1185 ++++++++++++++++++++++++++++--
 Cargo.toml                       |    5 +
 src/commands/identity.rs         |   18 +-
 src/commands/net_test.rs         |    4 +-
 src/commands/revocation.rs       |    8 +-
 src/commands/vault.rs            |  976 ++++++++++++++++++++++--
 src/conf.rs                      |   21 +-
 src/data.rs                      |   65 +-
 src/database.rs                  |   71 ++
 src/entities.rs                  |    2 +
 src/entities/vault_account.rs    |  106 +++
 src/entities/vault_derivation.rs |  150 ++++
 src/inputs.rs                    |   85 +++
 src/keys.rs                      |  461 +++++++++++-
 src/main.rs                      |   58 +-
 src/utils.rs                     |   25 +-
 17 files changed, 3067 insertions(+), 174 deletions(-)
 create mode 100644 src/database.rs
 create mode 100644 src/entities.rs
 create mode 100644 src/entities/vault_account.rs
 create mode 100644 src/entities/vault_derivation.rs
 create mode 100644 src/inputs.rs

diff --git a/.gitignore b/.gitignore
index ea8c4bf..40d9aca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 /target
+/.idea
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 574da92..b94f02f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -89,6 +89,17 @@ dependencies = [
  "sha2 0.10.8",
 ]
 
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "ahash"
 version = "0.8.11"
@@ -111,6 +122,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "aliasable"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+
 [[package]]
 name = "allocator-api2"
 version = "0.2.18"
@@ -571,7 +588,7 @@ dependencies = [
  "futures-lite",
  "parking",
  "polling",
- "rustix 0.38.37",
+ "rustix 0.38.42",
  "slab",
  "tracing",
  "windows-sys 0.59.0",
@@ -614,7 +631,7 @@ dependencies = [
  "cfg-if",
  "event-listener 5.3.1",
  "futures-lite",
- "rustix 0.38.37",
+ "rustix 0.38.42",
  "tracing",
 ]
 
@@ -630,12 +647,34 @@ dependencies = [
  "cfg-if",
  "futures-core",
  "futures-io",
- "rustix 0.38.37",
+ "rustix 0.38.42",
  "signal-hook-registry",
  "slab",
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "async-task"
 version = "4.7.1"
@@ -650,7 +689,16 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
 ]
 
 [[package]]
@@ -767,6 +815,20 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bigdecimal"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c"
+dependencies = [
+ "autocfg",
+ "libm",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "serde",
+]
+
 [[package]]
 name = "bincode"
 version = "1.3.3"
@@ -822,6 +884,9 @@ name = "bitflags"
 version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "bitvec"
@@ -896,6 +961,29 @@ dependencies = [
  "piper",
 ]
 
+[[package]]
+name = "borsh"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03"
+dependencies = [
+ "borsh-derive",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate 3.2.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "bounded-collections"
 version = "0.2.0"
@@ -929,6 +1017,28 @@ version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c"
 
+[[package]]
+name = "bytecheck"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
+dependencies = [
+ "bytecheck_derive",
+ "ptr_meta",
+ "simdutf8",
+]
+
+[[package]]
+name = "bytecheck_derive"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "byteorder"
 version = "1.5.0"
@@ -1001,6 +1111,7 @@ dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "num-traits",
+ "serde",
  "windows-targets 0.52.6",
 ]
 
@@ -1046,7 +1157,7 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1103,7 +1214,7 @@ dependencies = [
  "crossterm 0.27.0",
  "strum",
  "strum_macros",
- "unicode-width",
+ "unicode-width 0.1.14",
 ]
 
 [[package]]
@@ -1149,6 +1260,19 @@ dependencies = [
  "toml 0.5.11",
 ]
 
+[[package]]
+name = "console"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "unicode-width 0.2.0",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "const-oid"
 version = "0.9.6"
@@ -1231,6 +1355,21 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "crc"
+version = "3.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
 [[package]]
 name = "crc32fast"
 version = "1.4.2"
@@ -1375,7 +1514,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1423,7 +1562,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim 0.11.1",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1445,7 +1584,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
 dependencies = [
  "darling_core 0.20.10",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1468,9 +1607,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
 dependencies = [
  "const-oid",
+ "pem-rfc7468",
  "zeroize",
 ]
 
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+ "serde",
+]
+
 [[package]]
 name = "derivative"
 version = "2.2.0"
@@ -1490,7 +1640,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1501,7 +1651,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1514,7 +1664,20 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustc_version",
- "syn 2.0.79",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "dialoguer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
+dependencies = [
+ "console",
+ "shell-words",
+ "tempfile",
+ "thiserror",
+ "zeroize",
 ]
 
 [[package]]
@@ -1587,7 +1750,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1627,12 +1790,18 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "syn 2.0.79",
+ "syn 2.0.87",
  "termcolor",
  "toml 0.8.19",
  "walkdir",
 ]
 
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
 [[package]]
 name = "downcast-rs"
 version = "1.2.1"
@@ -1725,6 +1894,9 @@ name = "either"
 version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "elliptic-curve"
@@ -1746,6 +1918,12 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.34"
@@ -1782,12 +1960,23 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "errno"
-version = "0.3.9"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
 dependencies = [
  "libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1833,7 +2022,7 @@ dependencies = [
  "prettyplease",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -1962,12 +2151,38 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "flume"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin",
+]
+
 [[package]]
 name = "fnv"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
 [[package]]
 name = "form_urlencoded"
 version = "1.2.1"
@@ -2058,6 +2273,17 @@ dependencies = [
  "num_cpus",
 ]
 
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
 [[package]]
 name = "futures-io"
 version = "0.3.30"
@@ -2085,7 +2311,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -2136,6 +2362,7 @@ dependencies = [
  "colored",
  "comfy-table",
  "confy",
+ "dialoguer",
  "directories 5.0.1",
  "env_logger",
  "futures",
@@ -2147,7 +2374,9 @@ dependencies = [
  "parity-scale-codec",
  "reqwest",
  "rpassword",
+ "rstest",
  "scrypt",
+ "sea-orm",
  "serde",
  "serde_json",
  "sp-core",
@@ -2205,6 +2434,12 @@ version = "0.31.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
 
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
 [[package]]
 name = "graphql-introspection-query"
 version = "0.2.0"
@@ -2314,6 +2549,9 @@ name = "hashbrown"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash 0.7.8",
+]
 
 [[package]]
 name = "hashbrown"
@@ -2321,7 +2559,7 @@ version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
 dependencies = [
- "ahash",
+ "ahash 0.8.11",
 ]
 
 [[package]]
@@ -2330,7 +2568,7 @@ version = "0.14.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 dependencies = [
- "ahash",
+ "ahash 0.8.11",
  "allocator-api2",
  "serde",
 ]
@@ -2341,6 +2579,15 @@ version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
 
+[[package]]
+name = "hashlink"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
+dependencies = [
+ "hashbrown 0.14.5",
+]
+
 [[package]]
 name = "heck"
 version = "0.4.1"
@@ -2416,6 +2663,15 @@ dependencies = [
  "hmac 0.8.1",
 ]
 
+[[package]]
+name = "home"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "http"
 version = "0.2.12"
@@ -2559,7 +2815,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim 0.10.0",
- "syn 2.0.79",
+ "syn 2.0.87",
  "unic-langid",
 ]
 
@@ -2573,7 +2829,7 @@ dependencies = [
  "i18n-config",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -2671,6 +2927,17 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590"
 
+[[package]]
+name = "inherent"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "inout"
 version = "0.1.3"
@@ -2693,7 +2960,7 @@ dependencies = [
  "newline-converter",
  "thiserror",
  "unicode-segmentation",
- "unicode-width",
+ "unicode-width 0.1.14",
 ]
 
 [[package]]
@@ -3034,12 +3301,15 @@ name = "lazy_static"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
 
 [[package]]
 name = "libc"
-version = "0.2.159"
+version = "0.2.168"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
+checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
 
 [[package]]
 name = "libm"
@@ -3105,6 +3375,17 @@ dependencies = [
  "libsecp256k1-core",
 ]
 
+[[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
 [[package]]
 name = "linux-raw-sys"
 version = "0.1.4"
@@ -3169,6 +3450,16 @@ dependencies = [
  "regex-automata 0.1.10",
 ]
 
+[[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest 0.10.7",
+]
+
 [[package]]
 name = "memchr"
 version = "2.7.4"
@@ -3181,7 +3472,7 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64"
 dependencies = [
- "rustix 0.38.37",
+ "rustix 0.38.42",
 ]
 
 [[package]]
@@ -3265,6 +3556,23 @@ version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "30aefc44d813c51b5e7952950e87c17f2e0e1a3274d63c8281a701e05323d548"
 
+[[package]]
+name = "native-tls"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
 [[package]]
 name = "newline-converter"
 version = "0.2.2"
@@ -3322,6 +3630,29 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+dependencies = [
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
 [[package]]
 name = "num-format"
 version = "0.4.4"
@@ -3341,6 +3672,17 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
 [[package]]
 name = "num-rational"
 version = "0.4.2"
@@ -3359,6 +3701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 dependencies = [
  "autocfg",
+ "libm",
 ]
 
 [[package]]
@@ -3408,17 +3751,89 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
 
 [[package]]
-name = "openssl-probe"
-version = "0.1.5"
+name = "openssl"
+version = "0.10.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
 
 [[package]]
-name = "option-ext"
-version = "0.2.0"
+name = "openssl-macros"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
-
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "ordered-float"
+version = "3.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ouroboros"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67"
+dependencies = [
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
+]
+
+[[package]]
+name = "ouroboros_macro"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd"
+dependencies = [
+ "heck 0.4.1",
+ "itertools 0.12.1",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "overload"
 version = "0.1.1"
@@ -3522,6 +3937,15 @@ dependencies = [
  "password-hash",
 ]
 
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
 [[package]]
 name = "percent-encoding"
 version = "2.3.1"
@@ -3545,7 +3969,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -3571,6 +3995,17 @@ dependencies = [
  "futures-io",
 ]
 
+[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
 [[package]]
 name = "pkcs8"
 version = "0.10.2"
@@ -3581,6 +4016,12 @@ dependencies = [
  "spki",
 ]
 
+[[package]]
+name = "pkg-config"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+
 [[package]]
 name = "polkavm-common"
 version = "0.9.0"
@@ -3605,7 +4046,7 @@ dependencies = [
  "polkavm-common",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -3615,7 +4056,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429"
 dependencies = [
  "polkavm-derive-impl",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -3628,7 +4069,7 @@ dependencies = [
  "concurrent-queue",
  "hermit-abi 0.4.0",
  "pin-project-lite",
- "rustix 0.38.37",
+ "rustix 0.38.42",
  "tracing",
  "windows-sys 0.59.0",
 ]
@@ -3650,6 +4091,12 @@ version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
 
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.20"
@@ -3666,7 +4113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
 dependencies = [
  "proc-macro2",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -3725,6 +4172,28 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.86"
@@ -3734,6 +4203,19 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+ "version_check",
+ "yansi",
+]
+
 [[package]]
 name = "psm"
 version = "0.1.23"
@@ -3743,6 +4225,26 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "ptr_meta"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
+dependencies = [
+ "ptr_meta_derive",
+]
+
+[[package]]
+name = "ptr_meta_derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.37"
@@ -3861,7 +4363,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -3908,6 +4410,21 @@ version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
+[[package]]
+name = "relative-path"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
+
+[[package]]
+name = "rend"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
+dependencies = [
+ "bytecheck",
+]
+
 [[package]]
 name = "reqwest"
 version = "0.11.27"
@@ -3991,6 +4508,35 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "rkyv"
+version = "0.7.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
+dependencies = [
+ "bitvec",
+ "bytecheck",
+ "bytes",
+ "hashbrown 0.12.3",
+ "ptr_meta",
+ "rend",
+ "rkyv_derive",
+ "seahash",
+ "tinyvec",
+ "uuid",
+]
+
+[[package]]
+name = "rkyv_derive"
+version = "0.7.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "rpassword"
 version = "7.3.1"
@@ -4002,6 +4548,56 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "rsa"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519"
+dependencies = [
+ "const-oid",
+ "digest 0.10.7",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rstest"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035"
+dependencies = [
+ "futures",
+ "futures-timer",
+ "rstest_macros",
+ "rustc_version",
+]
+
+[[package]]
+name = "rstest_macros"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a"
+dependencies = [
+ "cfg-if",
+ "glob",
+ "proc-macro-crate 3.2.0",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "relative-path",
+ "rustc_version",
+ "syn 2.0.87",
+ "unicode-ident",
+]
+
 [[package]]
 name = "rtoolbox"
 version = "0.0.2"
@@ -4032,7 +4628,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rust-embed-utils",
- "syn 2.0.79",
+ "syn 2.0.87",
  "walkdir",
 ]
 
@@ -4046,6 +4642,22 @@ dependencies = [
  "walkdir",
 ]
 
+[[package]]
+name = "rust_decimal"
+version = "1.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555"
+dependencies = [
+ "arrayvec 0.7.6",
+ "borsh",
+ "bytes",
+ "num-traits",
+ "rand",
+ "rkyv",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.24"
@@ -4089,15 +4701,15 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.37"
+version = "0.38.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
+checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
 dependencies = [
  "bitflags 2.6.0",
  "errno",
  "libc",
  "linux-raw-sys 0.4.14",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -4391,7 +5003,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "scale-info",
- "syn 2.0.79",
+ "syn 2.0.87",
  "thiserror",
 ]
 
@@ -4431,7 +5043,7 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367"
 dependencies = [
- "ahash",
+ "ahash 0.8.11",
  "cfg-if",
  "hashbrown 0.13.2",
 ]
@@ -4482,6 +5094,99 @@ dependencies = [
  "untrusted",
 ]
 
+[[package]]
+name = "sea-bae"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "sea-orm"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b24d72a69e89762982c29af249542b06c59fa131f87cc9d5b94be1f692b427a"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "bigdecimal",
+ "chrono",
+ "futures",
+ "log",
+ "ouroboros",
+ "rust_decimal",
+ "sea-orm-macros",
+ "sea-query",
+ "sea-query-binder",
+ "serde",
+ "serde_json",
+ "sqlx",
+ "strum",
+ "thiserror",
+ "time",
+ "tracing",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "sea-orm-macros"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0497f4fd82ecb2a222bea5319b9048f8ab58d4e734d095b062987acbcdeecdda"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "sea-bae",
+ "syn 2.0.87",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sea-query"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "085e94f7d7271c0393ac2d164a39994b1dff1b06bc40cd9a0da04f3d672b0fee"
+dependencies = [
+ "bigdecimal",
+ "chrono",
+ "inherent",
+ "ordered-float",
+ "rust_decimal",
+ "serde_json",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "sea-query-binder"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608"
+dependencies = [
+ "bigdecimal",
+ "chrono",
+ "rust_decimal",
+ "sea-query",
+ "serde_json",
+ "sqlx",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
 [[package]]
 name = "sec1"
 version = "0.7.3"
@@ -4595,7 +5300,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -4708,6 +5413,12 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
 [[package]]
 name = "shlex"
 version = "1.3.0"
@@ -4754,6 +5465,12 @@ dependencies = [
  "rand_core",
 ]
 
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
 [[package]]
 name = "simple-mermaid"
 version = "0.1.1"
@@ -4780,6 +5497,9 @@ name = "smallvec"
 version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "smol"
@@ -5063,7 +5783,7 @@ source = "git+https://github.com/duniter/duniter-polkadot-sdk#c84530c57a6f9ab808
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5073,7 +5793,7 @@ source = "git+https://github.com/duniter/duniter-polkadot-sdk.git?branch=duniter
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5217,7 +5937,7 @@ dependencies = [
  "proc-macro-crate 3.2.0",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5230,7 +5950,7 @@ dependencies = [
  "proc-macro-crate 3.2.0",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5316,7 +6036,7 @@ name = "sp-trie"
 version = "29.0.0"
 source = "git+https://github.com/duniter/duniter-polkadot-sdk.git?branch=duniter-substrate-v1.14.0#bcc60f3e4170c3908689252242f40761270c9a51"
 dependencies = [
- "ahash",
+ "ahash 0.8.11",
  "hash-db",
  "lazy_static",
  "memory-db",
@@ -5376,6 +6096,9 @@ name = "spin"
 version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
 
 [[package]]
 name = "spki"
@@ -5387,6 +6110,230 @@ dependencies = [
  "der",
 ]
 
+[[package]]
+name = "sqlformat"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
+dependencies = [
+ "nom",
+ "unicode_categories",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
+dependencies = [
+ "atoi",
+ "bigdecimal",
+ "byteorder",
+ "bytes",
+ "chrono",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener 5.3.1",
+ "futures-channel",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown 0.14.5",
+ "hashlink",
+ "hex",
+ "indexmap 2.6.0",
+ "log",
+ "memchr",
+ "native-tls",
+ "once_cell",
+ "paste",
+ "percent-encoding",
+ "rust_decimal",
+ "serde",
+ "serde_json",
+ "sha2 0.10.8",
+ "smallvec",
+ "sqlformat",
+ "thiserror",
+ "time",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
+dependencies = [
+ "dotenvy",
+ "either",
+ "heck 0.5.0",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2 0.10.8",
+ "sqlx-core",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+ "syn 2.0.87",
+ "tempfile",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-mysql"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bigdecimal",
+ "bitflags 2.6.0",
+ "byteorder",
+ "bytes",
+ "chrono",
+ "crc",
+ "digest 0.10.7",
+ "dotenvy",
+ "either",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "generic-array",
+ "hex",
+ "hkdf",
+ "hmac 0.12.1",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rand",
+ "rsa",
+ "rust_decimal",
+ "serde",
+ "sha1",
+ "sha2 0.10.8",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror",
+ "time",
+ "tracing",
+ "uuid",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bigdecimal",
+ "bitflags 2.6.0",
+ "byteorder",
+ "chrono",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac 0.12.1",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "num-bigint",
+ "once_cell",
+ "rand",
+ "rust_decimal",
+ "serde",
+ "serde_json",
+ "sha2 0.10.8",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror",
+ "time",
+ "tracing",
+ "uuid",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-sqlite"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
+dependencies = [
+ "atoi",
+ "chrono",
+ "flume",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "serde",
+ "serde_urlencoded",
+ "sqlx-core",
+ "time",
+ "tracing",
+ "url",
+ "uuid",
+]
+
 [[package]]
 name = "ss58-registry"
 version = "1.50.0"
@@ -5414,6 +6361,17 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -5442,7 +6400,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5513,7 +6471,7 @@ dependencies = [
  "scale-info",
  "scale-typegen",
  "subxt-metadata",
- "syn 2.0.79",
+ "syn 2.0.87",
  "thiserror",
  "tokio",
 ]
@@ -5573,7 +6531,7 @@ dependencies = [
  "quote",
  "scale-typegen",
  "subxt-codegen",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5601,9 +6559,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.79"
+version = "2.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
+checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5649,6 +6607,19 @@ version = "0.12.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
 
+[[package]]
+name = "tempfile"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix 0.38.42",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "termcolor"
 version = "1.4.1"
@@ -5675,7 +6646,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5688,6 +6659,37 @@ dependencies = [
  "once_cell",
 ]
 
+[[package]]
+name = "time"
+version = "0.3.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
 [[package]]
 name = "tinystr"
 version = "0.7.6"
@@ -5736,7 +6738,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -5897,7 +6899,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -6088,6 +7090,12 @@ dependencies = [
  "tinyvec",
 ]
 
+[[package]]
+name = "unicode-properties"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
+
 [[package]]
 name = "unicode-segmentation"
 version = "1.12.0"
@@ -6100,12 +7108,24 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
 
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
 
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
 [[package]]
 name = "universal-hash"
 version = "0.5.1"
@@ -6148,12 +7168,27 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
+[[package]]
+name = "uuid"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "valuable"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
 
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
 [[package]]
 name = "version_check"
 version = "0.9.5"
@@ -6215,6 +7250,12 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.93"
@@ -6237,7 +7278,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
  "wasm-bindgen-shared",
 ]
 
@@ -6271,7 +7312,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -6489,6 +7530,16 @@ dependencies = [
  "rustls-pki-types",
 ]
 
+[[package]]
+name = "whoami"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
+dependencies = [
+ "redox_syscall",
+ "wasite",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -6792,6 +7843,12 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
 [[package]]
 name = "yap"
 version = "0.11.0"
@@ -6816,7 +7873,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
 
 [[package]]
@@ -6836,5 +7893,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.87",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index efb44d9..272280b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,6 +37,7 @@ reqwest = { version = "^0.11.27", default-features = false, features = [
     "rustls-tls",
 ] }
 rpassword = "^7.3.1"
+dialoguer = "0.11.0"
 serde = { version = "^1.0", features = ["derive"] }
 serde_json = "^1.0.128"
 tokio = { version = "^1.40.0", features = ["macros"] }
@@ -45,6 +46,7 @@ bs58 = "^0.5.1"
 inquire = "^0.6.2"
 directories = "^5.0.1"
 comfy-table = "^7.1.1"
+sea-orm = { version = "1.1.0", features = [ "sqlx-sqlite", "runtime-tokio-native-tls", "macros" ] }
 
 # crypto
 scrypt = { version = "^0.11", default-features = false } # for old-style key generation
@@ -54,6 +56,9 @@ age = { default-features = false, version = "^0.10.0", features = ["armor"] }
 bip39 = { version = "^2.0.0", features = ["rand"] }                           # mnemonic
 colored = "2.1.0"
 
+# Tests
+rstest = "0.23.0"
+
 # allows to build gcli for different runtimes and with different predefined networks 
 [features]
 default = ["gdev"] # default runtime is "gdev", gdev network is available
diff --git a/src/commands/identity.rs b/src/commands/identity.rs
index 0d8bfc8..91299fd 100644
--- a/src/commands/identity.rs
+++ b/src/commands/identity.rs
@@ -124,7 +124,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 		}
 		Subcommand::GenRevocDoc => {
 			data = data.fetch_idty_index().await?;
-			commands::revocation::print_revoc_sig(&data)
+			commands::revocation::print_revoc_sig(&data).await
 		}
 		Subcommand::MemberCount => {
 			println!(
@@ -488,7 +488,7 @@ pub async fn confirm_identity(data: &Data, name: String) -> Result<(), subxt::Er
 
 /// generate revokation document and submit it immediately
 pub async fn revoke_identity(data: &Data) -> Result<(), subxt::Error> {
-	let (_payload, signature) = generate_revoc_doc(data);
+	let (_payload, signature) = generate_revoc_doc(data).await;
 
 	// Transform signature to MultiSignature
 	// TODO: allow other signature formats
@@ -519,6 +519,11 @@ pub fn generate_link_account(
 			let signature = keypair.sign(&payload);
 			(payload, Signature::Sr25519(signature))
 		}
+		KeyPair::Ed25519(keypair) => {
+			let signature = keypair.sign(&payload);
+			(payload, Signature::Ed25519(signature))
+		}
+		//FIXME Cleanup
 		KeyPair::Nacl(keypair) => {
 			let signature = nacl::sign::signature(&payload, &keypair.skey).expect("could not sign");
 			(payload, Signature::Nacl(signature))
@@ -545,6 +550,11 @@ pub fn generate_chok_payload(
 			let signature = keypair.sign(&payload);
 			(payload, Signature::Sr25519(signature))
 		}
+		KeyPair::Ed25519(keypair) => {
+			let signature = keypair.sign(&payload);
+			(payload, Signature::Ed25519(signature))
+		}
+		//FIXME Cleanup
 		KeyPair::Nacl(keypair) => {
 			// should not migrate to Nacl
 			let signature = nacl::sign::signature(&payload, &keypair.skey).expect("could not sign");
@@ -564,6 +574,8 @@ pub async fn link_account(
 	// TODO cleaner way to manage signature
 	let signature = match signature {
 		Signature::Sr25519(signature) => MultiSignature::Sr25519(signature.into()),
+		Signature::Ed25519(signature) => MultiSignature::Ed25519(signature.into()),
+		//FIXME Cleanup
 		Signature::Nacl(signature) => MultiSignature::Ed25519(signature.try_into().unwrap()),
 	};
 
@@ -588,6 +600,8 @@ pub async fn change_owner_key(
 	// TODO cleaner way to manage signature
 	let signature = match signature {
 		Signature::Sr25519(signature) => MultiSignature::Sr25519(signature.into()),
+		Signature::Ed25519(signature) => MultiSignature::Ed25519(signature.into()),
+		//FIXME Cleanup
 		Signature::Nacl(signature) => MultiSignature::Ed25519(signature.try_into().unwrap()),
 	};
 
diff --git a/src/commands/net_test.rs b/src/commands/net_test.rs
index 6f1ac34..f9cea93 100644
--- a/src/commands/net_test.rs
+++ b/src/commands/net_test.rs
@@ -4,7 +4,7 @@ use sp_core::DeriveJunction;
 use subxt::ext::sp_runtime::MultiAddress;
 
 pub async fn repart(data: &Data, target: u32, actual_repart: Option<u32>) -> anyhow::Result<()> {
-	let KeyPair::Sr25519(keypair) = data.keypair() else {
+	let KeyPair::Sr25519(keypair) = data.keypair().await else {
 		panic!("Cesium keys not implemented there")
 	};
 	let mut pairs = Vec::new();
@@ -43,7 +43,7 @@ pub async fn repart(data: &Data, target: u32, actual_repart: Option<u32>) -> any
 }
 
 pub async fn spam_roll(data: &Data, actual_repart: usize) -> anyhow::Result<()> {
-	let KeyPair::Sr25519(keypair) = data.keypair() else {
+	let KeyPair::Sr25519(keypair) = data.keypair().await else {
 		panic!("Cesium keys not implemented there")
 	};
 	let client = data.client();
diff --git a/src/commands/revocation.rs b/src/commands/revocation.rs
index d9d60e1..aec5690 100644
--- a/src/commands/revocation.rs
+++ b/src/commands/revocation.rs
@@ -4,15 +4,15 @@ use crate::*;
 // use crate::runtime::runtime_types::pallet_identity::types::RevocationPayload;
 type EncodedRevocationPayload = Vec<u8>;
 
-pub fn print_revoc_sig(data: &Data) {
-	let (_, signature) = generate_revoc_doc(data);
+pub async fn print_revoc_sig(data: &Data) {
+	let (_, signature) = generate_revoc_doc(data).await;
 	println!("revocation payload signature");
 	println!("0x{}", hex::encode(signature));
 }
 
-pub fn generate_revoc_doc(data: &Data) -> (EncodedRevocationPayload, sr25519::Signature) {
+pub async fn generate_revoc_doc(data: &Data) -> (EncodedRevocationPayload, sr25519::Signature) {
 	let payload = (b"revo", data.genesis_hash, data.idty_index()).encode();
-	let KeyPair::Sr25519(keypair) = data.keypair() else {
+	let KeyPair::Sr25519(keypair) = data.keypair().await else {
 		panic!("Cesium keys not implemented there")
 	};
 	let signature = keypair.sign(&payload);
diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index b6e09b7..1478b67 100644
--- a/src/commands/vault.rs
+++ b/src/commands/vault.rs
@@ -1,19 +1,73 @@
+use crate::entities::vault_account::CryptoType;
+use crate::entities::{vault_account, vault_derivation};
 use crate::*;
 use age::secrecy::Secret;
+use comfy_table::{Cell, Table};
+use sea_orm::ActiveValue::Set;
+use sea_orm::{ActiveModelTrait, EntityTrait, ModelTrait};
+use sea_orm::{ColumnTrait, QueryFilter};
+use sea_orm::{ConnectionTrait, TransactionTrait};
 use std::io::{Read, Write};
+use std::path::PathBuf;
 
 /// define universal dividends subcommands
-#[derive(Clone, Default, Debug, clap::Parser)]
+#[derive(Clone, Debug, clap::Parser)]
 pub enum Subcommand {
-	#[default]
 	/// List available keys
-	List,
-	/// Show where vault stores secret
-	Where,
+	#[clap(subcommand)]
+	List(ListChoice),
+	/// Use specific vault key (changes the config address)
+	Use {
+		#[clap(flatten)]
+		address_or_vault_name: AddressOrVaultNameGroup,
+	},
 	/// Generate a mnemonic
 	Generate,
-	/// Import mnemonic with interactive prompt
-	Import,
+	/// Import key from (substrate)mnemonic or other format with interactive prompt
+	Import {
+		/// Secret key format (substrate, seed, cesium)
+		#[clap(short = 'S', long, required = false, default_value = SecretFormat::Substrate)]
+		secret_format: SecretFormat,
+	},
+	/// Add a derivation to an existing (root) vault key
+	#[clap(alias = "deriv")]
+	#[clap(alias = "derive")]
+	Derivation {
+		#[clap(flatten)]
+		address_or_vault_name: AddressOrVaultNameGroup,
+	},
+	/// Give a meaningful vault name to a vault key or derivation
+	Rename {
+		/// Account Address
+		address: AccountId,
+	},
+	/// Remove a vault key (and potential derivations if it's a root key)
+	Remove {
+		#[clap(flatten)]
+		address_or_vault_name: AddressOrVaultNameGroup,
+	},
+	/// (deprecated)List available key files (needs to be migrated with command "vault migrate" in order to use them)
+	ListFiles,
+	/// (deprecated)Migrate old key files into db (will have to provide password for each key)
+	Migrate,
+	/// Show where vault db (or old keys) is stored
+	Where,
+}
+
+#[derive(Clone, Default, Debug, clap::Parser)]
+pub enum ListChoice {
+	/// List all keys and derivations in the vault
+	#[default]
+	All,
+	/// List only root keys
+	Root,
+}
+
+pub struct VaultDataToImport {
+	secret_format: SecretFormat,
+	secret: keys::Secret,
+	address: AccountId,
+	key_pair: KeyPair,
 }
 
 // encrypt input with passphrase
@@ -43,76 +97,898 @@ fn decrypt(input: &[u8], passphrase: String) -> Result<Vec<u8>, age::DecryptErro
 }
 
 /// handle ud commands
-pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
+pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
 	// match subcommand
 	match command {
-		Subcommand::List => {
-			if let Ok(entries) = std::fs::read_dir(data.project_dir.data_dir()) {
+		Subcommand::List(choice) => match choice {
+			ListChoice::All => {
+				let derivations = vault_derivation::list_all_derivations_in_order(
+					data.connection.as_ref().unwrap(),
+				)
+				.await?;
+
+				let table = compute_vault_derivations_table(&derivations).await?;
+
 				println!("available keys:");
-				entries.for_each(|e| println!("{}", e.unwrap().file_name().to_str().unwrap()));
-			} else {
-				println!("could not read project dir");
+				println!("{table}");
+			}
+			ListChoice::Root => {
+				let derivations = vault_derivation::list_all_root_derivations_in_order(
+					data.connection.as_ref().unwrap(),
+				)
+				.await?;
+
+				let table = compute_vault_derivations_table(&derivations).await?;
+
+				println!("available root keys:");
+				println!("{table}");
 			}
+		},
+		Subcommand::ListFiles => {
+			let vault_key_addresses = fetch_vault_key_addresses(&data).await?;
+
+			let table = compute_vault_key_files_table(&vault_key_addresses).await?;
+
+			println!("available key files (needs to be migrated with command \"vault migrate\" in order to use them):");
+			println!("{table}");
 		}
-		Subcommand::Where => {
-			println!("{}", data.project_dir.data_dir().to_str().unwrap());
+		Subcommand::Use {
+			address_or_vault_name,
+		} => {
+			let derivation = retrieve_vault_derivation(&data, address_or_vault_name).await?;
+
+			//FIXME not sure if this is ok (but since it's a CLI; this data instance won't be used afterwards)
+			let mut data = data;
+			data.cfg.address =
+				Some(AccountId::from_str(&derivation.address).expect("invalid address"));
+
+			println!("Using key {}", derivation);
+			conf::save_config(&data);
 		}
 		Subcommand::Generate => {
 			// TODO allow custom word count
 			let mnemonic = bip39::Mnemonic::generate(12).unwrap();
 			println!("{mnemonic}");
 		}
-		Subcommand::Import => {
-			let mnemonic = rpassword::prompt_password("Mnemonic: ")?;
+		Subcommand::Import { secret_format } => {
+			let vault_data_for_import =
+				prompt_secret_and_compute_vault_data_to_import(secret_format)?;
+
+			println!(
+				"Trying to import for address :'{}'",
+				vault_data_for_import.address
+			);
+
+			if let Some(derivation) =
+				vault_derivation::Entity::find_by_id(vault_data_for_import.address.to_string())
+					.one(data.connection.as_ref().unwrap())
+					.await?
+			{
+				println!(
+					"Vault entry already exists for address:'{}'",
+					vault_data_for_import.address
+				);
+
+				let linked_derivations = vault_derivation::fetch_all_linked_derivations_in_order(
+					data.connection.as_ref().unwrap(),
+					&derivation.root_address.clone(),
+				)
+				.await?;
+				println!("Here are all the linked derivations already present in the vault:");
+
+				let table = compute_vault_derivations_table(&linked_derivations).await?;
+				println!("{table}");
+
+				return Ok(());
+			}
+
 			println!("Enter password to protect the key");
-			let password = rpassword::prompt_password("Password: ")?;
-			let address = store_mnemonic(&data, &mnemonic, password)?;
-			println!("Stored secret for {address}");
+			let password = inputs::prompt_password_confirm()?;
+
+			println!("(Optional) Enter a name for the vault entry");
+			let name = inputs::prompt_vault_name()?;
+
+			let txn = data.connection.as_ref().unwrap().begin().await?;
+
+			let _derivation = create_derivation_for_vault_data_to_import(
+				&txn,
+				&vault_data_for_import,
+				&password,
+				name.as_ref(),
+			)
+			.await?;
+
+			txn.commit().await?;
+
+			println!("Import done");
+		}
+		Subcommand::Derivation {
+			address_or_vault_name,
+		} => {
+			let root_derivation = retrieve_vault_derivation(&data, address_or_vault_name).await?;
+
+			if root_derivation.path.is_some() {
+				println!("Can only add derivation on a ROOT key.");
+				println!(
+					"The selected key with address:'{}' already has a ROOT with address:'{}'",
+					root_derivation.address, root_derivation.root_address
+				);
+				println!("You can check for available ROOT keys with command 'vault list root'");
+				return Ok(());
+			}
+
+			let vault_account = vault_account::Entity::find_by_id(&root_derivation.address)
+				.one(data.connection.as_ref().unwrap())
+				.await?
+				.ok_or(GcliError::Input(format!(
+					"Could not find (root) vault account for address:'{}'",
+					root_derivation.address
+				)))?;
+
+			println!("Adding derivation to root key: {root_derivation}");
+
+			println!("Enter password to decrypt the root key");
+
+			let root_secret_suri = retrieve_suri_from_vault_account(&vault_account)?;
+
+			let derivation_path = inputs::prompt_vault_derivation_path()?;
+
+			let derivation_secret_suri = format!("{root_secret_suri}{derivation_path}");
+
+			let derivation_keypair =
+				compute_keypair(vault_account.crypto_type, &derivation_secret_suri)?;
+
+			let derivation_address: String = derivation_keypair.address().to_string();
+
+			let check_derivation = vault_derivation::Entity::find_by_id(&derivation_address)
+				.one(data.connection.as_ref().unwrap())
+				.await?;
+
+			if check_derivation.is_some() {
+				println!("Derivation already exists for address:'{derivation_address}'");
+				return Ok(());
+			}
+
+			println!("(Optional) Enter a name for the new derivation");
+			let name = inputs::prompt_vault_name()?;
+
+			let derivation = vault_derivation::ActiveModel {
+				address: Set(derivation_address),
+				name: Set(name),
+				path: Set(Some(derivation_path)),
+				root_address: Set(root_derivation.root_address.clone()),
+			};
+			let derivation = derivation.insert(data.connection.as_ref().unwrap()).await?;
+			println!("Created derivation {}", derivation);
+		}
+		Subcommand::Rename { address } => {
+			let derivation = vault_derivation::Entity::find_by_id(address.to_string())
+				.one(data.connection.as_ref().unwrap())
+				.await?;
+
+			if derivation.is_none() {
+				println!("No vault entry found for address:'{address}'");
+				println!("You might want to import it first with 'vault import'");
+				return Ok(());
+			}
+
+			let derivation = derivation.unwrap();
+
+			println!(
+				"Current name for address:'{address}' is {:?}",
+				derivation.name
+			);
+
+			println!("Enter new vault name for the key (leave empty to remove the name)");
+			let name = inputs::prompt_vault_name()?;
+
+			let old_name = derivation.name.clone();
+			let mut derivation: vault_derivation::ActiveModel = derivation.into();
+			derivation.name = Set(name.clone());
+			let _derivation = derivation.update(data.connection.as_ref().unwrap()).await?;
+			println!(
+				"Renamed address:'{address}' from {:?} to {:?}",
+				old_name, name
+			);
+		}
+		Subcommand::Remove {
+			address_or_vault_name,
+		} => {
+			let derivation = retrieve_vault_derivation(&data, address_or_vault_name).await?;
+			let address_to_delete = derivation.address.clone();
+
+			let txn = data.connection.as_ref().unwrap().begin().await?;
+
+			//If deleting a root derivation; also delete the vault account and all linked derivations
+			if derivation.path.is_none() {
+				let all_derivations_to_delete =
+					vault_derivation::fetch_all_linked_derivations_in_order(
+						&txn,
+						&address_to_delete,
+					)
+					.await?;
+
+				let table = compute_vault_derivations_table(&all_derivations_to_delete).await?;
+
+				println!("All derivations linked to the root derivation:");
+				println!("{table}");
+
+				println!(
+					"This root derivation has {} derivations in total",
+					all_derivations_to_delete.len()
+				);
+				let confirmed = inputs::confirm_action(
+                    "Are you sure you want to delete it along with the vault account (and saved key)?".to_string()
+                )?;
+
+				if !confirmed {
+					return Ok(());
+				}
+
+				for derivation_to_delete in all_derivations_to_delete {
+					let delete_result = derivation_to_delete.delete(&txn).await?;
+					println!("Deleted {} derivation", delete_result.rows_affected);
+				}
+
+				let delete_result = vault_account::Entity::delete_by_id(&address_to_delete)
+					.exec(&txn)
+					.await?;
+				println!("Deleted {} vault account", delete_result.rows_affected);
+			} else {
+				let delete_result = derivation.delete(&txn).await?;
+				println!("Deleted {} derivation", delete_result.rows_affected);
+			}
+
+			txn.commit().await?;
+
+			println!("Done removing address:'{address_to_delete}'");
+		}
+		Subcommand::Migrate => {
+			println!("Migrating existing key files to db");
+
+			let vault_key_addresses = fetch_vault_key_addresses(&data).await?;
+
+			let table = compute_vault_key_files_table(&vault_key_addresses).await?;
+			println!("available key files to possibly migrate:");
+			println!("{table}");
+
+			for address in vault_key_addresses {
+				//Check if we already have a vault_derivation for that address
+				let derivation = vault_derivation::Entity::find_by_id(&address)
+					.one(data.connection.as_ref().unwrap())
+					.await?;
+
+				if derivation.is_some() {
+					//Already migrated
+					continue;
+				}
+
+				println!();
+				println!("Trying to migrate key {address}");
+				let vault_data_from_file = match try_fetch_vault_data_from_file(&data, &address) {
+					Ok(Some(vault_data)) => vault_data,
+					Ok(None) => {
+						println!("No vault entry file found for address {address}");
+						continue;
+					}
+					Err(e) => {
+						println!("Error while fetching vault data for address {address}: {e}");
+						println!("Continuing to next one");
+						continue;
+					}
+				};
+
+				let vault_data_to_import = VaultDataToImport {
+					secret_format: SecretFormat::Substrate,
+					secret: keys::Secret::SimpleSecret(vault_data_from_file.secret),
+					address: AccountId::from_str(&address).expect("invalid address"),
+					key_pair: vault_data_from_file.key_pair,
+				};
+
+				let txn = data.connection.as_ref().unwrap().begin().await?;
+
+				let derivation = create_derivation_for_vault_data_to_import(
+					&txn,
+					&vault_data_to_import,
+					&vault_data_from_file.password,
+					None,
+				)
+				.await?;
+
+				txn.commit().await?;
+				println!("Import done: {}", derivation);
+			}
+
+			println!("Migration done");
+		}
+		Subcommand::Where => {
+			println!("{}", data.project_dir.data_dir().to_str().unwrap());
 		}
 	};
 
 	Ok(())
 }
 
-/// store mnemonic protected with password
-pub fn store_mnemonic(
+fn parse_prefix_and_derivation_path_from_string(
+	raw_string: String,
+) -> Result<(String, Option<String>), GcliError> {
+	if raw_string.contains("/") {
+		raw_string
+			.find("/")
+			.map_or(Err(GcliError::Input("Invalid format".to_string())), |idx| {
+				let (prefix, derivation_path) = raw_string.split_at(idx);
+				Ok((prefix.to_string(), Some(derivation_path.to_string())))
+			})
+	} else {
+		Ok((raw_string, None))
+	}
+}
+
+fn map_secret_format_to_crypto_type(secret_format: SecretFormat) -> CryptoType {
+	match secret_format {
+		SecretFormat::Seed => vault_account::CryptoType::Sr25519Seed,
+		SecretFormat::Substrate => vault_account::CryptoType::Sr25519Mnemonic,
+		SecretFormat::Predefined => vault_account::CryptoType::Sr25519Mnemonic,
+		SecretFormat::Cesium => vault_account::CryptoType::Ed25519Seed,
+	}
+}
+
+/// This method will scan files in the data directory and return the addresses of the vault keys found
+async fn fetch_vault_key_addresses(data: &Data) -> Result<Vec<String>, GcliError> {
+	let mut entries = std::fs::read_dir(data.project_dir.data_dir())?
+		.map(|res| res.map(|e| e.path()))
+		.collect::<Result<Vec<_>, std::io::Error>>()?;
+
+	// To have consistent ordering
+	entries.sort();
+
+	let mut vault_key_addresses: Vec<String> = vec![];
+	entries.iter().for_each(|dir_path| {
+		let filename = dir_path.file_name().unwrap().to_str().unwrap();
+		// To only keep the address part of the filename for names like "<ss58 address>-<secret_format>"
+		let potential_address = filename.split("-").next().unwrap();
+		// If potential_address is a valid AccountId
+		if AccountId::from_str(potential_address).is_ok() {
+			vault_key_addresses.push(potential_address.to_string());
+		}
+	});
+
+	Ok(vault_key_addresses)
+}
+
+async fn compute_vault_key_files_table(vault_key_addresses: &[String]) -> Result<Table, GcliError> {
+	let mut table = Table::new();
+	table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
+	table.set_header(vec!["Key file"]);
+
+	vault_key_addresses.iter().for_each(|address| {
+		table.add_row(vec![Cell::new(address)]);
+	});
+
+	Ok(table)
+}
+
+async fn compute_vault_derivations_table(
+	derivations_ordered: &[vault_derivation::Model],
+) -> Result<Table, GcliError> {
+	let mut table = Table::new();
+	table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
+	table.set_header(vec!["Address", "Path", "Vault Name"]);
+	let empty_name = "".to_string();
+	let root_path = "<Root>".to_string();
+
+	let mut current_root_address = "".to_string();
+	let mut current_root_name: Option<String> = None;
+
+	for derivation in derivations_ordered {
+		if derivation.root_address != current_root_address {
+			// First entry should be a root derivation
+			if derivation.path.is_some() {
+				return Err(GcliError::Input(
+					"Order of derivations parameter is wrong".to_string(),
+				));
+			}
+			current_root_address = derivation.root_address.clone();
+			current_root_name = derivation.name.clone();
+		} else {
+			// Validate that the RootAddress is the same as current_root_address
+			if derivation.root_address != current_root_address {
+				return Err(GcliError::Input(
+					"Order of derivations parameter is wrong".to_string(),
+				));
+			}
+		}
+
+		let address = if derivation.path.is_none() {
+			derivation.address.clone()
+		} else {
+			"  ".to_string() + &derivation.address
+		};
+
+		let path = if derivation.path.is_none() {
+			root_path.clone()
+		} else {
+			derivation.path.clone().unwrap()
+		};
+
+		let name = if derivation.name.is_none() {
+			if derivation.path.is_none() {
+				empty_name.clone()
+			} else if let Some(current_root_name) = &current_root_name {
+				format!(
+					"<{}{}>",
+					current_root_name,
+					derivation.path.clone().unwrap()
+				)
+			} else {
+				empty_name.clone()
+			}
+		} else {
+			derivation.name.clone().unwrap()
+		};
+
+		table.add_row(vec![
+			Cell::new(&address),
+			Cell::new(&path),
+			Cell::new(&name),
+		]);
+	}
+
+	Ok(table)
+}
+
+pub async fn retrieve_address_string<T: AddressOrVaultName>(
 	data: &Data,
-	mnemonic: &str,
-	password: String,
-) -> Result<AccountId, GcliError> {
-	// check validity by deriving keypair
-	let keypair = pair_from_str(mnemonic)?;
-	let address = keypair.public();
-	// write encrypted mnemonic in file identified by pubkey
-	let path = data.project_dir.data_dir().join(address.to_string());
-	let mut file = std::fs::File::create(path)?;
-	file.write_all(&encrypt(mnemonic.as_bytes(), password).map_err(|e| anyhow!(e))?[..])?;
-	Ok(keypair.public().into())
-}
-
-/// try get secret in keystore
-pub fn try_fetch_secret(data: &Data, address: AccountId) -> Result<Option<String>, GcliError> {
-	let path = data.project_dir.data_dir().join(address.to_string());
+	address_or_vault_name: T,
+) -> Result<String, GcliError> {
+	if let Some(address) = address_or_vault_name.address() {
+		return Ok(address.to_string());
+	}
+
+	let derivation = retrieve_vault_derivation(data, address_or_vault_name).await?;
+
+	Ok(derivation.address)
+}
+
+pub async fn retrieve_vault_derivation<T: AddressOrVaultName>(
+	data: &Data,
+	address_or_vault_name: T,
+) -> Result<vault_derivation::Model, GcliError> {
+	let derivation = if let Some(name) = address_or_vault_name.name() {
+		let (name, derivation_path_opt) =
+			parse_prefix_and_derivation_path_from_string(name.to_string())?;
+
+		let derivation = vault_derivation::Entity::find()
+			.filter(vault_derivation::Column::Name.eq(Some(name.clone())))
+			.one(data.connection.as_ref().unwrap())
+			.await?;
+
+		let derivation = derivation.ok_or(GcliError::Input(format!(
+			"No vault derivation found with name:'{name}'"
+		)))?;
+
+		match derivation_path_opt {
+			None => derivation,
+			Some(path) => {
+				let sub_derivation = vault_derivation::Entity::find()
+					.filter(
+						vault_derivation::Column::RootAddress.eq(derivation.root_address.clone()),
+					)
+					.filter(vault_derivation::Column::Path.eq(Some(path.clone())))
+					.one(data.connection.as_ref().unwrap())
+					.await?;
+
+				sub_derivation.ok_or(GcliError::Input(format!(
+					"No vault derivation found with root name:'{name}' and path:'{path}'"
+				)))?
+			}
+		}
+	} else if let Some(address) = address_or_vault_name.address() {
+		let derivation = vault_derivation::Entity::find_by_id(address.to_string())
+			.one(data.connection.as_ref().unwrap())
+			.await?;
+
+		derivation.ok_or(GcliError::Input(format!(
+			"No vault derivation found with Address:'{address}'"
+		)))?
+	} else {
+		//Should never happen since clap enforces exactly one of the 2 options
+		return Err(GcliError::Input("No address or name provided".to_string()));
+	};
+	Ok(derivation)
+}
+
+fn create_vault_data_to_import<F, P>(
+	secret_format: SecretFormat,
+	prompt_fn: F,
+) -> Result<VaultDataToImport, GcliError>
+where
+	F: Fn() -> (keys::Secret, P),
+	P: Into<KeyPair>,
+{
+	let (secret, pair) = prompt_fn();
+	let key_pair = pair.into();
+	Ok(VaultDataToImport {
+		secret_format,
+		secret,
+		address: key_pair.address(),
+		key_pair,
+	})
+}
+
+fn prompt_secret_and_compute_vault_data_to_import(
+	secret_format: SecretFormat,
+) -> Result<VaultDataToImport, GcliError> {
+	match secret_format {
+		SecretFormat::Substrate => {
+			create_vault_data_to_import(secret_format, prompt_secret_substrate_and_compute_keypair)
+		}
+		SecretFormat::Seed => {
+			create_vault_data_to_import(secret_format, prompt_seed_and_compute_keypair)
+		}
+		SecretFormat::Cesium => {
+			create_vault_data_to_import(secret_format, prompt_secret_cesium_and_compute_keypair)
+		}
+		SecretFormat::Predefined => {
+			create_vault_data_to_import(secret_format, prompt_predefined_and_compute_keypair)
+		}
+	}
+}
+
+/// Creates derivation and if necessary root vault account and root derivation
+///
+/// Does it all using "db" parameter that should better be a transaction since multiple operations can be done
+pub async fn create_derivation_for_vault_data_to_import<C>(
+	db: &C,
+	vault_data: &VaultDataToImport,
+	password: &str,
+	name: Option<&String>,
+) -> Result<vault_derivation::Model, GcliError>
+where
+	C: ConnectionTrait,
+{
+	//To be safe
+	if vault_derivation::Entity::find_by_id(vault_data.address.to_string())
+		.one(db)
+		.await?
+		.is_some()
+	{
+		return Err(GcliError::Input(format!(
+			"Vault entry already exists for address {}",
+			vault_data.address
+		)));
+	}
+
+	let secret_suri: String = match &vault_data.secret_format {
+		SecretFormat::Cesium => {
+			if let KeyPair::Nacl(keypair) = &vault_data.key_pair {
+				// In case of cesium key, we will store the seed suri instead of id/password (so it supports derivations)
+				let seed: [u8; 32] = keypair.skey[0..32]
+					.try_into()
+					.expect("slice with incorrect length");
+				format!("0x{}", hex::encode(seed))
+			} else {
+				return Err(GcliError::Input("Expected KeyPair::Nacl".to_string()));
+			}
+		}
+		SecretFormat::Seed => {
+			if let keys::Secret::SimpleSecret(seed_str) = &vault_data.secret {
+				format!("0x{seed_str}")
+			} else {
+				return Err(GcliError::Input("Expected SimpleSecret".to_string()));
+			}
+		}
+		SecretFormat::Substrate | SecretFormat::Predefined => {
+			if let keys::Secret::SimpleSecret(secret_suri) = &vault_data.secret {
+				secret_suri.clone()
+			} else {
+				return Err(GcliError::Input("Expected SimpleSecret".to_string()));
+			}
+		}
+	};
+
+	let secret_format = vault_data.secret_format;
+
+	let (root_secret_suri, derivation_path_opt, root_address, derivation_address) =
+		compute_root_and_derivation_data(&secret_format, secret_suri)?;
+
+	// Making sure the computed address is the same as the address to import
+	let address_to_import = vault_data.address.to_string();
+	if let Some(derivation_address) = &derivation_address {
+		if *derivation_address != address_to_import {
+			return Err(GcliError::Input(format!(
+				"Derivation address {} does not match the expected address {}",
+				derivation_address, address_to_import
+			)));
+		}
+	} else if root_address != address_to_import {
+		return Err(GcliError::Input(format!(
+			"Derivation address {} does not match the expected address {}",
+			root_address, address_to_import
+		)));
+	}
+
+	let encrypted_private_key =
+		encrypt(root_secret_suri.as_bytes(), password.to_string()).map_err(|e| anyhow!(e))?;
+
+	let _root_account = vault_account::create_vault_account(
+		db,
+		&root_address,
+		map_secret_format_to_crypto_type(secret_format),
+		encrypted_private_key,
+	)
+	.await?;
+
+	let derivation = if let Some(derivation_path) = derivation_path_opt {
+		// Root derivation
+		let _root_derivation =
+			vault_derivation::create_root_vault_derivation(db, &root_address, None).await?;
+
+		// Compute derivation !
+		let derivation = vault_derivation::ActiveModel {
+			address: Set(derivation_address.unwrap().clone()),
+			name: Set(name.cloned()),
+			path: Set(Some(derivation_path)),
+			root_address: Set(root_address.clone()),
+		};
+		let derivation = derivation.insert(db).await?;
+		println!("Created derivation {}", derivation);
+		derivation
+	} else {
+		let derivation =
+			vault_derivation::create_root_vault_derivation(db, &root_address, name).await?;
+		println!("Created derivation {}", derivation);
+		derivation
+	};
+
+	Ok(derivation)
+}
+
+fn compute_root_and_derivation_data(
+	secret_format: &SecretFormat,
+	secret_suri: String,
+) -> Result<(String, Option<String>, String, Option<String>), GcliError> {
+	let (root_secret_suri, derivation_path_opt) =
+		parse_prefix_and_derivation_path_from_string(secret_suri)?;
+
+	let (root_address, derivation_address_opt) = match &secret_format {
+		SecretFormat::Cesium => match &derivation_path_opt {
+			None => {
+				let root_suri = &root_secret_suri;
+				let root_pair = pair_from_ed25519_str(root_suri)?;
+				let root_address: AccountId = root_pair.public().into();
+
+				(root_address.to_string(), None)
+			}
+			Some(derivation_path) => {
+				let root_suri = &root_secret_suri;
+				let root_pair = pair_from_ed25519_str(root_suri)?;
+				let root_address: AccountId = root_pair.public().into();
+
+				let derivation_suri = root_suri.clone() + derivation_path;
+				let derivation_pair = pair_from_ed25519_str(&derivation_suri)?;
+				let derivation_address: AccountId = derivation_pair.public().into();
+
+				(
+					root_address.to_string(),
+					Some(derivation_address.to_string()),
+				)
+			}
+		},
+		SecretFormat::Substrate | SecretFormat::Seed | SecretFormat::Predefined => {
+			match &derivation_path_opt {
+				None => {
+					let root_suri = &root_secret_suri;
+					let root_pair = pair_from_sr25519_str(root_suri)?;
+					let root_address: AccountId = root_pair.public().into();
+
+					(root_address.to_string(), None)
+				}
+				Some(derivation_path) => {
+					let root_suri = &root_secret_suri;
+					let root_pair = pair_from_sr25519_str(root_suri)?;
+					let root_address: AccountId = root_pair.public().into();
+
+					let derivation_suri = root_suri.clone() + derivation_path;
+					let derivation_pair = pair_from_sr25519_str(&derivation_suri)?;
+					let derivation_address: AccountId = derivation_pair.public().into();
+
+					(
+						root_address.to_string(),
+						Some(derivation_address.to_string()),
+					)
+				}
+			}
+		}
+	};
+	Ok((
+		root_secret_suri,
+		derivation_path_opt,
+		root_address,
+		derivation_address_opt,
+	))
+}
+
+fn get_vault_key_path(data: &Data, vault_filename: &str) -> PathBuf {
+	data.project_dir.data_dir().join(vault_filename)
+}
+
+/// look for different possible paths for vault keys and return both format and path
+fn find_substrate_vault_key_file(data: &Data, address: &str) -> Result<Option<PathBuf>, GcliError> {
+	let path = get_vault_key_path(data, address);
 	if path.exists() {
+		return Ok(Some(path));
+	}
+
+	Ok(None)
+}
+
+/// try to get secret in keystore, prompt for the password and compute the keypair
+pub async fn try_fetch_key_pair(
+	data: &Data,
+	address: AccountId,
+) -> Result<Option<KeyPair>, GcliError> {
+	if let Some(derivation) = vault_derivation::Entity::find_by_id(address.to_string())
+		.one(data.connection.as_ref().unwrap())
+		.await?
+	{
+		if let Some(vault_account) =
+			vault_account::Entity::find_by_id(derivation.root_address.clone())
+				.one(data.connection.as_ref().unwrap())
+				.await?
+		{
+			let root_secret_suri = retrieve_suri_from_vault_account(&vault_account)?;
+
+			let secret_suri = if let Some(derivation_path) = derivation.path {
+				format!("{root_secret_suri}{derivation_path}")
+			} else {
+				root_secret_suri
+			};
+
+			let key_pair = compute_keypair(vault_account.crypto_type, &secret_suri)?;
+
+			//To be safe
+			if address != key_pair.address() {
+				return Err(GcliError::Input(format!(
+					"Computed address {} does not match the expected address {}",
+					key_pair.address(),
+					address
+				)));
+			}
+
+			Ok(Some(key_pair))
+		} else {
+			Ok(None)
+		}
+	} else {
+		Ok(None)
+	}
+}
+
+pub fn retrieve_suri_from_vault_account(
+	vault_account: &vault_account::Model,
+) -> Result<String, GcliError> {
+	let password = inputs::prompt_password()?;
+
+	let cypher = &vault_account.encrypted_private_key;
+	let secret_vec =
+		decrypt(cypher, password.clone()).map_err(|e| GcliError::Input(e.to_string()))?;
+	let secret_suri = String::from_utf8(secret_vec).map_err(|e| anyhow!(e))?;
+
+	Ok(secret_suri)
+}
+
+pub fn compute_keypair(crypto_type: CryptoType, secret_suri: &str) -> Result<KeyPair, GcliError> {
+	let key_pair = match crypto_type {
+		CryptoType::Sr25519Mnemonic | CryptoType::Sr25519Seed => {
+			pair_from_sr25519_str(secret_suri)?.into()
+		}
+		CryptoType::Ed25519Seed => pair_from_ed25519_str(secret_suri)?.into(),
+	};
+	Ok(key_pair)
+}
+
+pub struct VaultDataFromFile {
+	address: String,
+	secret_format: SecretFormat,
+	secret: String,
+	path: PathBuf,
+	password: String,
+	key_pair: KeyPair,
+}
+
+/// try to get secret in keystore, prompt for the password and compute the keypair
+pub fn try_fetch_vault_data_from_file(
+	data: &Data,
+	address: &str,
+) -> Result<Option<VaultDataFromFile>, GcliError> {
+	if let Some(path) = find_substrate_vault_key_file(data, address)? {
 		println!("Enter password to unlock account {address}");
 		let password = rpassword::prompt_password("Password: ")?;
-		let mut file = std::fs::OpenOptions::new().read(true).open(path)?;
+		let mut file = std::fs::OpenOptions::new().read(true).open(path.clone())?;
 		let mut cypher = vec![];
 		file.read_to_end(&mut cypher)?;
-		let secret = decrypt(&cypher, password).map_err(|e| GcliError::Input(e.to_string()))?;
-		let secretstr = String::from_utf8(secret).map_err(|e| anyhow!(e))?;
-		Ok(Some(secretstr))
+		let secret_vec =
+			decrypt(&cypher, password.clone()).map_err(|e| GcliError::Input(e.to_string()))?;
+		let secret = String::from_utf8(secret_vec).map_err(|e| anyhow!(e))?;
+
+		let key_pair = pair_from_sr25519_str(&secret)?.into();
+
+		Ok(Some(VaultDataFromFile {
+			address: address.to_string(),
+			secret_format: SecretFormat::Substrate,
+			secret,
+			path,
+			password,
+			key_pair,
+		}))
 	} else {
 		Ok(None)
 	}
 }
 
-// test that armored encryption/decryption work as intended
-#[test]
-fn test_encrypt_decrypt() {
-	let plaintext = b"Hello world!";
-	let passphrase = "this is not a good passphrase".to_string();
-	let encrypted = encrypt(plaintext, passphrase.clone()).unwrap();
-	let decrypted = decrypt(&encrypted, passphrase).unwrap();
-	assert_eq!(decrypted, plaintext);
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use rstest::rstest;
+
+	/// test that armored encryption/decryption work as intended
+	#[test]
+	fn test_encrypt_decrypt() {
+		let plaintext = b"Hello world!";
+		let passphrase = "this is not a good passphrase".to_string();
+		let encrypted = encrypt(plaintext, passphrase.clone()).unwrap();
+		let decrypted = decrypt(&encrypted, passphrase).unwrap();
+		assert_eq!(decrypted, plaintext);
+	}
+
+	#[rstest]
+	#[case(
+		String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk//0"),
+		String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk"),
+		Some(String::from("//0"))
+	)]
+	#[case(
+		String::from("0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e//0"),
+		String::from("0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"),
+		Some(String::from("//0"))
+	)]
+	#[case(
+		String::from(
+			"bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice"
+		),
+		String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk"),
+		Some(String::from("//Alice"))
+	)]
+	#[case(
+		String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk"),
+		String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk"),
+		None
+	)]
+	#[case(
+		String::from("0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"),
+		String::from("0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"),
+		None
+	)]
+	#[case(
+		String::from("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"),
+		String::from("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e"),
+		None
+	)]
+	#[case(
+		String::from("someVaultName//Alice"),
+		String::from("someVaultName"),
+		Some(String::from("//Alice"))
+	)]
+	#[case(String::from("someVaultName"), String::from("someVaultName"), None)]
+	fn test_parse_prefix_and_derivation_path_from_string(
+		#[case] raw_string: String,
+		#[case] expected_prefix: String,
+		#[case] expected_derivation_path: Option<String>,
+	) {
+		let (root_secret, derivation_path) =
+			parse_prefix_and_derivation_path_from_string(raw_string).unwrap();
+		assert_eq!(expected_prefix, root_secret);
+		assert_eq!(expected_derivation_path, derivation_path);
+	}
 }
diff --git a/src/conf.rs b/src/conf.rs
index 31a13ef..a6f6ff7 100644
--- a/src/conf.rs
+++ b/src/conf.rs
@@ -1,3 +1,4 @@
+use crate::entities::vault_derivation;
 use crate::*;
 use serde::{Deserialize, Serialize};
 
@@ -72,7 +73,7 @@ pub enum Subcommand {
 }
 
 /// handle conf command
-pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
+pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
 	// match subcommand
 	match command {
 		Subcommand::Where => {
@@ -83,10 +84,19 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError>
 		}
 		Subcommand::Show => {
 			println!("{}", data.cfg);
+			if let Some(account_id) = data.cfg.address {
+				if let Some(derivation) = vault_derivation::fetch_vault_derivation(
+					data.connection.as_ref().unwrap(),
+					account_id.to_string().as_str(),
+				)
+				.await?
+				{
+					println!("(Vault entry: {})", derivation);
+				}
+			}
 		}
 		Subcommand::Save => {
-			confy::store(APP_NAME, None, &data.cfg).expect("unable to write config");
-			println!("Configuration updated!");
+			save_config(&data);
 		}
 		Subcommand::Default => {
 			confy::store(APP_NAME, None, Config::default()).expect("unable to write config");
@@ -95,3 +105,8 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError>
 
 	Ok(())
 }
+
+pub fn save_config(data: &Data) {
+	confy::store(APP_NAME, None, &data.cfg).expect("unable to write config");
+	println!("Configuration updated!");
+}
diff --git a/src/data.rs b/src/data.rs
index 5dbc79d..5061c5d 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -1,11 +1,13 @@
-use std::str::FromStr;
-
+use crate::commands::vault;
 use crate::*;
 use indexer::Indexer;
+use sea_orm::DatabaseConnection;
+use std::str::FromStr;
 
 // consts
 pub const LOCAL_DUNITER_ENDPOINT: &str = "ws://localhost:9944";
 pub const LOCAL_INDEXER_ENDPOINT: &str = "http://localhost:4350/graphql";
+pub const SQLITE_DB_FILENAME: &str = "gcli.sqlite";
 const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
 
 #[cfg(feature = "gdev")]
@@ -28,27 +30,29 @@ pub const GDEV_INDEXER_ENDPOINTS: [&str; 2] = [
 /// Data of current command
 /// can also include fetched information
 pub struct Data {
-	// command line arguments
+	/// command line arguments
 	pub args: Args,
-	// config
+	/// config
 	pub cfg: conf::Config,
-	// rpc to substrate client
+	/// database connection
+	pub connection: Option<DatabaseConnection>,
+	/// rpc to substrate client
 	pub client: Option<Client>,
-	// graphql to duniter-indexer
+	/// graphql to duniter-indexer
 	pub indexer: Option<Indexer>,
-	// user keypair
+	/// user keypair
 	pub keypair: Option<KeyPair>,
-	// user identity index
+	/// user identity index
 	pub idty_index: Option<IdtyId>,
-	// token decimals
+	/// token decimals
 	pub token_decimals: u32,
-	// token symbol
+	/// token symbol
 	pub token_symbol: String,
-	// genesis hash
+	/// genesis hash
 	pub genesis_hash: Hash,
-	// indexer genesis hash
+	/// indexer genesis hash
 	pub indexer_genesis_hash: Hash,
-	// gcli base path
+	/// gcli base path
 	pub project_dir: directories::ProjectDirs,
 }
 
@@ -70,6 +74,7 @@ impl Default for Data {
 			project_dir,
 			args: Default::default(),
 			cfg: Default::default(),
+			connection: Default::default(),
 			client: Default::default(),
 			indexer: Default::default(),
 			keypair: Default::default(),
@@ -85,15 +90,18 @@ impl Default for Data {
 // implement helper functions for Data
 impl Data {
 	/// --- constructor ---
-	pub fn new(args: Args) -> Result<Self, GcliError> {
-		Self {
+	pub async fn new(args: Args) -> Result<Self, GcliError> {
+		let mut data = Self {
 			args,
 			cfg: conf::load_conf(),
 			token_decimals: 0,
 			token_symbol: "tokens".into(),
 			..Default::default()
-		}
-		.overwrite_from_args()
+		};
+		//Necessary to support checking "vault names" in the base arguments
+		data = data.build_connection().await?;
+		data = data.overwrite_from_args().await?;
+		Ok(data)
 	}
 	// --- getters ---
 	// the "unwrap" should not fail if data is well prepared
@@ -106,11 +114,11 @@ impl Data {
 	pub fn address(&self) -> AccountId {
 		self.cfg.address.clone().expect("an address is needed")
 	}
-	pub fn keypair(&self) -> KeyPair {
+	pub async fn keypair(&self) -> KeyPair {
 		match self.keypair.clone() {
 			Some(keypair) => keypair,
 			None => loop {
-				match fetch_or_get_keypair(self, self.cfg.address.clone()) {
+				match fetch_or_get_keypair(self, self.cfg.address.clone()).await {
 					Ok(pair) => return pair,
 					Err(e) => println!("{e:?} → retry"),
 				}
@@ -136,7 +144,7 @@ impl Data {
 	}
 	// --- mutators ---
 	/// use arguments to overwrite config
-	pub fn overwrite_from_args(mut self) -> Result<Self, GcliError> {
+	pub async fn overwrite_from_args(mut self) -> Result<Self, GcliError> {
 		// network
 		if let Some(network) = self.args.network.clone() {
 			// a network was provided as arugment
@@ -184,8 +192,10 @@ impl Data {
 			self.cfg.address = Some(keypair.address());
 			self.keypair = Some(keypair);
 		}
-		// address
-		if let Some(address) = self.args.address.clone() {
+		// address or vault name
+		if let Some(address_or_vault_name) = self.args.address_or_vault_name.clone() {
+			let address = vault::retrieve_address_string(&self, address_or_vault_name).await?;
+
 			self.cfg.address = Some(AccountId::from_str(&address).expect("invalid address"));
 			// if giving address, cancel secret
 			self.keypair = None
@@ -224,7 +234,16 @@ impl Data {
 		};
 		Ok(self)
 	}
-	/// get issuer index
+
+	/// build a database connection
+	pub async fn build_connection(mut self) -> Result<Self, GcliError> {
+		let data_dir = self.project_dir.data_dir();
+		let connection = database::build_sqlite_connection(data_dir, SQLITE_DB_FILENAME).await?;
+		self.connection = Some(connection);
+		Ok(self)
+	}
+
+	/// get issuer index<br>
 	/// needs address and client first
 	pub async fn fetch_idty_index(mut self) -> Result<Self, GcliError> {
 		self.idty_index = Some(
diff --git a/src/database.rs b/src/database.rs
new file mode 100644
index 0000000..fad4f71
--- /dev/null
+++ b/src/database.rs
@@ -0,0 +1,71 @@
+use crate::entities::{vault_account, vault_derivation};
+use crate::utils::GcliError;
+use sea_orm::sea_query::IndexCreateStatement;
+use sea_orm::{ConnectionTrait, Database, DatabaseConnection, Schema};
+use std::fs;
+use std::path::Path;
+
+pub async fn build_sqlite_connection(
+	data_dir: &Path,
+	filename: &str,
+) -> Result<DatabaseConnection, GcliError> {
+	let sqlite_path = data_dir.join(filename);
+
+	// Check if the file exists, and create it if it doesn't (otherwise the connection will fail)
+	if !Path::new(&sqlite_path).exists() {
+		fs::File::create(sqlite_path.clone())?;
+	}
+
+	let sqlite_path_str = sqlite_path
+		.into_os_string()
+		.into_string()
+		.map_err(|_| GcliError::Input("Invalid SQLite path".to_string()))?;
+
+	let sqlite_db_url = format!("sqlite://{}", sqlite_path_str);
+
+	let connection = initialize_db(&sqlite_db_url).await?;
+	Ok(connection)
+}
+
+pub async fn initialize_db(db_url: &str) -> Result<DatabaseConnection, GcliError> {
+	let db = Database::connect(db_url).await?;
+	let schema = Schema::new(db.get_database_backend());
+
+	create_table_if_not_exists(&db, &schema, vault_account::Entity).await?;
+	create_table_if_not_exists(&db, &schema, vault_derivation::Entity).await?;
+
+	Ok(db)
+}
+
+async fn create_table_if_not_exists<E: sea_orm::EntityTrait>(
+	db: &DatabaseConnection,
+	schema: &Schema,
+	entity: E,
+) -> Result<(), GcliError> {
+	db.execute(
+		db.get_database_backend()
+			.build(schema.create_table_from_entity(entity).if_not_exists()),
+	)
+	.await?;
+	Ok(())
+}
+
+/// The only way to add composed unique index...
+#[allow(dead_code)]
+async fn create_table_if_not_exists_with_index<E: sea_orm::EntityTrait>(
+	db: &DatabaseConnection,
+	schema: &Schema,
+	entity: E,
+	index: &mut IndexCreateStatement,
+) -> Result<(), GcliError> {
+	db.execute(
+		db.get_database_backend().build(
+			schema
+				.create_table_from_entity(entity)
+				.index(index)
+				.if_not_exists(),
+		),
+	)
+	.await?;
+	Ok(())
+}
diff --git a/src/entities.rs b/src/entities.rs
new file mode 100644
index 0000000..2de801c
--- /dev/null
+++ b/src/entities.rs
@@ -0,0 +1,2 @@
+pub mod vault_account;
+pub mod vault_derivation;
diff --git a/src/entities/vault_account.rs b/src/entities/vault_account.rs
new file mode 100644
index 0000000..649d0b8
--- /dev/null
+++ b/src/entities/vault_account.rs
@@ -0,0 +1,106 @@
+use crate::inputs;
+use crate::utils::GcliError;
+use sea_orm::prelude::StringLen;
+use sea_orm::ActiveValue::Set;
+use sea_orm::{
+	ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, EnumIter, Related, RelationDef,
+	RelationTrait,
+};
+use sea_orm::{ActiveModelTrait, ConnectionTrait, PrimaryKeyTrait};
+use sea_orm::{DeriveActiveEnum, EntityTrait};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "vault_account")]
+pub struct Model {
+	/// SS58 Address of (root) account
+	#[sea_orm(primary_key, auto_increment = false)]
+	pub address: String,
+	pub crypto_type: CryptoType,
+	pub encrypted_private_key: Vec<u8>,
+}
+
+impl Display for Model {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		write!(f, "[address:\"{}\"]", self.address)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[sea_orm(
+	rs_type = "String",
+	db_type = "String(StringLen::None)",
+	rename_all = "PascalCase"
+)]
+pub enum CryptoType {
+	/// The secret key or BIP39 mnemonic
+	Sr25519Mnemonic,
+	/// The 32B SR25519 seed without "0x" prefix
+	Sr25519Seed,
+	/// The 32B ED25519 seed without "0x" prefix (for cesium)
+	Ed25519Seed,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter)]
+pub enum Relation {
+	Derivation,
+}
+
+impl RelationTrait for Relation {
+	fn def(&self) -> RelationDef {
+		match self {
+			Self::Derivation => Entity::has_many(super::vault_derivation::Entity).into(),
+		}
+	}
+}
+
+// `Related` trait has to be implemented by hand
+impl Related<super::vault_derivation::Entity> for Entity {
+	fn to() -> RelationDef {
+		Relation::Derivation.def()
+	}
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+
+/// Creates a (root) vault account (if it doesn't exist) and returns it
+pub async fn create_vault_account<C>(
+	db: &C,
+	address: &str,
+	crypto_type: CryptoType,
+	encrypted_private_key: Vec<u8>,
+) -> Result<Model, GcliError>
+where
+	C: ConnectionTrait,
+{
+	let vault_account = Entity::find_by_id(address.to_owned()).one(db).await?;
+
+	Ok(match vault_account {
+		Some(vault_account) => {
+			println!("Already existing (root) vault account {vault_account}");
+
+			let overwrite_key =
+				inputs::confirm_action("Do you want to overwrite with the new encrypted key ?")?;
+			if overwrite_key {
+				let mut vault_account: ActiveModel = vault_account.into();
+				vault_account.encrypted_private_key = Set(encrypted_private_key);
+				let vault_account = vault_account.update(db).await?;
+				println!("Updated (root) vault account {vault_account}");
+
+				vault_account
+			} else {
+				vault_account
+			}
+		}
+		None => {
+			let vault_account = ActiveModel {
+				address: Set(address.to_owned()),
+				crypto_type: Set(crypto_type),
+				encrypted_private_key: Set(encrypted_private_key),
+			};
+			let vault_account = vault_account.insert(db).await?;
+			println!("Created vault account {}", vault_account);
+			vault_account
+		}
+	})
+}
diff --git a/src/entities/vault_derivation.rs b/src/entities/vault_derivation.rs
new file mode 100644
index 0000000..16beab6
--- /dev/null
+++ b/src/entities/vault_derivation.rs
@@ -0,0 +1,150 @@
+use crate::utils::GcliError;
+use sea_orm::sea_query::NullOrdering;
+use sea_orm::ActiveValue::Set;
+use sea_orm::PrimaryKeyTrait;
+use sea_orm::QueryFilter;
+use sea_orm::{
+	ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, EnumIter, Related, RelationDef,
+	RelationTrait,
+};
+use sea_orm::{ActiveModelTrait, ColumnTrait};
+use sea_orm::{ConnectionTrait, EntityTrait, Order, QueryOrder};
+use std::fmt::Display;
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "vault_derivation")]
+pub struct Model {
+	/// SS58 Address
+	#[sea_orm(primary_key, auto_increment = false)]
+	pub address: String,
+	/// Optional name for the derivation
+	#[sea_orm(unique)]
+	pub name: Option<String>,
+	/// derivation path - None if for the root account
+	pub path: Option<String>,
+	/// ForeignKey to root vault_account SS58 Address
+	pub root_address: String,
+}
+
+impl Display for Model {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		write!(
+			f,
+			"[address:\"{}\", name:{:?}, path:{:?}, root_address:\"{}\"]",
+			self.address, self.name, self.path, self.root_address
+		)
+	}
+}
+
+#[derive(Copy, Clone, Debug, EnumIter)]
+pub enum Relation {
+	RootAccount,
+}
+
+impl RelationTrait for Relation {
+	fn def(&self) -> RelationDef {
+		match self {
+			Self::RootAccount => Entity::belongs_to(super::vault_account::Entity)
+				.from(Column::RootAddress)
+				.to(super::vault_account::Column::Address)
+				.into(),
+		}
+	}
+}
+
+// `Related` trait has to be implemented by hand
+impl Related<super::vault_account::Entity> for Entity {
+	fn to() -> RelationDef {
+		Relation::RootAccount.def()
+	}
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+
+/// Creates a root derivation (if it doesn't exist) and returns it
+pub async fn create_root_vault_derivation<C>(
+	db: &C,
+	root_address: &str,
+	root_name: Option<&String>,
+) -> Result<Model, GcliError>
+where
+	C: ConnectionTrait,
+{
+	create_vault_derivation(db, root_address, root_address, root_name, None).await
+}
+
+/// Creates a derivation (if it doesn't exist) and returns it
+pub async fn create_vault_derivation<C>(
+	db: &C,
+	address: &str,
+	root_address: &str,
+	name: Option<&String>,
+	path: Option<&String>,
+) -> Result<Model, GcliError>
+where
+	C: ConnectionTrait,
+{
+	let derivation = Entity::find_by_id(root_address.to_owned()).one(db).await?;
+
+	Ok(match derivation {
+		Some(derivation) => derivation,
+		None => {
+			let derivation = ActiveModel {
+				address: Set(address.to_owned()),
+				name: Set(name.cloned()),
+				path: Set(path.cloned()),
+				root_address: Set(root_address.to_owned()),
+			};
+			derivation.insert(db).await?
+		}
+	})
+}
+
+pub async fn fetch_vault_derivation<C>(db: &C, address: &str) -> Result<Option<Model>, GcliError>
+where
+	C: ConnectionTrait,
+{
+	let derivation = Entity::find_by_id(address.to_owned()).one(db).await?;
+	Ok(derivation)
+}
+
+pub async fn fetch_all_linked_derivations_in_order<C>(
+	db: &C,
+	root_address: &str,
+) -> Result<Vec<Model>, GcliError>
+where
+	C: ConnectionTrait,
+{
+	let linked_derivations = Entity::find()
+		.filter(Column::RootAddress.eq(root_address.to_owned()))
+		.order_by_with_nulls(Column::Path, Order::Asc, NullOrdering::First)
+		.all(db)
+		.await?;
+	Ok(linked_derivations)
+}
+
+pub async fn list_all_derivations_in_order<C>(db: &C) -> Result<Vec<Model>, GcliError>
+where
+	C: ConnectionTrait,
+{
+	let derivations = Entity::find()
+		.order_by_asc(Column::RootAddress)
+		.order_by_with_nulls(Column::Path, Order::Asc, NullOrdering::First)
+		.all(db)
+		.await?;
+
+	Ok(derivations)
+}
+
+pub async fn list_all_root_derivations_in_order<C>(db: &C) -> Result<Vec<Model>, GcliError>
+where
+	C: ConnectionTrait,
+{
+	let derivations = Entity::find()
+		.filter(Column::Path.is_null())
+		.order_by_asc(Column::RootAddress)
+		.all(db)
+		.await?;
+
+	Ok(derivations)
+}
diff --git a/src/inputs.rs b/src/inputs.rs
new file mode 100644
index 0000000..1eb188e
--- /dev/null
+++ b/src/inputs.rs
@@ -0,0 +1,85 @@
+use crate::utils::GcliError;
+
+pub fn prompt_password() -> Result<String, GcliError> {
+	prompt_password_query("Password")
+}
+
+pub fn prompt_password_confirm() -> Result<String, GcliError> {
+	prompt_password_query_confirm("Password")
+}
+
+pub fn prompt_password_query(query: impl ToString) -> Result<String, GcliError> {
+	dialoguer::Password::default()
+		.with_prompt(query.to_string())
+		.allow_empty_password(true)
+		.interact()
+		.map_err(|e| GcliError::Input(e.to_string()))
+}
+
+pub fn prompt_password_query_confirm(query: impl ToString) -> Result<String, GcliError> {
+	dialoguer::Password::new()
+		.with_prompt(query.to_string())
+		.allow_empty_password(true)
+		.with_confirmation(
+			format!("Repeat {}", query.to_string()),
+			"Error: the values do not match.",
+		)
+		.interact()
+		.map_err(|e| GcliError::Input(e.to_string()))
+}
+
+/// Prompt for a (direct) vault name (cannot contain derivation path)
+///
+/// Also preventing to use '<' and '>' as those are used in the display of
+pub fn prompt_vault_name() -> Result<Option<String>, GcliError> {
+	let name = dialoguer::Input::new()
+		.with_prompt("Name")
+		.validate_with({
+			move |input: &String| -> Result<(), &str> {
+				if input.contains('<') || input.contains('>') || input.contains('/') {
+					Err("Name cannot contain characters '<', '>', '/'")
+				} else {
+					Ok(())
+				}
+			}
+		})
+		.allow_empty(true)
+		.interact_text()
+		.map_err(|e| GcliError::Input(e.to_string()))?;
+
+	let name = if name.trim().is_empty() {
+		None
+	} else {
+		Some(name.trim().to_string())
+	};
+
+	Ok(name)
+}
+
+/// Prompt for a derivation path
+pub fn prompt_vault_derivation_path() -> Result<String, GcliError> {
+	let path = dialoguer::Input::new()
+		.with_prompt("Derivation path")
+		.validate_with({
+			move |input: &String| -> Result<(), &str> {
+				if !input.starts_with("/") {
+					Err("derivation path needs to start with one or more '/'")
+				} else {
+					Ok(())
+				}
+			}
+		})
+		.allow_empty(false)
+		.interact_text()
+		.map_err(|e| GcliError::Input(e.to_string()))?;
+
+	Ok(path)
+}
+
+pub fn confirm_action(query: impl ToString) -> Result<bool, GcliError> {
+	dialoguer::Confirm::new()
+		.with_prompt(query.to_string())
+		//.default(false)
+		.interact()
+		.map_err(|e| GcliError::Input(e.to_string()))
+}
diff --git a/src/keys.rs b/src/keys.rs
index ae695c9..2a687cc 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -1,5 +1,7 @@
 use crate::*;
-use sr25519::Pair as Sr25519Pair;
+use sp_core::ed25519;
+use sp_core::ed25519::Pair as Ed25519Pair;
+use sp_core::sr25519::Pair as Sr25519Pair;
 
 pub const SUBSTRATE_MNEMONIC: &str =
 	"bottom drive obey lake curtain smoke basket hold race lonely fit walk";
@@ -17,6 +19,12 @@ pub enum SecretFormat {
 	/// Cesium (scrypt + nacl)
 	Cesium,
 }
+
+pub enum Secret {
+	SimpleSecret(String),
+	DualSecret(String, String),
+}
+
 impl FromStr for SecretFormat {
 	type Err = std::io::Error;
 
@@ -47,14 +55,21 @@ impl From<SecretFormat> for OsStr {
 }
 
 /// wrapper type for keys + signature
+//FIXME check if it's ok to keep large enum variant
+// Sr25519 second-largest variant contains at least 256 bytes
+// Ed25519 largest variant contains at least 480 bytes
+#[allow(clippy::large_enum_variant)]
 pub enum KeyPair {
 	Sr25519(Sr25519Pair),
+	Ed25519(Ed25519Pair),
+	//FIXME Cleanup
 	Nacl(nacl::sign::Keypair),
 }
 impl KeyPair {
 	pub fn address(&self) -> AccountId {
 		match self {
 			KeyPair::Sr25519(keypair) => keypair.public().into(),
+			KeyPair::Ed25519(keypair) => keypair.public().into(),
 			KeyPair::Nacl(keypair) => keypair.pkey.into(),
 		}
 	}
@@ -64,6 +79,7 @@ impl Clone for KeyPair {
 	fn clone(&self) -> Self {
 		match self {
 			KeyPair::Sr25519(keypair) => KeyPair::Sr25519(keypair.clone()),
+			KeyPair::Ed25519(keypair) => KeyPair::Ed25519(*keypair),
 			KeyPair::Nacl(keypair) => KeyPair::Nacl(nacl::sign::Keypair {
 				skey: keypair.skey,
 				pkey: keypair.pkey,
@@ -76,6 +92,11 @@ impl From<Sr25519Pair> for KeyPair {
 		KeyPair::Sr25519(pair)
 	}
 }
+impl From<Ed25519Pair> for KeyPair {
+	fn from(pair: Ed25519Pair) -> KeyPair {
+		KeyPair::Ed25519(pair)
+	}
+}
 impl From<nacl::sign::Keypair> for KeyPair {
 	fn from(pair: nacl::sign::Keypair) -> KeyPair {
 		KeyPair::Nacl(pair)
@@ -83,6 +104,8 @@ impl From<nacl::sign::Keypair> for KeyPair {
 }
 pub enum Signature {
 	Sr25519(sr25519::Signature),
+	Ed25519(ed25519::Signature),
+	//FIXME Cleanup
 	Nacl(Vec<u8>),
 }
 
@@ -106,9 +129,9 @@ pub fn pair_from_secret(
 	secret: &str,
 ) -> Result<Sr25519Pair, GcliError> {
 	match secret_format {
-		SecretFormat::Substrate => pair_from_str(secret),
-		SecretFormat::Predefined => pair_from_str(secret), /* if predefined, secret arg is replaced in config */
-		SecretFormat::Seed => pair_from_seed(secret),
+		SecretFormat::Substrate => pair_from_sr25519_str(secret),
+		SecretFormat::Predefined => pair_from_sr25519_str(secret), /* if predefined, secret arg is replaced in config */
+		SecretFormat::Seed => pair_from_sr25519_seed(secret),
 		SecretFormat::Cesium => Err(GcliError::Logic(
 			"cesium format incompatible with single secret".to_string(),
 		)),
@@ -116,7 +139,7 @@ pub fn pair_from_secret(
 }
 
 /// get keypair from given string secret
-pub fn pair_from_str(secret: &str) -> Result<Sr25519Pair, GcliError> {
+pub fn pair_from_sr25519_str(secret: &str) -> Result<Sr25519Pair, GcliError> {
 	Sr25519Pair::from_string(secret, None)
 		.map_err(|_| GcliError::Input("Invalid secret".to_string()))
 }
@@ -124,7 +147,7 @@ pub fn pair_from_str(secret: &str) -> Result<Sr25519Pair, GcliError> {
 /// get keypair from given seed
 // note: Sr25519Pair::from_string does exactly that when seed is 0x prefixed
 // (see from_string_with_seed method in crypto core)
-pub fn pair_from_seed(secret: &str) -> Result<Sr25519Pair, GcliError> {
+pub fn pair_from_sr25519_seed(secret: &str) -> Result<Sr25519Pair, GcliError> {
 	let mut seed = [0; 32];
 	hex::decode_to_slice(secret, &mut seed)
 		.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
@@ -132,6 +155,22 @@ pub fn pair_from_seed(secret: &str) -> Result<Sr25519Pair, GcliError> {
 	Ok(pair)
 }
 
+/// get keypair from given ed25519 string secret (used for cesium)
+pub fn pair_from_ed25519_str(secret: &str) -> Result<Ed25519Pair, GcliError> {
+	Ed25519Pair::from_string(secret, None)
+		.map_err(|_| GcliError::Input("Invalid secret".to_string()))
+}
+
+/// get keypair from given ed25519 seed (used for cesium)
+#[allow(unused)]
+pub fn pair_from_ed25519_seed(secret: &str) -> Result<Ed25519Pair, GcliError> {
+	let mut seed = [0; 32];
+	hex::decode_to_slice(secret, &mut seed)
+		.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
+	let pair = Ed25519Pair::from_seed(&seed);
+	Ok(pair)
+}
+
 /// get mnemonic from predefined derivation path
 pub fn predefined_mnemonic(deriv: &str) -> String {
 	format!("{SUBSTRATE_MNEMONIC}//{deriv}")
@@ -139,7 +178,7 @@ pub fn predefined_mnemonic(deriv: &str) -> String {
 
 /// get keypair from predefined secret
 pub fn pair_from_predefined(deriv: &str) -> Result<Sr25519Pair, GcliError> {
-	pair_from_str(&predefined_mnemonic(deriv))
+	pair_from_sr25519_str(&predefined_mnemonic(deriv))
 }
 
 /// get keypair from Cesium id/pwd
@@ -152,10 +191,15 @@ pub fn pair_from_cesium(id: String, pwd: String) -> nacl::sign::Keypair {
 
 /// ask user to input a secret
 pub fn prompt_secret_substrate() -> Sr25519Pair {
+	// Only interested in the keypair which is the second element of the tuple
+	prompt_secret_substrate_and_compute_keypair().1
+}
+
+pub fn prompt_secret_substrate_and_compute_keypair() -> (Secret, Sr25519Pair) {
 	loop {
-		let mnemonic = &rpassword::prompt_password("Mnemonic: ").unwrap();
-		match pair_from_str(mnemonic) {
-			Ok(pair) => return pair,
+		let mnemonic = rpassword::prompt_password("Mnemonic: ").unwrap();
+		match pair_from_sr25519_str(&mnemonic) {
+			Ok(pair) => return (Secret::SimpleSecret(mnemonic), pair),
 			Err(_) => println!("Invalid secret"),
 		}
 	}
@@ -163,17 +207,30 @@ pub fn prompt_secret_substrate() -> Sr25519Pair {
 
 /// ask user pass (Cesium format)
 pub fn prompt_secret_cesium() -> nacl::sign::Keypair {
+	// Only interested in the keypair which is the second element of the tuple
+	prompt_secret_cesium_and_compute_keypair().1
+}
+
+pub fn prompt_secret_cesium_and_compute_keypair() -> (Secret, nacl::sign::Keypair) {
 	let id = rpassword::prompt_password("Cesium id: ").unwrap();
 	let pwd = rpassword::prompt_password("Cesium password: ").unwrap();
-	pair_from_cesium(id, pwd)
+	(
+		Secret::DualSecret(id.clone(), pwd.clone()),
+		pair_from_cesium(id, pwd),
+	)
 }
 
 /// ask user to input a seed
 pub fn prompt_seed() -> Sr25519Pair {
+	// Only interested in the keypair which is the second element of the tuple
+	prompt_seed_and_compute_keypair().1
+}
+
+pub fn prompt_seed_and_compute_keypair() -> (Secret, Sr25519Pair) {
 	loop {
-		let seed = &rpassword::prompt_password("Seed: ").unwrap();
-		match pair_from_seed(seed) {
-			Ok(pair) => return pair,
+		let seed = rpassword::prompt_password("Seed: ").unwrap();
+		match pair_from_sr25519_seed(&seed) {
+			Ok(pair) => return (Secret::SimpleSecret(seed), pair),
 			Err(_) => println!("Invalid seed"),
 		}
 	}
@@ -181,8 +238,16 @@ pub fn prompt_seed() -> Sr25519Pair {
 
 /// ask user pass (Cesium format)
 pub fn prompt_predefined() -> Sr25519Pair {
+	// Only interested in the keypair which is the second element of the tuple
+	prompt_predefined_and_compute_keypair().1
+}
+
+pub fn prompt_predefined_and_compute_keypair() -> (Secret, Sr25519Pair) {
 	let deriv = rpassword::prompt_password("Enter derivation path: ").unwrap();
-	pair_from_predefined(&deriv).expect("invalid secret")
+	(
+		Secret::SimpleSecret(predefined_mnemonic(&deriv)),
+		pair_from_predefined(&deriv).expect("invalid secret"),
+	)
 }
 
 /// ask user secret in relevant format
@@ -196,7 +261,10 @@ pub fn prompt_secret(secret_format: SecretFormat) -> KeyPair {
 }
 
 /// get the secret from user, trying first keystore then input
-pub fn fetch_or_get_keypair(data: &Data, address: Option<AccountId>) -> Result<KeyPair, GcliError> {
+pub async fn fetch_or_get_keypair(
+	data: &Data,
+	address: Option<AccountId>,
+) -> Result<KeyPair, GcliError> {
 	if let Some(address) = address {
 		// if address corresponds to predefined, (for example saved to config)
 		// keypair is already known (useful for dev mode)
@@ -204,9 +272,9 @@ pub fn fetch_or_get_keypair(data: &Data, address: Option<AccountId>) -> Result<K
 			return Ok(pair_from_predefined(d).unwrap().into());
 		};
 
-		// look for corresponding secret in keystore
-		if let Some(secret) = commands::vault::try_fetch_secret(data, address)? {
-			return get_keypair(SecretFormat::Substrate, Some(&secret));
+		// look for corresponding KeyPair in keystore
+		if let Some(key_pair) = commands::vault::try_fetch_key_pair(data, address).await? {
+			return Ok(key_pair);
 		};
 	}
 	// at the moment, there is no way to confg gcli to use an other kind of secret
@@ -225,3 +293,358 @@ fn catch_known(address: &str) -> Option<&str> {
 		_ => None,
 	}
 }
+
+// Unit tests
+#[cfg(test)]
+mod tests {
+	use super::*;
+	mod substrate {
+		use super::*;
+
+		/// Testing sr25519 mnemonic derivations
+		///
+		/// Using `subkey` command to have expected values from mnemonic derivations (using `SUBSTRATE_MNEMONIC` for tests)
+		///
+		/// ##### The root mnemonic
+		/// ```
+		/// subkey inspect
+		/// URI:
+		/// Secret phrase:       bottom drive obey lake curtain smoke basket hold race lonely fit walk
+		///   Network ID:        substrate
+		///   Secret seed:       0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e
+		///   Public key (hex):  0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
+		///   Account ID:        0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
+		///   Public key (SS58): 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
+		///   SS58 Address:      5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
+		/// ```
+		///
+		/// ##### The '//0' derivation
+		/// ```
+		/// subkey inspect
+		/// URI:
+		/// Secret Key URI `bottom drive obey lake curtain smoke basket hold race lonely fit walk//0` is account:
+		///   Network ID:        substrate
+		///   Secret seed:       0x914dded06277afbe5b0e8a30bce539ec8a9552a784d08e530dc7c2915c478393
+		///   Public key (hex):  0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
+		///   Account ID:        0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
+		///   Public key (SS58): 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
+		///   SS58 Address:      5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
+		/// ```
+		#[test]
+		fn test_sr25519_mnemonic_derivations() {
+			let root_sr25519_pair = pair_from_sr25519_str(SUBSTRATE_MNEMONIC).unwrap();
+
+			let expected_root_ss58_address_string =
+				"5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV".to_string();
+			let root_ss58_address: AccountId = root_sr25519_pair.public().into();
+			println!("root SS58 Address: '{}'", root_ss58_address);
+			assert_eq!(
+				expected_root_ss58_address_string,
+				root_ss58_address.to_string()
+			);
+
+			// Using derive on root keypair to get '//0'
+			let (deriv_0_sr25519_pair, _seed) = root_sr25519_pair
+				.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
+				.unwrap();
+
+			let expected_deriv_0_ss58_address_string =
+				"5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH".to_string();
+			let deriv_0_ss58_address: AccountId = deriv_0_sr25519_pair.public().into();
+			println!("derived '//0' SS58 Address: '{}'", deriv_0_ss58_address);
+			assert_eq!(
+				expected_deriv_0_ss58_address_string,
+				deriv_0_ss58_address.to_string()
+			);
+
+			// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
+			let deriv_0_suri = SUBSTRATE_MNEMONIC.to_string() + "//0";
+			let deriv_0_suri_sr25519_pair =
+				sp_core::sr25519::Pair::from_string(&deriv_0_suri, None).unwrap();
+			let deriv_0_suri_ss58_address: AccountId = deriv_0_suri_sr25519_pair.public().into();
+			println!(
+				"derived '//0' from suri SS58 Address: '{}'",
+				deriv_0_suri_ss58_address
+			);
+			assert_eq!(
+				expected_deriv_0_ss58_address_string,
+				deriv_0_suri_ss58_address.to_string()
+			);
+		}
+	}
+
+	mod seed {
+		use super::*;
+
+		/// Testing sr25519 seed derivations
+		///
+		/// Using `subkey` command to have expected values from seed derivations (using seed linked to `SUBSTRATE_MNEMONIC` for tests)
+		///
+		/// ##### The root seed
+		/// ```
+		/// subkey inspect
+		/// URI:
+		/// Secret Key URI `0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e` is account:
+		///   Network ID:        substrate
+		///   Secret seed:       0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e
+		///   Public key (hex):  0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
+		///   Account ID:        0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
+		///   Public key (SS58): 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
+		///   SS58 Address:      5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
+		/// ```
+		///
+		/// ##### The '//0' derivation
+		/// ```
+		/// subkey inspect
+		/// URI:
+		/// Secret Key URI `0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e//0` is account:
+		///   Network ID:        substrate
+		///   Secret seed:       0x914dded06277afbe5b0e8a30bce539ec8a9552a784d08e530dc7c2915c478393
+		///   Public key (hex):  0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
+		///   Account ID:        0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
+		///   Public key (SS58): 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
+		///   SS58 Address:      5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
+		/// ```
+		#[test]
+		fn test_sr25519_seed_derivations() {
+			let root_seed = "fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e";
+			let root_sr25519_pair = pair_from_sr25519_seed(root_seed).unwrap();
+
+			let expected_root_ss58_address_string =
+				"5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV".to_string();
+			let root_ss58_address: AccountId = root_sr25519_pair.public().into();
+			println!("root SS58 Address: '{}'", root_ss58_address);
+			assert_eq!(
+				expected_root_ss58_address_string,
+				root_ss58_address.to_string()
+			);
+
+			// Using derive on root keypair to get "//0"
+			let (deriv_0_sr25519_pair, _seed) = root_sr25519_pair
+				.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
+				.unwrap();
+
+			let expected_deriv_0_ss58_address_string =
+				"5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH".to_string();
+			let deriv_0_ss58_address: AccountId = deriv_0_sr25519_pair.public().into();
+			println!("derived '//0' SS58 Address: '{}'", deriv_0_ss58_address);
+			assert_eq!(
+				expected_deriv_0_ss58_address_string,
+				deriv_0_ss58_address.to_string()
+			);
+
+			// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
+			let deriv_0_suri = "0x".to_string() + root_seed + "//0";
+			let deriv_0_suri_sr25519_pair =
+				sp_core::sr25519::Pair::from_string(&deriv_0_suri, None).unwrap();
+			let deriv_0_suri_ss58_address: AccountId = deriv_0_suri_sr25519_pair.public().into();
+			println!(
+				"derived '//0' from suri SS58 Address: '{}'",
+				deriv_0_suri_ss58_address
+			);
+			assert_eq!(
+				expected_deriv_0_ss58_address_string,
+				deriv_0_suri_ss58_address.to_string()
+			);
+		}
+	}
+
+	mod predefined {
+		use super::*;
+
+		/// Testing predefined mnemonic derivations (using names instead of indexes)
+		///
+		/// Using `subkey` command to have expected values from predefined mnemonic derivations
+		///
+		/// ##### The root mnemonic
+		/// ```
+		/// subkey inspect
+		/// URI:
+		/// Secret phrase:       bottom drive obey lake curtain smoke basket hold race lonely fit walk
+		///   Network ID:        substrate
+		///   Secret seed:       0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e
+		///   Public key (hex):  0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
+		///   Account ID:        0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
+		///   Public key (SS58): 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
+		///   SS58 Address:      5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
+		/// ```
+		///
+		/// ##### The '//Alice' derivation
+		/// ```
+		/// subkey inspect
+		/// URI:
+		/// Secret Key URI `bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice` is account:
+		///   Network ID:        substrate
+		///   Secret seed:       0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a
+		///   Public key (hex):  0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
+		///   Account ID:        0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
+		///   Public key (SS58): 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
+		///   SS58 Address:      5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
+		/// ```
+		#[test]
+		fn test_predefined_mnemonic_derivations() {
+			let root_sr25519_pair = pair_from_sr25519_str(SUBSTRATE_MNEMONIC).unwrap();
+
+			let expected_root_ss58_address_string =
+				"5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV".to_string();
+			let root_ss58_address: AccountId = root_sr25519_pair.public().into();
+			println!("root SS58 Address: '{}'", root_ss58_address);
+			assert_eq!(
+				expected_root_ss58_address_string,
+				root_ss58_address.to_string()
+			);
+
+			// Using derive on root keypair to get Alice
+			let (alice_sr25519_pair, _seed) = root_sr25519_pair
+				.derive(
+					Some(sp_core::DeriveJunction::hard("Alice")).into_iter(),
+					None,
+				)
+				.unwrap();
+
+			let expected_alice_ss58_address_string =
+				"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string();
+			let alice_ss58_address: AccountId = alice_sr25519_pair.public().into();
+			println!("Alice SS58 Address: '{}'", alice_ss58_address);
+			assert_eq!(
+				expected_alice_ss58_address_string,
+				alice_ss58_address.to_string()
+			);
+
+			// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
+			let alice_suri = SUBSTRATE_MNEMONIC.to_owned() + "//Alice";
+			let alice_suri_sr25519_pair =
+				sp_core::sr25519::Pair::from_string(&alice_suri, None).unwrap();
+			let alice_suri_ss58_address: AccountId = alice_suri_sr25519_pair.public().into();
+			println!("Alice suri SS58 Address: '{}'", alice_suri_ss58_address);
+			assert_eq!(
+				expected_alice_ss58_address_string,
+				alice_suri_ss58_address.to_string()
+			);
+		}
+	}
+
+	mod cesium {
+		use super::*;
+
+		/// Test which verifies that it's possible to derive a key coming from a cesium v1 id & password
+		///
+		/// Using subkey command with **ed25519** scheme to show we can derive a key from a seed
+		/// and to retrieve expected values.
+		///
+		/// ##### Without derivation (using seed from the test)
+		/// ```
+		/// subkey inspect --scheme ed25519
+		/// URI:
+		/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84` is account:
+		///   Network ID:        substrate
+		///   Secret seed:       0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84
+		///   Public key (hex):  0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
+		///   Account ID:        0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
+		///   Public key (SS58): 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
+		///   SS58 Address:      5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
+		/// ```
+		///
+		/// ##### With derivation '//0' (using seed from the test)
+		/// ```
+		/// subkey inspect --scheme ed25519
+		/// URI:
+		/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84//0` is account:
+		///   Network ID:        substrate
+		///   Secret seed:       0x916e95359a49c82e3d84269b90551433352c433eb9bb270fb8cb86e8a6c9ec85
+		///   Public key (hex):  0x1658ce32f039dff26d83b5282f611cfed8c71296a311417ef737db4e016194de
+		///   Account ID:        0x1658ce32f039dff26d83b5282f611cfed8c71296a311417ef737db4e016194de
+		///   Public key (SS58): 5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf
+		///   SS58 Address:      5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf
+		/// ```
+		#[test]
+		fn test_cesium_v1_key_derivation() {
+			let cesium_id = "test_cesium_id".to_string();
+			let cesium_pwd = "test_cesium_pwd".to_string();
+
+			let cesium_keypair = pair_from_cesium(cesium_id, cesium_pwd);
+
+			println!(
+				"Nacl keypair: pkey:'0x{}' skey:'0x{}'",
+				hex::encode(cesium_keypair.pkey),
+				hex::encode(cesium_keypair.skey)
+			);
+
+			let cesium_v1_pubkey = bs58::encode(cesium_keypair.pkey).into_string();
+			println!("Cesium v1 Pubkey: '{}'", cesium_v1_pubkey);
+			let cesium_v1_address: AccountId = cesium_keypair.pkey.into();
+			println!("SS58 Address: '{}'", cesium_v1_address);
+
+			//ed25519 seed **seems** to be the first 32 bytes of the secret key from nacl keypair
+			let mut seed: [u8; 32] = [0; 32];
+			seed.copy_from_slice(&cesium_keypair.skey[0..32]);
+			println!();
+			println!("ed25519 seed: '0x{}'", hex::encode(seed));
+
+			let ed25519_pair_from_seed = sp_core::ed25519::Pair::from_seed(&seed);
+
+			println!();
+			println!(
+				"ed25519 keypair from seed : public:'0x{}' raw_vec:'0x{}'",
+				hex::encode(ed25519_pair_from_seed.public().0),
+				hex::encode(ed25519_pair_from_seed.to_raw_vec().as_slice())
+			);
+			let ed25519_address_from_seed: AccountId = ed25519_pair_from_seed.public().into();
+			println!(
+				"ed25519 keypair from seed : public SS58 Address:'{}'",
+				ed25519_address_from_seed
+			);
+			assert_eq!(cesium_v1_address, ed25519_address_from_seed);
+
+			let root_suri = "0x".to_string() + &hex::encode(seed);
+			let ed25519_pair_from_suri =
+				sp_core::ed25519::Pair::from_string(&root_suri, None).unwrap();
+			let ed25519_address_from_suri: AccountId = ed25519_pair_from_suri.public().into();
+			println!(
+				"ed25519 keypair from suri : public SS58 Address:'{}'",
+				ed25519_address_from_suri
+			);
+			assert_eq!(cesium_v1_address, ed25519_address_from_suri);
+
+			// Tested derivation manually with `subkey` command using adapted suri: `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84//0`
+			let expected_ss58_address_derivation_0 =
+				"5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf".to_string();
+
+			// Using derive on the ed25519 keypair
+			let (derived_ed25519_pair, _seed) = ed25519_pair_from_seed
+				.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
+				.unwrap();
+			println!();
+			println!(
+				"derived ed25519 keypair: public:'0x{}' raw_vec:'0x{}'",
+				hex::encode(derived_ed25519_pair.public().0),
+				hex::encode(derived_ed25519_pair.to_raw_vec().as_slice())
+			);
+			let derived_ed25519_address =
+				subxt::utils::AccountId32::from(derived_ed25519_pair.public());
+			println!(
+				"derived ed25519 keypair: public SS58 Address:'{}'",
+				derived_ed25519_address
+			);
+			assert_eq!(
+				expected_ss58_address_derivation_0,
+				derived_ed25519_address.to_string()
+			);
+
+			// Using sp_core::ed25519::Pair::from_string(suri, None) to derive keypair from suri
+			let deriv_0_suri = root_suri.clone() + "//0";
+			let derived_ed25519_pair_from_suri =
+				sp_core::ed25519::Pair::from_string(&deriv_0_suri, None).unwrap();
+			let derived_ed25519_address_from_suri: AccountId =
+				derived_ed25519_pair_from_suri.public().into();
+			println!(
+				"derived ed25519 keypair from suri : public SS58 Address:'{}'",
+				derived_ed25519_address_from_suri
+			);
+			assert_eq!(
+				expected_ss58_address_derivation_0,
+				derived_ed25519_address_from_suri.to_string()
+			);
+		}
+	}
+}
diff --git a/src/main.rs b/src/main.rs
index ba4857a..68b37cc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,11 @@
 mod commands;
 mod conf;
 mod data;
+mod database;
 mod display;
+mod entities;
 mod indexer;
+mod inputs;
 mod keys;
 mod runtime_config;
 mod utils;
@@ -51,8 +54,8 @@ pub struct Args {
 	#[clap(short = 'S', long)]
 	secret_format: Option<SecretFormat>,
 	/// Address
-	#[clap(short, long)]
-	address: Option<String>,
+	#[clap(flatten)]
+	address_or_vault_name: Option<AddressOrVaultNameGroupOptional>,
 	/// Overwrite duniter websocket RPC endpoint
 	#[clap(short, long)]
 	url: Option<String>,
@@ -67,6 +70,51 @@ pub struct Args {
 	output_format: OutputFormat,
 }
 
+trait AddressOrVaultName {
+	fn address(&self) -> Option<&AccountId>;
+	fn name(&self) -> Option<&String>;
+}
+
+#[derive(Debug, clap::Args, Clone)]
+#[group(required = false, multiple = false)]
+pub struct AddressOrVaultNameGroupOptional {
+	/// Account Address
+	#[clap(short)]
+	address: Option<AccountId>,
+	/// Vault name for that account
+	#[clap(short = 'v')]
+	name: Option<String>,
+}
+
+impl AddressOrVaultName for AddressOrVaultNameGroupOptional {
+	fn address(&self) -> Option<&AccountId> {
+		self.address.as_ref()
+	}
+	fn name(&self) -> Option<&String> {
+		self.name.as_ref()
+	}
+}
+
+#[derive(Debug, clap::Args, Clone)]
+#[group(required = true, multiple = false)]
+pub struct AddressOrVaultNameGroup {
+	/// Account Address
+	#[clap(short)]
+	address: Option<AccountId>,
+	/// Vault name for that account
+	#[clap(short = 'v')]
+	name: Option<String>,
+}
+
+impl AddressOrVaultName for AddressOrVaultNameGroup {
+	fn address(&self) -> Option<&AccountId> {
+		self.address.as_ref()
+	}
+	fn name(&self) -> Option<&String> {
+		self.name.as_ref()
+	}
+}
+
 // TODO derive the fromstr implementation
 /// secret format
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
@@ -158,7 +206,7 @@ async fn main() -> Result<(), GcliError> {
 	env_logger::init();
 
 	// parse argument and initialize data
-	let data = Data::new(Args::parse())?;
+	let data = Data::new(Args::parse()).await?;
 
 	// match subcommands
 	let result = match data.args.subcommand.clone() {
@@ -182,8 +230,8 @@ async fn main() -> Result<(), GcliError> {
 			commands::blockchain::handle_command(data, subcommand).await
 		}
 		Subcommand::Indexer(subcommand) => indexer::handle_command(data, subcommand).await,
-		Subcommand::Config(subcommand) => conf::handle_command(data, subcommand),
-		Subcommand::Vault(subcommand) => commands::vault::handle_command(data, subcommand),
+		Subcommand::Config(subcommand) => conf::handle_command(data, subcommand).await,
+		Subcommand::Vault(subcommand) => commands::vault::handle_command(data, subcommand).await,
 		Subcommand::Cesium(subcommand) => commands::cesium::handle_command(data, subcommand).await,
 		Subcommand::Publish => commands::publish::handle_command().await,
 	};
diff --git a/src/utils.rs b/src/utils.rs
index 05d7252..2393bd3 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,4 +1,5 @@
 use crate::*;
+use sea_orm::DbErr;
 
 /// track progress of transaction on the network
 /// until it is in block with success or failure
@@ -56,14 +57,19 @@ pub async fn submit_call<TxPayload: Payload>(
 		.await?;
 
 	// sign and submit
-	match data.keypair() {
+	match data.keypair().await {
 		// sr25519 key pair
 		KeyPair::Sr25519(keypair) => data.client().tx().create_signed_offline(
 			payload,
 			&PairSigner::<Runtime, sp_core::sr25519::Pair>::new(keypair),
 			DefaultExtrinsicParamsBuilder::new().nonce(nonce).build(),
 		),
-		// nacl key pair
+		KeyPair::Ed25519(keypair) => data.client().tx().create_signed_offline(
+			payload,
+			&PairSigner::<Runtime, sp_core::ed25519::Pair>::new(keypair),
+			DefaultExtrinsicParamsBuilder::new().nonce(nonce).build(),
+		),
+		//FIXME cleanup
 		KeyPair::Nacl(keypair) => data.client().tx().create_signed_offline(
 			payload,
 			&commands::cesium::CesiumSigner::new(keypair),
@@ -93,19 +99,29 @@ pub fn look_event<E: std::fmt::Debug + StaticEvent + DisplayEvent>(
 /// custom error type intended to provide more convenient error message to user
 #[derive(Debug)]
 pub enum GcliError {
+	//TODO Check allowing dead code is ok for those (we are using the values only in case of exception)
 	/// error coming from subxt
 	Subxt(subxt::Error),
 	/// error coming from duniter
+	#[allow(dead_code)]
 	Duniter(String),
 	/// error coming from indexer
+	#[allow(dead_code)]
 	Indexer(String),
+	/// error coming from database
+	#[allow(dead_code)]
+	DatabaseError(DbErr),
 	/// logic error (illegal operation or security)
+	#[allow(dead_code)]
 	Logic(String),
 	/// input error
+	#[allow(dead_code)]
 	Input(String),
 	/// error coming from anyhow (to be removed)
+	#[allow(dead_code)]
 	Anyhow(anyhow::Error),
 	/// error coming from io
+	#[allow(dead_code)]
 	IoError(std::io::Error),
 }
 impl std::fmt::Display for GcliError {
@@ -141,3 +157,8 @@ impl From<std::io::Error> for GcliError {
 		GcliError::IoError(error)
 	}
 }
+impl From<sea_orm::DbErr> for GcliError {
+	fn from(error: sea_orm::DbErr) -> Self {
+		GcliError::DatabaseError(error)
+	}
+}
-- 
GitLab