{"record":{"id":"047e50762958f980","repo":"linera-io/linera-protocol","slug":"unauthenticatedtransferowner","errorCode":"UnauthenticatedTransferOwner","errorMessage":"ExecutionError::UnauthenticatedTransferOwner","messagePattern":"ExecutionError::UnauthenticatedTransferOwner","errorType":"exception","errorClass":"ExecutionError","httpStatus":null,"severity":"error","filePath":"linera-execution/src/system.rs","lineNumber":740,"sourceCode":"            Ok(Some(\n                OutgoingMessage::new(recipient.chain_id, message).with_kind(MessageKind::Tracked),\n            ))\n        }\n    }\n\n    /// Transfers `amount` from `source` to `recipient`, debiting the source account.\n    pub async fn transfer(\n        &mut self,\n        authenticated_owner: Option<AccountOwner>,\n        authenticated_application_id: Option<ApplicationId>,\n        source: AccountOwner,\n        recipient: Account,\n        amount: Amount,\n    ) -> Result<Option<OutgoingMessage>, ExecutionError> {\n        if source == AccountOwner::CHAIN {\n            let authenticated_owner =\n                authenticated_owner.ok_or(ExecutionError::UnauthenticatedTransferOwner)?;\n            ensure!(\n                self.ownership.get().await?.is_owner(&authenticated_owner),\n                ExecutionError::UnauthenticatedTransferOwner\n            );\n        } else {\n            ensure!(\n                authenticated_owner == Some(source)\n                    || authenticated_application_id.map(AccountOwner::from) == Some(source),\n                ExecutionError::UnauthenticatedTransferOwner\n            );\n        }\n        ensure!(\n            amount > Amount::ZERO,\n            ExecutionError::IncorrectTransferAmount\n        );\n        self.debit(&source, amount).await?;\n        self.credit_or_send_message(source, recipient, amount).await\n    }\n","sourceCodeStart":722,"sourceCodeEnd":758,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-execution/src/system.rs#L722-L758","documentation":"A Transfer that spends the chain's own main balance (source == AccountOwner::CHAIN) must be authenticated by a signature. This particular throw fires when the operation carries no authenticated owner at all (authenticated_owner is None), meaning the block or transaction was submitted unsigned. The signer is subsequently also required to be an owner of the chain, which is the companion check at the next line.","triggerScenarios":"Submitting SystemOperation::Transfer { owner: AccountOwner::CHAIN, amount, recipient } in an unauthenticated block or transaction (no signature attached), or an application calling ContractRuntime::transfer(AccountOwner::CHAIN, recipient, amount) from an operation context whose authenticated_signer is None.","commonSituations":"A client builds the transfer but never attaches a signing key, or the wallet's default key is not registered as an owner/super-owner in the chain description; attempting a state change through an unauthenticated GraphQL query or read-only path; an SDK application assumes the operation is signed but it was routed through an unauthenticated entry point.","solutions":["Sign the block containing the Transfer with a key that is a super-owner or owner of the chain, so authenticated_owner is Some(owner) and ownership.is_owner(owner) holds.","Inspect the chain's ownership configuration (the ChainDescription's owners/super-owner) and either register the intended key or switch to a key already listed.","If the goal was to move a user's funds rather than the chain balance, set the transfer's owner to that user's AccountOwner and have them sign the operation."],"exampleFix":"// before: app moves the chain balance; operation arrived with no signer\nruntime.transfer(AccountOwner::CHAIN, recipient, amount);\n\n// after: require a signer up front, or spend the signed owner's own account\nlet Some(signer) = context.authenticated_signer() else {\n    return Err(Error::MissingSigner); // fail fast with a clear message\n};\nruntime.transfer(signer.into(), recipient, amount);","handlingStrategy":"validation","validationCode":"// Chain-balance transfers need a signer that is a chain owner\nif source == AccountOwner::CHAIN {\n    ensure!(\n        authenticated_owner.is_some_and(|owner| ownership.is_owner(&owner)),\n        \"sign the block with a chain owner key before transferring from the chain balance\"\n    );\n}","typeGuard":"fn is_unauthenticated_transfer(e: &ExecutionError) -> bool {\n    matches!(e, ExecutionError::UnauthenticatedTransferOwner)\n}","tryCatchPattern":"match result {\n    Err(ExecutionError::UnauthenticatedTransferOwner) => {\n        // The block had no authenticated owner: rebuild it with the\n        // chain owner's signature attached and resubmit.\n    }\n    Err(e) => return Err(e.into()),\n    Ok(value) => { /* ... */ }\n}","preventionTips":["Make signing part of the transfer submission path — never build a chain-balance Transfer without attaching a key.","Verify the signing key is listed in the chain's ownership (super-owner or owners) before submission.","In applications, check context.authenticated_signer() early and reject unsigned operations with a clear message."],"tags":["linera","transfer","authentication","signature","chain-balance"],"backgroundTag":"unauthorized-transfer-signer","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}