You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Per Section 10 of the Source of Truth, the FishnetWallet is a minimal Solidity contract with a single execute function gated by an EIP712 permit signature. The current contract (contracts/src/FishnetWallet.sol) is an empty 4-line skeleton. This issue covers the core contract logic.
1. FishnetPermit Struct (Section 4.3)
struct FishnetPermit {
address wallet; // smart wallet addressuint64 chainId; // chain the tx executes onuint256 nonce; // replay protection (incremental)uint48 expiry; // unix timestamp, short-lived (~5 min)address target; // contract being calleduint256 value; // ETH/native value sentbytes32 calldataHash; // keccak256(calldata)bytes32 policyHash; // hash of active policy version
}
2. State Variables
address public owner — wallet owner (deployer)
address public fishnetSigner — authorized Fishnet signer address
mapping(uint256 => bool) public usedNonces — replay protection
[SC-1] Implement FishnetWallet Core Contract (execute + EIP712 permit verification)
Labels:
smart-contract,solidity,priority:high,week-3-4Assignee: Yash
Context
Per Section 10 of the Source of Truth, the FishnetWallet is a minimal Solidity contract with a single
executefunction gated by an EIP712 permit signature. The current contract (contracts/src/FishnetWallet.sol) is an empty 4-line skeleton. This issue covers the core contract logic.1. FishnetPermit Struct (Section 4.3)
2. State Variables
address public owner— wallet owner (deployer)address public fishnetSigner— authorized Fishnet signer addressmapping(uint256 => bool) public usedNonces— replay protectionbool public paused— emergency pause flag3. EIP712 Domain & Typehash
name = "Fishnet"version = "1"chainId = block.chainidverifyingContract = address(this)DOMAIN_SEPARATOR— computed in constructor, cached4.
execute()FunctionThe core function — gated by permit signature:
require(block.timestamp <= permit.expiry, "permit expired")require(!usedNonces[permit.nonce], "nonce already used")require(permit.target == target, "target mismatch")require(permit.calldataHash == keccak256(data), "calldata mismatch")require(permit.wallet == address(this), "wallet mismatch")require(_verifySignature(permit, signature), "invalid signature")usedNonces[permit.nonce] = true(bool success, ) = target.call{value: value}(data)require(success, "execution failed")ActionExecuted(target, value, permit.nonce, permit.policyHash)5.
_verifySignature()Internal Functionkeccak256(abi.encode(PERMIT_TYPEHASH, permit.wallet, permit.chainId, permit.nonce, permit.expiry, permit.target, permit.value, permit.calldataHash, permit.policyHash))keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash))ecrecover(digest, v, r, s)(unpack signature bytes)recoveredSigner == fishnetSigner6. Owner Functions
setSigner(address _signer) external onlyOwner— rotate Fishnet signer, emitSignerUpdatedwithdraw(address to) external onlyOwner— withdraw all ETH from walletpause() external onlyOwner— setpaused = true, emitPausedunpause() external onlyOwner— setpaused = false, emitUnpausedreceive() external payable— accept ETH deposits7. Modifiers
onlyOwner—require(msg.sender == owner, "not owner")whenNotPaused—require(!paused, "wallet paused")8. Events
event ActionExecuted(address indexed target, uint256 value, uint256 nonce, bytes32 policyHash)event SignerUpdated(address indexed oldSigner, address indexed newSigner)event Paused(address account)event Unpaused(address account)Acceptance Criteria
forge build(Solidity ^0.8.19)execute()only succeeds with a valid EIP712 permit signed byfishnetSigner