linera-io/linera-protocol · error

default chain requested but none set

Error message

default chain requested but none set

What it means

linera-client's ClientContext stores the default chain as an Option<ChainId> and default_chain() (linera-client/src/client_context.rs:395) unwraps it unconditionally. The panic fires when the context was built without a default chain - typically a wallet that has no user chain selected, such as a fresh wallet or one holding only the admin chain. Every API that needs an implicit chain (default_account, ownership, change_ownership, set_preferred_owner, the benchmark helpers, supply_fungible_tokens) routes through this accessor, so any of them can trigger it.

Source

Thrown at linera-client/src/client_context.rs:397

    pub fn wallet(&self) -> &Env::Wallet {
        self.client.wallet()
    }

    /// Returns the ID of the admin chain.
    pub fn admin_chain_id(&self) -> ChainId {
        self.client.admin_chain_id()
    }

    /// Retrieve the default account. Current this is the common account of the default
    /// chain.
    pub fn default_account(&self) -> Account {
        Account::chain(self.default_chain())
    }

    /// Retrieve the default chain.
    pub fn default_chain(&self) -> ChainId {
        self.default_chain
            .expect("default chain requested but none set")
    }

    /// Returns the lowest non-admin chain ID in the wallet.
    pub async fn first_non_admin_chain(&self) -> Result<ChainId, Error> {
        let admin_chain_id = self.admin_chain_id();
        let chain_ids = self
            .wallet()
            .chain_ids()
            .try_filter(|chain_id| futures::future::ready(*chain_id != admin_chain_id))
            .try_collect::<Vec<ChainId>>()
            .await
            .map_err(Error::wallet)?;
        Ok(chain_ids
            .into_iter()
            .min()
            .expect("No non-admin chain specified in wallet with no non-admin chain"))
    }

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Claim or create a user chain (e.g. via the faucet) so the wallet holds a non-admin chain, then set it as the default chain in the wallet.
  2. Re-run the command with an explicit --chain-id so default_chain() is never consulted.
  3. Inspect the wallet (linera wallet show) and assign a default chain pointing at an existing chain entry.
  4. If embedding linera-client, populate the default chain from wallet.chain_ids() before calling any default-chain-dependent API.

Example fix

// before
let account = context.default_account(); // panics: "default chain requested but none set"

// after
// Option<ChainId> field on the context options; check it first
match context_options.default_chain {
    Some(chain_id) => Account::chain(chain_id),
    None => return Err(anyhow::anyhow!("no default chain in wallet; pass --chain-id or claim a chain first")),
}
Defensive patterns

Strategy: validation

Validate before calling

// Before any default-chain-dependent call, verify the wallet has a usable chain:
let chain_ids: Vec<ChainId> = wallet.chain_ids().try_collect().await?;
let has_user_chain = chain_ids.iter().any(|id| *id != client_context.admin_chain_id());
if !has_user_chain {
    anyhow::bail!("wallet holds no non-admin chain; claim one from the faucet or pass --chain-id");
}

Type guard

fn has_default_chain(ctx: &ClientContext) -> bool {
    // default_chain is Option<ChainId> on the context/options; treat None as unsafe to call
    ctx_default_chain_option(ctx).is_some()
}

fn ctx_default_chain_option(ctx: &ClientContext) -> Option<ChainId> {
    // if the field is not directly accessible, mirror it via wallet state:
    // Some(chain) only when the wallet marks a chain as default/preferred.
    ctx.default_chain
}

Try / catch

// Panics are not Result-based; when embedding, isolate with catch_unwind at the boundary:
let result = std::panic::catch_unwind(AssertUnwindSafe(|| context.default_account()));
match result {
    Ok(account) => { /* proceed */ }
    Err(_) => return Err(anyhow::anyhow!("no default chain configured in wallet")),
}

Prevention

When it happens

Trigger: Calling default_account(), ownership(), change_ownership(), set_preferred_owner(), or the benchmark preparation flow while wallet/config left default_chain as None. Concretely: running a wallet-dependent CLI command against a wallet whose only entry is the admin chain, or constructing ClientContext programmatically with default_chain: None (as the unit-test stub in linera-client/src/unit_tests/client_context.rs does).

Common situations: Fresh wallet created but no user chain claimed from the faucet yet; a wallet whose default chain entry was removed or lost after migration; CLI wallet generation that omitted a default chain assignment; embedding linera-client and forgetting to derive the default chain from wallet.chain_ids() before use.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/f5b327a2fa0e7019. Report an issue: GitHub.