linera-io/linera-protocol · error · Error
No signature returned
Error message
No signature returned
What it means
sign() wraps personal_sign and treats a falsy return (undefined/null/empty string) as a protocol violation: the wallet acknowledged the request but produced no signature. Well-behaved wallets either return a 65-byte hex signature or reject, so this indicates a broken or non-conforming provider response rather than a user action.
Source
Thrown at web/@linera/metamask/src/signer.ts:71
const connected = accounts.find(
(acc) => acc.toLowerCase() === owner.toLowerCase(),
);
if (!connected) {
throw new Error(
`MetaMask is not connected with the requested owner: ${owner}`,
);
}
// Encode message as hex string
const msgHex = `0x${uint8ArrayToHex(value)}`;
try {
const signature = (await window.ethereum.request({
method: "personal_sign",
params: [msgHex, owner],
})) as string;
if (!signature) {
throw new Error("No signature returned");
}
return signature;
} catch (err: any) {
throw new Error(
`MetaMask signature request failed: ${err?.message || err}`,
);
}
}
async getPublicKey(_owner: string): Promise<string> {
// MetaMask signs only for `Address20` (EVM secp256k1) owners. The wasm bridge
// never calls `getPublicKey` on the Address20 path — EVM signatures carry the
// signer's address inline in `AccountSignature::EvmSecp256k1`. If we get here,
// a caller is reaching past the bridge contract.
throw new Error(
"MetaMask signer does not expose a public key; EVM signatures carry the address",
);View on GitHub (pinned to 6c226ddcb3)
Solutions
- Retry the request once after a short delay; transient provider glitches often clear.
- Ask the user to restart the browser/extension — a wedged content script returns empty responses.
- In tests, make sure the personal_sign mock returns a hex string.
Example fix
// before
const sig = await signer.sign(owner, bytes); // 'No signature returned'
// after
let sig: string;
try {
sig = await signer.sign(owner, bytes);
} catch (e) {
if (e instanceof Error && e.message === 'No signature returned') { /* retry once */ }
throw e;
} Defensive patterns
Strategy: retry
Try / catch
try { sig = await signer.sign(owner, bytes); }
catch (e) {
if (e instanceof Error && e.message === 'No signature returned') { sig = await signer.sign(owner, bytes); /* one retry */ }
else throw e;
} Prevention
- In test harnesses, mock personal_sign to return a valid hex signature.
- Ask users to restart the browser/extension when responses come back empty.
- Log the raw provider response when this fires to identify non-conforming wallets.
When it happens
Trigger: personal_sign resolves with undefined/null — stubbed/mocked window.ethereum in tests returning nothing, a wallet extension bug after update, or an exotic wallet that silently swallows the prompt.
Common situations: Test harnesses that mock eth_requestAccounts but forget to mock personal_sign; wallet extensions in a broken state after an update (restart fixes it); privacy/scanner extensions interfering with the provider's RPC responses.
Related errors
- MetaMask signature request failed: ${err?.message || err}
- MetaMask signer does not expose a public key; EVM signatures
- transaction receipt not found for {tx_hash}
- receipt missing block_hash (pending tx?)
- receipt missing transaction_index
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/cb823492b0834a43.
Report an issue: GitHub.