diff --git a/Cargo.lock b/Cargo.lock
index 7c5fc58d699276aae555459c39ab04a3a3d0e754..f6684b603f95d700986d4df8084f8677180589ce 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1598,6 +1598,7 @@ dependencies = [
  "sc-finality-grandpa",
  "sc-keystore",
  "sc-network",
+ "sc-network-common",
  "sc-rpc-api",
  "sc-service",
  "sc-telemetry",
diff --git a/Cargo.toml b/Cargo.toml
index 42515b104c23d9b439972ac2d0d268895ffb91fa..0896f98b8037d2e72ff49d5552a27b58b2df43f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -99,6 +99,7 @@ sc-executor = { git = "https://github.com/duniter/substrate", branch = "duniter-
 sc-finality-grandpa = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" }
 sc-keystore = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" }
 sc-network = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" }
+sc-network-common = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" }
 sc-rpc-api = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" }
 sc-service = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32", default-features = false }
 sc-telemetry = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.32" }
diff --git a/node/specs/gtest_client-specs.json b/node/specs/gtest_client-specs.json
new file mode 100644
index 0000000000000000000000000000000000000000..a554af2e64ad2ecf26fd2444325e7d2d881c1ed1
--- /dev/null
+++ b/node/specs/gtest_client-specs.json
@@ -0,0 +1,20 @@
+{
+    "name": "Ğdev",
+    "id": "gdev",
+    "chainType": "Live",
+    "bootNodes": [
+        "/dns/gdev.p2p.legal/tcp/30334/p2p/12D3KooW9v5WsP38qU1kmafvA4CDw2vzYnFoWtdUqwonZtJK597r",
+        "/dns/gdev.elo.tf/tcp/30334/p2p/12D3KooWH4gAzvcRRCH8bFGZYomCGn3tosXDGtnL5yVVMkpGRL7B",
+        "/dns/gdev.librelois.fr/tcp/30334/p2p/12D3KooWMb8hmxdtBQhGLB8caokbAZQ3YokN9erMe6oAEsPDrwYf"
+    ],
+    "telemetryEndpoints": [
+        [
+            "/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F",
+            0
+        ]
+    ],
+    "properties": {
+        "tokenDecimals": 2,
+        "tokenSymbol": "ĞD"
+    }
+}
\ No newline at end of file
diff --git a/resources/gtest.json b/node/specs/gtest_genesis.json
similarity index 100%
rename from resources/gtest.json
rename to node/specs/gtest_genesis.json
diff --git a/node/src/chain_spec/gtest.rs b/node/src/chain_spec/gtest.rs
index a00b51ea1a097240da30c98cf6ec5f28383b2c4b..023aef9f39e0ec298e9953a216cbb5bf9e7c11c8 100644
--- a/node/src/chain_spec/gtest.rs
+++ b/node/src/chain_spec/gtest.rs
@@ -18,13 +18,18 @@ use super::*;
 use common_runtime::constants::*;
 use common_runtime::entities::IdtyData;
 use common_runtime::*;
+use gtest_genesis::{build_genesis, GenesisJson};
 use gtest_runtime::{
     opaque::SessionKeys, AccountConfig, AccountId, AuthorityMembersConfig, BabeConfig, CertConfig,
     GenesisConfig, IdentityConfig, ImOnlineId, MembershipConfig, SessionConfig, SmithCertConfig,
     SmithMembershipConfig, SudoConfig, SystemConfig, TechnicalCommitteeConfig,
     UniversalDividendConfig, WASM_BINARY,
 };
+use jsonrpsee::core::JsonValue;
+use sc_network_common::config::MultiaddrWithPeerId; // in the future available in sc_network::config
 use sc_service::ChainType;
+use sc_telemetry::TelemetryEndpoints;
+use serde::Deserialize;
 use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
 use sp_consensus_babe::AuthorityId as BabeId;
 use sp_core::{blake2_256, sr25519, Encode, H256};
@@ -32,6 +37,7 @@ use sp_finality_grandpa::AuthorityId as GrandpaId;
 use sp_membership::MembershipData;
 use std::collections::BTreeMap;
 
+pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
 pub type AuthorityKeys = (
     AccountId,
     GrandpaId,
@@ -39,14 +45,6 @@ pub type AuthorityKeys = (
     ImOnlineId,
     AuthorityDiscoveryId,
 );
-
-pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
-
-const TOKEN_DECIMALS: usize = 2;
-const TOKEN_SYMBOL: &str = "ĞT";
-// The URL for the telemetry server.
-// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
-
 /// Generate an authority keys.
 pub fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys {
     (
@@ -57,7 +55,6 @@ pub fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys {
         get_from_seed::<AuthorityDiscoveryId>(s),
     )
 }
-
 /// Generate session keys
 fn get_session_keys_from_seed(s: &str) -> SessionKeys {
     let authority_keys = get_authority_keys_from_seed(s);
@@ -68,7 +65,6 @@ fn get_session_keys_from_seed(s: &str) -> SessionKeys {
         authority_keys.4,
     )
 }
-
 /// make session keys struct
 fn session_keys(
     grandpa: GrandpaId,
@@ -84,29 +80,27 @@ fn session_keys(
     }
 }
 
-// can not use ClientSpec which is private :'(
-// // client specs used in chainspecs
-// static client_spec_dev = ClientSpec {
-//     name: "ĞTest Development",
-//     id: "gtest_dev",
-//     chain_type: ChainType::Development,
-//     boot_nodes: vec![],
-//     telemetry_endpoints: None,
-//     protocol_id: None,
-//     fork_id: None,
-//     properties: Some(
-//         serde_json::json!({
-//             "tokenDecimals": TOKEN_DECIMALS,
-//             "tokenSymbol": TOKEN_SYMBOL,
-//         })
-//         .as_object()
-//         .expect("must be a map")
-//         .clone()),
-//     extensions: None,
-//     consensus_engine: (),
-//     genesis: Default::default(),
-//     code_substitutes: BTreeMap::new(),
-// };
+// === client specifications ===
+
+/// emulate client specifications to get them from json
+#[derive(Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
+pub struct ClientSpec {
+    name: String,
+    id: String,
+    chain_type: ChainType,
+    boot_nodes: Vec<MultiaddrWithPeerId>,
+    telemetry_endpoints: Option<TelemetryEndpoints>,
+    // protocol_id: Option<String>,
+    // #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")]
+    // fork_id: Option<String>,
+    properties: Option<serde_json::Map<std::string::String, JsonValue>>,
+    // #[serde(default)]
+    // code_substitutes: BTreeMap<String, Bytes>,
+}
+
+// === development chainspecs ===
 
 /// generate development chainspec with Alice validator
 // there is some code duplication because we can not use ClientSpec
@@ -117,6 +111,19 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> {
     if let Ok(genesis_json_path) = std::env::var("DUNITER_GTEST_GENESIS") {
         // log
         log::info!("loading genesis from {genesis_json_path}");
+        // open json genesis file
+        let file = std::fs::File::open(&genesis_json_path)
+            .map_err(|e| format!("Error opening gen conf file `{}`: {}", genesis_json_path, e))?;
+        // memory map the file to avoid loading it in memory
+        let bytes = unsafe {
+            memmap2::Mmap::map(&file).map_err(|e| {
+                format!("Error mmaping gen conf file `{}`: {}", genesis_json_path, e)
+            })?
+        };
+        // parse the json file
+        let genesis_data: GenesisJson = serde_json::from_slice(&bytes)
+            .map_err(|e| format!("Error parsing gen conf file: {}", e))?;
+
         // return chainspecs
         Ok(ChainSpec::from_genesis(
             // Name
@@ -127,9 +134,9 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> {
             sc_service::ChainType::Development,
             // genesis config constructor
             move || {
-                super::gtest_genesis::build_genesis(
-                    // path of json genesis
-                    &genesis_json_path,
+                build_genesis(
+                    // genesis data built from json
+                    genesis_data.clone(),
                     // wasm binary
                     wasm_binary,
                     // replace authority by Alice
@@ -148,8 +155,8 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> {
             // Properties
             Some(
                 serde_json::json!({
-                    "tokenDecimals": TOKEN_DECIMALS,
-                    "tokenSymbol": TOKEN_SYMBOL,
+                    "tokenDecimals": 2,
+                    "tokenSymbol": "ĞT",
                 })
                 .as_object()
                 .expect("must be a map")
@@ -194,8 +201,8 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> {
             // Properties
             Some(
                 serde_json::json!({
-                        "tokenDecimals": TOKEN_DECIMALS,
-                        "tokenSymbol": TOKEN_SYMBOL,
+                    "tokenDecimals": 2,
+                    "tokenSymbol": "ĞT",
                 })
                 .as_object()
                 .expect("must be a map")
@@ -207,29 +214,29 @@ pub fn development_chainspecs() -> Result<ChainSpec, String> {
     }
 }
 
-/// generate live chainspecs from DUNITER_GTEST_GENESIS
-/// one smith must have session keys
-pub fn live_chainspecs() -> Result<ChainSpec, String> {
-    let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?;
+// === live chainspecs ===
 
-    let genesis_json_path =
-        std::env::var("DUNITER_GTEST_GENESIS").expect("DUNITER_GTEST_GENESIS needed");
+/// live chainspecs
+// one smith must have session keys
+pub fn live_chainspecs(
+    client_spec: ClientSpec,
+    genesis_data: GenesisJson,
+) -> Result<ChainSpec, String> {
+    let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?;
 
-    // log
-    log::info!("loading genesis from {genesis_json_path}");
     // return chainspecs
     Ok(ChainSpec::from_genesis(
         // Name
-        "ĞTest",
+        client_spec.name.as_str(),
         // ID
-        "gtest",
+        client_spec.id.as_str(),
         // chain type
-        sc_service::ChainType::Live,
+        client_spec.chain_type,
         // genesis config constructor
         move || {
-            super::gtest_genesis::build_genesis(
-                // path of json genesis
-                &genesis_json_path,
+            build_genesis(
+                // genesis data
+                genesis_data.clone(),
                 // wasm binary
                 wasm_binary,
                 // do not replace session keys
@@ -237,29 +244,16 @@ pub fn live_chainspecs() -> Result<ChainSpec, String> {
             )
             .expect("genesis building failed")
         },
-        // Bootnodes (TODO add bootnodes)
-        vec![],
+        // Bootnodes
+        client_spec.boot_nodes,
         // Telemetry (by default, enable telemetry, can be disabled with argument)
-        Some(
-            sc_service::config::TelemetryEndpoints::new(vec![(
-                "wss://telemetry.polkadot.io/submit/".to_owned(),
-                0,
-            )])
-            .expect("invalid telemetry endpoints"),
-        ), // Protocol ID
-        Some("gtest1"),
-        //Fork ID
+        client_spec.telemetry_endpoints,
+        // Protocol ID
+        None,
+        // Fork ID
         None,
         // Properties
-        Some(
-            serde_json::json!({
-                "tokenDecimals": TOKEN_DECIMALS,
-                "tokenSymbol": TOKEN_SYMBOL,
-            })
-            .as_object()
-            .expect("must be a map")
-            .clone(),
-        ),
+        client_spec.properties,
         // Extensions
         None,
     ))
diff --git a/node/src/chain_spec/gtest_genesis.rs b/node/src/chain_spec/gtest_genesis.rs
index 9aa6f737fd9c253d9e735772df834b111c39baed..004a1cc6b1c3157a55a6e28ce8ea3d76ad9d75c4 100644
--- a/node/src/chain_spec/gtest_genesis.rs
+++ b/node/src/chain_spec/gtest_genesis.rs
@@ -38,7 +38,8 @@ static SMITH_MIN_CERT: u32 = parameters::SmithWotMinCertForMembership::get();
 
 // define structure of json
 #[derive(Clone, Deserialize)]
-struct GenesisJson {
+#[serde(deny_unknown_fields)]
+pub struct GenesisJson {
     identities: HashMap<String, Identity>,
     smiths: HashMap<String, Smith>,
     first_ud: u64,
@@ -96,8 +97,8 @@ fn validate_idty_name(idty_name: &str) -> bool {
 /// ============================================================================================ ///
 /// build genesis from json file
 pub fn build_genesis(
-    // path of genesis config
-    genesis_config_path: &str,
+    // genesis data build from json
+    genesis_data: GenesisJson,
     // wasm binary
     wasm_binary: &[u8],
     // useful to enforce Alice authority when developing
@@ -120,28 +121,6 @@ pub fn build_genesis(
         };
     log::info!("genesis timestamp: {}", genesis_timestamp);
 
-    // open json genesis file
-    let file = std::fs::File::open(&genesis_config_path).map_err(|e| {
-        format!(
-            "Error opening gen conf file `{}`: {}",
-            genesis_config_path, e
-        )
-    })?;
-
-    // memory map the file to avoid loading it in memory
-    let bytes = unsafe {
-        memmap2::Mmap::map(&file).map_err(|e| {
-            format!(
-                "Error mmaping gen conf file `{}`: {}",
-                genesis_config_path, e
-            )
-        })?
-    };
-
-    // parse the json file
-    let genesis_data: GenesisJson = serde_json::from_slice(&bytes)
-        .map_err(|e| format!("Error parsing gen conf file: {}", e))?;
-
     // declare variables for building genesis
     // -------------------------------------
     // track if fatal error occured, but let processing continue
diff --git a/node/src/command.rs b/node/src/command.rs
index 107ccd59c2a5f76080d93a5e5acbaf0be5cc5e2a..bd16a8f0fe2759936a3afb38872fe0f2f18efa2d 100644
--- a/node/src/command.rs
+++ b/node/src/command.rs
@@ -18,6 +18,8 @@
 pub mod key;
 pub mod utils;
 
+#[cfg(feature = "gtest")]
+use crate::chain_spec::{gtest, gtest_genesis};
 use crate::cli::{Cli, Subcommand};
 #[cfg(feature = "g1")]
 use crate::service::G1Executor;
@@ -130,9 +132,29 @@ impl SubstrateCli for Cli {
             // otherwise you get a local testnet with generated genesis
             #[cfg(feature = "gtest")]
             "gtest_dev" => Box::new(chain_spec::gtest::development_chainspecs()?),
-            // chainspecs for live network (must set DUNITER_GTEST_GENESIS)
+            // chainspecs for live network
+            // must have following files in node/specs folder:
+            // - gtest.json
+            // - gtest_client-specs.json
             #[cfg(feature = "gtest")]
-            "gtest_live" => Box::new(chain_spec::gtest::live_chainspecs()?),
+            "gtest_live" => {
+                const JSON_CLIENT_SPEC: &str = "./node/specs/gtest_client-specs.json";
+                const JSON_GENESIS: &str = "./node/specs/gtest_genesis.json";
+                let client_spec: gtest::ClientSpec = serde_json::from_slice(
+                    &std::fs::read(JSON_CLIENT_SPEC)
+                        .map_err(|e| format!("failed to read {JSON_CLIENT_SPEC} {e}"))?[..],
+                )
+                .map_err(|e| format!("failed to parse {e}"))?;
+                let genesis_data: gtest_genesis::GenesisJson = serde_json::from_slice(
+                    &std::fs::read(JSON_GENESIS)
+                        .map_err(|e| format!("failed to read {JSON_GENESIS} {e}"))?[..],
+                )
+                .map_err(|e| format!("failed to parse {e}"))?;
+                Box::new(chain_spec::gtest::live_chainspecs(
+                    client_spec,
+                    genesis_data,
+                )?)
+            }
             // hardcoded previously generated raw chainspecs
             #[cfg(feature = "gtest")]
             "gtest" => {