linera-io/linera-protocol · error · Error
MetaMask is not available
Error message
MetaMask is not available
What it means
The ChainClient guards concurrent proposals with a PendingProposal. If you try to build/propose a new block while a previous proposal is still pending (sent but not yet confirmed by a quorum), prepare... returns Error::BlockProposalError with this message. The pending block must be resolved first — either confirmed via certificates from validators, or explicitly retried — because two in-flight proposals for the same height cannot both succeed.
Source
Thrown at web/@linera/metamask/src/signer.ts:34
* and interacts with it using EIP-191-compliant requests. It provides a secure
* mechanism for message signing through the user's MetaMask wallet.
*
* ⚠️ WARNING: This signer requires MetaMask to be installed and unlocked in the browser.
* It will throw errors if MetaMask is unavailable, the user rejects a request, or
* if the requested signer is not among the connected accounts.
*
* The `MetaMask` signer verifies that the connected account matches the specified
* owner address before signing a message. All messages are encoded as hexadecimal
* strings and signed using the `personal_sign` method.
*
* Suitable for production use where MetaMask is the expected signer interface.
*/
export default class Signer implements SignerInterface {
private provider: ethers.BrowserProvider;
constructor() {
if (typeof window === "undefined" || !window.ethereum) {
throw new Error("MetaMask is not available");
}
this.provider = new ethers.BrowserProvider(window.ethereum!);
}
async sign(owner: string, value: Uint8Array): Promise<string> {
if (!window.ethereum) {
throw new Error("MetaMask is not available");
}
// Explicitly type the result and check for undefined
const accounts = (await window.ethereum.request({
method: "eth_requestAccounts",
})) as string[] | undefined;
if (!accounts || accounts.length === 0) {
throw new Error("No MetaMask accounts connected");
}
View on GitHub (pinned to 6c226ddcb3)
Solutions
- Run `linera retry-pending-block [CHAIN_ID]` (or client retry_pending_block / the node-service retry_pending_block endpoint) to commit or discard the outstanding proposal first.
- If the pending block is obsolete, explicitly drop it and then propose the new block in a fresh call.
- Serialize proposals per chain in your application (one in-flight proposal at a time) so the guard never trips.
- After retrying, verify the block got confirmed by checking next_block_height advanced before issuing new operations.
Example fix
# before: issuing a new operation while a proposal is pending linera transfer --amount 5 ... # BlockProposalError: already has a pending block # after: settle the pending block first linera retry-pending-block <CHAIN_ID> linera transfer --amount 5 ...
Defensive patterns
Strategy: retry
Validate before calling
// Application-level guard: serialize proposals per chain
if proposal_in_flight.contains(&chain_id) {
return Err(anyhow::anyhow!("a proposal is already pending for {chain_id}; finish it first"));
} Try / catch
match client.prepare_block(operations).await {
Ok(block) => block,
Err(Error::BlockProposalError(msg)) if msg.contains("pending block") => {
client.retry_pending_block().await?; // or node-service retry_pending_block
client.prepare_block(operations).await?
}
Err(e) => return Err(e),
} Prevention
- Keep at most one in-flight block proposal per chain (per-process mutex or cross-process lock).
- Run `linera retry-pending-block` after any failed or interrupted submit before issuing new operations.
- On restart, check for pending proposals as part of client initialization.
- Treat BlockProposalError with this message as recoverable, not fatal: finish the old block, then continue.
When it happens
Trigger: Calling the block-preparation entry point (which takes proposal_guard: &mut Option<PendingProposal>) twice without the first proposal being finalized: e.g. propose a block, the client crashes or the network stalls before confirmation, then call the same flow again. The guard ensure!(proposal_guard.is_none()) fires.
Common situations: Process restart after a failed submit; validator timeouts leaving the block unconfirmed; automation loops that re-issue 'transfer' commands while a previous one is in flight; the JSON-RPC service receiving two concurrent requests for the same chain.
Related errors
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/dc301d0c189ae243.
Report an issue: GitHub.