linera-io/linera-protocol · error
keypair not found for chain `{chain_id}`
Error message
keypair not found for chain `{chain_id}` What it means
In forget_keys, mutate found the chain entry but the closure chain.owner.take() returned None — the chain exists in the wallet yet has no owner/keypair attached (already forgotten, or a chain tracked watch-only). This inner Option is converted into the 'keypair not found' anyhow error.
Source
Thrown at linera-wallet-json/src/wallet.rs:200
/// Applies a mutation to the chain with the given ID, saving afterwards. Returns `None`
/// if the chain is not in the wallet.
pub fn mutate<R>(
&self,
chain_id: ChainId,
mutate: impl Fn(&mut Chain) -> R,
) -> Option<Result<R, persistent::file::Error>> {
self.0
.chains
.mutate(chain_id, mutate)
.map(|outcome| self.0.save().map(|()| outcome))
}
/// Removes and returns the owner of the given chain, erroring if the chain or owner is absent.
pub fn forget_keys(&self, chain_id: ChainId) -> anyhow::Result<AccountOwner> {
self.mutate(chain_id, |chain| chain.owner.take())
.ok_or_else(|| anyhow::anyhow!("nonexistent chain `{chain_id}`"))??
.ok_or_else(|| anyhow::anyhow!("keypair not found for chain `{chain_id}`"))
}
/// Writes the wallet to its file.
pub fn save(&self) -> Result<(), persistent::file::Error> {
self.0.save()
}
/// Returns the number of chains in the wallet.
pub fn num_chains(&self) -> usize {
self.0.chains.items().len()
}
/// Returns the IDs of all chains in the wallet.
pub fn chain_ids(&self) -> Vec<ChainId> {
self.0.chains.chain_ids()
}
/// Returns the list of all chain IDs for which we have a secret key.View on GitHub (pinned to 6c226ddcb3)
Solutions
- Treat it as already-done if your workflow is idempotent: match the error and continue when the keys were previously forgotten
- Inspect wallet chain state first: check whether owned_chain_ids() contains the chain before calling forget_keys
- Do not retry blindly — the wallet file was already saved without the owner on the first successful call
Example fix
// before
let owner = wallet.forget_keys(chain_id)?; // second call -> error
// after (idempotent helper)
let owner = match wallet.forget_keys(chain_id) {
Ok(owner) => owner,
Err(e) if e.to_string().contains("keypair not found") => {
return Ok(None); // keys were already forgotten
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
// Only attempt to forget keys we actually own
if wallet.owned_chain_ids().contains(&chain_id) {
wallet.forget_keys(chain_id)?;
} else {
tracing::warn!(%chain_id, "no keys held for chain; skipping");
} Try / catch
let owner = match wallet.forget_keys(chain_id) {
Ok(owner) => Some(owner),
Err(e) if e.to_string().contains("keypair not found") => None, // already forgotten
Err(e) => return Err(e),
}; Prevention
- Make forget workflows idempotent: match the 'keypair not found' message and treat as success
- Do not retry the operation on error — the wallet may already be saved without the owner
- Guard multi-step scripts with owned_chain_ids() membership checks
When it happens
Trigger: Calling forget_keys twice on the same chain; forgetting keys of a chain the wallet tracks without owning (e.g. a read-only/multi-owner chain where this wallet never held the keypair).
Common situations: Idempotency-unaware scripts that retry a forget operation after a timeout; wallet state changed by another process between listing and forgetting; user assuming 'forget' resets the chain rather than removing key material.
Related errors
- nonexistent chain `{chain_id}`
- Default wallet directory is not supported in this platform:
- Wallet already exists: {}
- ClosedChain
- CannotRejectMessage
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/d3763bb6aca4af6f.
Report an issue: GitHub.