diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5ee1e0..5da34d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ bump. Currently experimental: project bundling # Unreleased * feat: `icp canister delete` will now send the canister's remaining cycles to the caller +* feat: The root key of a `connected` network is now optional. When you connect to a local (loopback) network by URL, the root key is fetched from the network automatically, so `--network ` no longer requires `--root-key`. A remote network must still provide its root key (via the manifest `root-key` or `--root-key`); previously an omitted key silently defaulted to the mainnet key, which now surfaces a clear error instead. The built-in `ic` network is unchanged. # v1.0.2 diff --git a/crates/icp-cli/src/options.rs b/crates/icp-cli/src/options.rs index 54722beb..74c5a43e 100644 --- a/crates/icp-cli/src/options.rs +++ b/crates/icp-cli/src/options.rs @@ -99,8 +99,9 @@ pub(crate) struct NetworkOptInner { #[arg(long, short = 'n', env = "ICP_NETWORK", group = "network-select", help_heading = heading::NETWORK_PARAMETERS, value_parser = parse_network_target)] network: Option, - /// The root key to use if connecting to a network by URL. - /// Required when using `--network `. + /// The root key to use when connecting to a network by URL. + /// Optional: for a local (loopback) URL it is fetched from the network; + /// for a remote URL it is required. #[arg(long, short = 'k', requires = "network", help_heading = heading::NETWORK_PARAMETERS, value_parser = parse_root_key)] root_key: Option, } @@ -109,7 +110,7 @@ pub(crate) struct NetworkOptInner { // validation to only allow --root-key when the network is a url. #[derive(Clone, Debug, Default)] pub(crate) enum NetworkOpt { - Url(Url, RootKey), + Url(Url, Option), Name(String), @@ -122,14 +123,10 @@ impl FromArgMatches for NetworkOpt { let inner = NetworkOptInner::from_arg_matches(matches)?; match (inner.network, inner.root_key) { - // Case: We have a URL, so we REQUIRE the root key - (Some(NetworkTarget::Url(url)), Some(key)) => Ok(NetworkOpt::Url(url, key)), - - // ERROR Case: URL provided but missing root key - (Some(NetworkTarget::Url(_)), None) => Err(clap::Error::raw( - ErrorKind::MissingRequiredArgument, - "`--root-key` is required when `--network` is a URL.\n", - )), + // Case: URL, with or without a root key. A missing key is resolved + // when the network is accessed (fetched for a local network, error + // for remote). + (Some(NetworkTarget::Url(url)), key) => Ok(NetworkOpt::Url(url, key)), // Case: Named network (root key should be empty) (Some(NetworkTarget::Named(name)), None) => Ok(NetworkOpt::Name(name)), @@ -169,7 +166,7 @@ impl Args for NetworkOpt { impl From for NetworkSelection { fn from(v: NetworkOpt) -> Self { match v { - NetworkOpt::Url(url, RootKey(key)) => NetworkSelection::Url(url, key), + NetworkOpt::Url(url, key) => NetworkSelection::Url(url, key.map(|RootKey(k)| k)), NetworkOpt::Name(name) => NetworkSelection::Named(name), NetworkOpt::None => NetworkSelection::Default, } diff --git a/crates/icp-cli/tests/canister_call_root_key_tests.rs b/crates/icp-cli/tests/canister_call_root_key_tests.rs index 8005fa25..9ef53cdc 100644 --- a/crates/icp-cli/tests/canister_call_root_key_tests.rs +++ b/crates/icp-cli/tests/canister_call_root_key_tests.rs @@ -120,6 +120,22 @@ async fn canister_call_with_url_and_root_key() { .success() .stdout(eq("(\"Hello, world!\")").trim()); + // Test calling with url from external directory WITHOUT a root key: the + // network is local (loopback), so the root key is fetched automatically. + ctx.icp() + .args([ + "canister", + "call", + "--network", + gateway_url, + canister_id, + "greet", + "(\"world\")", + ]) + .assert() + .success() + .stdout(eq("(\"Hello, world!\")").trim()); + // Test calling with with url from external directory with bad root key ctx.icp() .args([ @@ -137,3 +153,28 @@ async fn canister_call_with_url_and_root_key() { .failure() .stderr(contains("invalid value 'badbadbad' for '--root-key")); } + +/// Connecting to a *remote* URL without a root key must fail fast with a clear +/// message, rather than silently defaulting to the mainnet key and failing +/// later with a cryptic certificate-verification error. The loopback check +/// short-circuits before any network request, so this needs no live server. +#[tokio::test] +async fn canister_call_remote_url_without_root_key_errors() { + let ctx = TestContext::new(); + + ctx.icp() + .args([ + "canister", + "call", + "--network", + "https://example.com", + "aaaaa-aa", + "greet", + "(\"world\")", + ]) + .assert() + .failure() + .stderr(contains( + "a root key is required to connect to remote network", + )); +} diff --git a/crates/icp-cli/tests/network_status_tests.rs b/crates/icp-cli/tests/network_status_tests.rs index a1281493..9ae91866 100644 --- a/crates/icp-cli/tests/network_status_tests.rs +++ b/crates/icp-cli/tests/network_status_tests.rs @@ -127,7 +127,9 @@ fn status_connected_network() { let ctx = TestContext::new(); let project_dir = ctx.create_project_dir("icp"); - // Project manifest with connected network + // Project manifest with a connected network. A remote network must specify + // its root key explicitly (only a local/loopback network has its key + // fetched automatically). write_string( &project_dir.join("icp.yaml"), r#" @@ -135,6 +137,7 @@ networks: - name: connected-network mode: connected url: https://ic0.app + root-key: 308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae "#, ) .expect("failed to write project manifest"); @@ -147,6 +150,34 @@ networks: .stdout(contains("Url: https://ic0.app")); } +/// A connected network at a remote URL must specify its root key; accessing one +/// without a key fails fast rather than silently assuming the mainnet key. +#[test] +fn status_connected_network_remote_without_root_key_errors() { + let ctx = TestContext::new(); + let project_dir = ctx.create_project_dir("icp"); + + write_string( + &project_dir.join("icp.yaml"), + r#" +networks: + - name: connected-network + mode: connected + url: https://ic0.app +"#, + ) + .expect("failed to write project manifest"); + + ctx.icp() + .current_dir(&project_dir) + .args(["network", "status", "connected-network"]) + .assert() + .failure() + .stderr(contains( + "a root key is required to connect to remote network", + )); +} + #[test] fn status_not_in_project() { let ctx = TestContext::new(); diff --git a/crates/icp/src/context/mod.rs b/crates/icp/src/context/mod.rs index 32777188..70091fb2 100644 --- a/crates/icp/src/context/mod.rs +++ b/crates/icp/src/context/mod.rs @@ -29,8 +29,9 @@ pub enum NetworkSelection { Default, /// Use a named network Named(String), - /// Use a network by URL - Url(Url, Vec), + /// Use a network by URL. The root key is optional: `None` is resolved when + /// the network is accessed (fetched for a local network, error for remote). + Url(Url, Option>), } /// Selection type for environments - similar to IdentitySelection @@ -180,7 +181,7 @@ impl Context { http_gateway_url: Some( IC_MAINNET_NETWORK_GATEWAY_URL.parse().unwrap(), ), - root_key: IC_ROOT_KEY.to_vec(), + root_key: Some(IC_ROOT_KEY.to_vec()), }, }, } @@ -197,7 +198,7 @@ impl Context { connected: crate::network::Connected { api_url: url.clone(), http_gateway_url: Some(url.clone()), - root_key: root_key.to_vec(), + root_key: root_key.clone(), }, }, }, diff --git a/crates/icp/src/lib.rs b/crates/icp/src/lib.rs index d5ad70cf..e2aa88b4 100644 --- a/crates/icp/src/lib.rs +++ b/crates/icp/src/lib.rs @@ -460,7 +460,7 @@ impl MockProjectLoader { connected: Connected { api_url: IC_MAINNET_NETWORK_API_URL.parse().unwrap(), http_gateway_url: Some(IC_MAINNET_NETWORK_GATEWAY_URL.parse().unwrap()), - root_key: IC_ROOT_KEY.to_vec(), + root_key: Some(IC_ROOT_KEY.to_vec()), }, }, }; diff --git a/crates/icp/src/network/access.rs b/crates/icp/src/network/access.rs index 6a7616db..7f864142 100644 --- a/crates/icp/src/network/access.rs +++ b/crates/icp/src/network/access.rs @@ -44,6 +44,25 @@ pub enum GetNetworkAccessError { #[snafu(display("failed to load network descriptor"))] LoadNetworkDescriptor { source: LoadNetworkFileError }, + + #[snafu(display( + "a root key is required to connect to remote network `{url}`; pass `--root-key` (or the manifest `root-key`), or use `--network ic` for mainnet" + ))] + RootKeyRequiredForRemote { url: Url }, + + #[snafu(display("failed to build agent to fetch the root key from local network `{url}`"))] + BuildRootKeyAgent { + url: Url, + #[snafu(source(from(ic_agent::AgentError, Box::new)))] + source: Box, + }, + + #[snafu(display("failed to fetch the root key from local network `{url}`"))] + FetchRootKey { + url: Url, + #[snafu(source(from(ic_agent::AgentError, Box::new)))] + source: Box, + }, } pub async fn get_managed_network_access( @@ -90,7 +109,10 @@ pub async fn get_managed_network_access( pub async fn get_connected_network_access( connected: &Connected, ) -> Result { - let root_key = connected.root_key.clone(); + let root_key = match &connected.root_key { + Some(rk) => rk.clone(), + None => resolve_root_key(&connected.api_url).await?, + }; Ok(NetworkAccess { root_key, @@ -99,3 +121,41 @@ pub async fn get_connected_network_access( use_friendly_domains: false, }) } + +/// Whether `url`'s host is a loopback address (or `localhost`). +fn is_loopback(url: &Url) -> bool { + match url.host() { + Some(url::Host::Domain(d)) => d.eq_ignore_ascii_case("localhost"), + Some(url::Host::Ipv4(ip)) => ip.is_loopback(), + Some(url::Host::Ipv6(ip)) => ip.is_loopback(), + None => false, + } +} + +/// Resolve the root key for a connected network whose key was omitted. +/// +/// For a local (loopback) network the key is fetched from the network's status +/// endpoint — safe because there is no meaningful man-in-the-middle surface on +/// loopback. For a remote network we refuse rather than silently defaulting to +/// the mainnet key: a genuine remote network never shares mainnet's key, so +/// defaulting would only defer the failure to the first certified call with a +/// cryptic verification error. +async fn resolve_root_key(api_url: &Url) -> Result, GetNetworkAccessError> { + if !is_loopback(api_url) { + return RootKeyRequiredForRemoteSnafu { + url: api_url.clone(), + } + .fail(); + } + + let agent = ic_agent::Agent::builder() + .with_url(api_url.as_str()) + .build() + .context(BuildRootKeyAgentSnafu { + url: api_url.clone(), + })?; + agent.fetch_root_key().await.context(FetchRootKeySnafu { + url: api_url.clone(), + })?; + Ok(agent.read_root_key()) +} diff --git a/crates/icp/src/network/mod.rs b/crates/icp/src/network/mod.rs index 6ac94746..c0908934 100644 --- a/crates/icp/src/network/mod.rs +++ b/crates/icp/src/network/mod.rs @@ -163,6 +163,25 @@ pub struct ManagedImageConfig { pub extra_hosts: Vec, } +/// Serde helper for an optional hex-encoded byte string (`hex::serde` does not +/// cover `Option`). +mod hex_opt { + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(v: &Option>, s: S) -> Result { + match v { + Some(bytes) => s.serialize_some(&hex::encode(bytes)), + None => s.serialize_none(), + } + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result>, D::Error> { + Option::::deserialize(d)? + .map(|s| hex::decode(s).map_err(serde::de::Error::custom)) + .transpose() + } +} + #[derive(Clone, Debug, PartialEq, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct Connected { @@ -172,10 +191,12 @@ pub struct Connected { /// The URL this network's HTTP gateway can be reached at. pub http_gateway_url: Option, - /// The root key of this network - #[serde(with = "hex::serde")] - #[schemars(with = "String")] - pub root_key: Vec, + /// The root key of this network. `None` means the key is resolved when the + /// network is accessed: fetched from a local (loopback) network, or an + /// error for a remote network. + #[serde(with = "hex_opt", default, skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] + pub root_key: Option>, } #[derive(Clone, Debug, Deserialize, PartialEq, JsonSchema, Serialize)] @@ -232,9 +253,9 @@ impl From for Gateway { impl From for Connected { fn from(value: ManifestConnected) -> Self { - let root_key = value - .root_key - .map_or_else(|| crate::context::IC_ROOT_KEY.to_vec(), |rk| rk.0); + // Absent keys are resolved at access time; we do not default to the + // mainnet key here. + let root_key = value.root_key.map(|rk| rk.0); match value.endpoints { Endpoints::Implicit { url } => Connected { api_url: url.clone(), diff --git a/crates/icp/src/project.rs b/crates/icp/src/project.rs index 3b8bcf6b..181cb4a0 100644 --- a/crates/icp/src/project.rs +++ b/crates/icp/src/project.rs @@ -349,7 +349,7 @@ pub async fn consolidate_manifest( connected: Connected { api_url: IC_MAINNET_NETWORK_API_URL.parse().unwrap(), http_gateway_url: Some(IC_MAINNET_NETWORK_GATEWAY_URL.parse().unwrap()), - root_key: IC_ROOT_KEY.to_vec(), + root_key: Some(IC_ROOT_KEY.to_vec()), }, }, }, diff --git a/docs/reference/cli.md b/docs/reference/cli.md index ce3dfab6..d0291efb 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -163,7 +163,7 @@ Make a canister call ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--args-file ` — Path to a file containing call arguments @@ -239,7 +239,7 @@ Examples: ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--controller ` — One or more controllers for the canister. Repeat `--controller` to specify multiple @@ -275,7 +275,7 @@ Cycles will be sent to the caller via the cycles ledger. This is done by install ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--proxy ` — Principal of a proxy canister to route the management canister call through @@ -332,7 +332,7 @@ Install a built WASM to a canister on a network * `-y`, `--yes` — Skip confirmation prompts, including the Candid interface compatibility check and the dangerous-operation prompt for `--wasm-memory-persistence replace` * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--proxy ` — Principal of a proxy canister to route the management canister call through @@ -367,7 +367,7 @@ Fetch and display canister logs ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `-f`, `--follow` — Continuously fetch and display new logs until interrupted with Ctrl+C @@ -396,7 +396,7 @@ Read a metadata section from a canister ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--json` — Output command results as JSON @@ -416,7 +416,7 @@ Migrate a canister ID from one subnet to another ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--replace ` — The canister to replace with the source canister's ID @@ -456,7 +456,7 @@ Queries the canister_status endpoint of the management canister and displays onl ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--json` — Format output as JSON @@ -477,7 +477,7 @@ Change a canister's settings to specified values ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `-f`, `--force` — Force the operation without confirmation prompts @@ -518,7 +518,7 @@ Synchronize a canister's settings with those defined in the project ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--proxy ` — Principal of a proxy canister to route the management canister calls through @@ -555,7 +555,7 @@ Create a snapshot of a canister's state ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--replace ` — Replace an existing snapshot instead of creating a new one. The old snapshot will be deleted once the new one is successfully created @@ -579,7 +579,7 @@ Delete a canister snapshot ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--proxy ` — Principal of a proxy canister to route the management canister call through @@ -600,7 +600,7 @@ Download a snapshot to local disk ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `-o`, `--output ` — Output directory for the snapshot files @@ -622,7 +622,7 @@ List all snapshots for a canister ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--json` — Output command results as JSON @@ -645,7 +645,7 @@ Restore a canister from a snapshot ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--proxy ` — Principal of a proxy canister to route the management canister calls through @@ -665,7 +665,7 @@ Upload a snapshot from local disk ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `-i`, `--input ` — Input directory containing the snapshot files @@ -690,7 +690,7 @@ Start a canister on a network ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--proxy ` — Principal of a proxy canister to route the management canister call through @@ -727,7 +727,7 @@ Examples: ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `-i`, `--id-only` — Only print the canister ids @@ -750,7 +750,7 @@ Stop a canister on a network ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--proxy ` — Principal of a proxy canister to route the management canister call through @@ -771,7 +771,7 @@ Top up a canister with cycles * `--amount ` — Amount of cycles to top up. Supports suffixes: k (thousand), m (million), b (billion), t (trillion) * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as @@ -800,7 +800,7 @@ Display the cycles balance ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--subaccount ` — The subaccount to check the balance for @@ -825,7 +825,7 @@ Exactly one of --icp or --cycles must be provided. * `--from-subaccount ` — Subaccount to withdraw the ICP from * `--to-subaccount ` — Subaccount to deposit the cycles to * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--json` — Output command results as JSON @@ -848,7 +848,7 @@ Transfer cycles to another principal * `--to-subaccount ` — The subaccount to transfer to (only if the receiver is a principal) * `--from-subaccount ` — The subaccount to transfer cycles from * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--json` — Output command results as JSON @@ -1668,7 +1668,7 @@ Display the token balance on the ledger (default token: icp) ###### **Options:** * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--subaccount ` — The subaccount to check the balance for @@ -1694,7 +1694,7 @@ Transfer ICP or ICRC1 tokens through their ledger (default token: icp) * `--to-subaccount ` — The subaccount to transfer to (only if the receiver is a principal) * `--from-subaccount ` — The subaccount to transfer from * `-n`, `--network ` — Name or URL of the network to target, conflicts with environment argument -* `-k`, `--root-key ` — The root key to use if connecting to a network by URL. Required when using `--network ` +* `-k`, `--root-key ` — The root key to use when connecting to a network by URL. Optional: for a local (loopback) URL it is fetched from the network; for a remote URL it is required * `-e`, `--environment ` — Override the environment to connect to. By default, the local environment is used * `--identity ` — The user identity to run this command as * `--json` — Output command results as JSON