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

  1. Retry the request once after a short delay; transient provider glitches often clear.
  2. Ask the user to restart the browser/extension — a wedged content script returns empty responses.
  3. 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

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


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