datahaven-xyz/datahaven · error
Invalid permit
Error message
Invalid permit
What it means
The call-permit precompile's `dispatch` reverts with "Invalid permit" when ECDSA recovery fails or, more commonly, when the recovered signer does not match the claimed `from` address (or is the zero address). The permit signature must recover to exactly the account authorizing the call; any mismatch means the signature is malformed, signed with the wrong key, or covers different data.
Solutions
- Re-sign the permit with the exact same fields the precompile hashes (address, from, call data hash, nonce from NoncesStorage, deadline, chain id) using the key controlling `from`.
- Ensure the `from` argument equals the signer address derived from the signature.
- Refresh the nonce: read NoncesStorage (the precompile's nonce query) before signing, since consumed nonces invalidate old signatures.
- Verify EIP-712 domain (name, version, chainId, verifyingContract) matches the precompile's expected domain.
Example fix
// before: signed with mismatched fields
let sig = sign_eip712(old_domain, stale_nonce, ...);
call_permit.dispatch(call, sig)?; // revert("Invalid permit")
// after: sign current nonce with matching domain
let nonce = call_permit.nonces(from);
let sig = sign_eip712(precompile_domain, nonce, deadline, call);
assert!(recover(sig) == from);
call_permit.dispatch(call, sig)?; Defensive patterns
Strategy: try-catch
Validate before calling
const recovered = recoverAddress(permitHash, sig);
if (recovered.toLowerCase() !== from.toLowerCase()) throw new Error('signer mismatch'); Type guard
function isPermitSignatureValid(sig, from) { try { return recoverAddress(sig).toLowerCase() === from.toLowerCase(); } catch { return false; } } Try / catch
try { await callPermit.dispatch(call, sig); } catch (e) { if (e.message.includes('Invalid permit')) { const nonce = await callPermit.nonces(from); const fresh = signPermitEip712(domain, { from, nonce, deadline, call }); return callPermit.dispatch(call, fresh); } throw e; } Prevention
- Fetch the current nonce immediately before signing
- Match EIP-712 domain (name, version, chainId, verifyingContract) exactly
- Sign with the key controlling `from`
- Keep client permit struct field order identical to the precompile's hashing order
When it happens
Trigger: Calling `dispatch` with: (1) a signature that fails `secp256k1_ecdsa_recover` (corrupt/truncated sig, wrong v), or (2) a recovered signer != `from` or signer == H160::zero() — e.g. the permit hash was built with fields differing from those used at signing time (nonce, deadline, call data, chain id, contract address).
Common situations: Client and precompile hashing different permit structs (field order/types mismatch); signer uses a different key than the `from` address; nonce already consumed so the signed nonce no longer matches `NoncesStorage`; EIP-155 v-value handling bugs; chain id or verifying-contract address mismatch when signing EIP-712.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13).
Data as JSON: /api/errors/007cc301b841ab1e.
Report an issue: GitHub.
Appendix: source
Thrown at operator/precompiles/call-permit/src/lib.rs:205
from,
to,
value,
data.clone(),
gas_limit,
nonce,
deadline,
);
let mut sig = [0u8; 65];
sig[0..32].copy_from_slice(&r.as_bytes());
sig[32..64].copy_from_slice(&s.as_bytes());
sig[64] = v;
let signer = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &permit)
.map_err(|_| revert("Invalid permit"))?;
let signer = H160::from(H256::from_slice(keccak_256(&signer).as_slice()));
ensure!(
signer != H160::zero() && signer == from,
revert("Invalid permit")
);
NoncesStorage::insert(from, nonce + U256::one());
// DISPATCH CALL
let sub_context = Context {
caller: from,
address: to.clone(),
apparent_value: value,
};
let transfer = if value.is_zero() {
None
} else {
Some(Transfer {
source: from,View on GitHub (pinned to edcb13dbbc)