Contract Overview
TheFishnetWallet contract is a permit-gated smart wallet that executes on-chain actions authorized by EIP-712 signed permits.
Source: contracts/src/FishnetWallet.sol
License: MIT
Solidity Version: ^0.8.19
State Variables
Public State
Wallet owner. Can update signer, pause/unpause, and withdraw funds.
Pause status. When
true, all execute() calls revert.Address authorized to sign permits. Signatures are verified against this address.
Tracks used nonces to prevent replay attacks. Each nonce can only be used once.
Immutable State
Cached EIP-712 domain separator for the original chain ID.
Original chain ID at deployment. Used to detect forks.
Constants
FishnetWallet.sol:29-33
uint64 chainId and uint48 expiry instead of uint256.
Structs
FishnetPermit
FishnetWallet.sol:35-44
Must match
address(this). Prevents permit reuse across wallets.Must match
block.chainid. Prevents cross-chain replay attacks.Unique identifier. Can be any unused value (not necessarily sequential).
UNIX timestamp. Permit reverts if
block.timestamp > expiry.The contract that will receive the call. Must match the
target parameter in execute().Amount of ETH to send. Must match the
value parameter in execute().keccak256(data) where data is the calldata. Must match keccak256(data) in execute().Identifier for the policy that approved this action. Emitted in events for audit trails.
Constructor
FishnetWallet.sol:62-69
Address of the backend signer. Reverts if
address(0).- Sets
msg.senderas owner - Caches the domain separator for the deployment chain
Core Functions
execute
FishnetWallet.sol:90-112
Contract to call. Must match
permit.target.ETH to send (in wei). Must match
permit.value.Calldata to pass to
target. keccak256(data) must match permit.calldataHash.The signed permit structure.
65-byte ECDSA signature in
r || s || v format.block.timestamp <= permit.expiry→ revertsPermitExpired()permit.chainId == block.chainid→ revertsWrongChain()!usedNonces[permit.nonce]→ revertsNonceUsed()permit.target == target→ revertsTargetMismatch()permit.value == value→ revertsValueMismatch()permit.calldataHash == keccak256(data)→ revertsCalldataMismatch()permit.wallet == address(this)→ revertsWalletMismatch()_verifySignature(permit, signature)→ revertsInvalidSignature()
- Marks nonce as used:
usedNonces[permit.nonce] = true - Calls
target.call{value: value}(data) - Emits
ActionExecuted(target, value, permit.nonce, permit.policyHash)
WalletPaused()if pausedExecutionFailed()if the target call fails
setSigner
FishnetWallet.sol:152-157
New signer address. Reverts if
address(0).owner.
Effects: Emits SignerUpdated(oldSigner, newSigner).
withdraw
FishnetWallet.sol:159-164
Recipient address.
owner.
Effects: Transfers entire balance and emits Withdrawn(to, amount).
pause
FishnetWallet.sol:166-169
execute() calls.
Restrictions: Only callable by owner.
Effects: Sets paused = true and emits Paused(account).
unpause
FishnetWallet.sol:171-174
owner.
Effects: Sets paused = false and emits Unpaused(account).
View Functions
DOMAIN_SEPARATOR
FishnetWallet.sol:71-76
bytes32 domain separator.
Events
ActionExecuted
FishnetWallet.sol:46
The contract that was called.
Amount of ETH sent.
Nonce of the executed permit.
Policy that authorized this action.
SignerUpdated
FishnetWallet.sol:47
Paused / Unpaused
FishnetWallet.sol:48-49
Withdrawn
FishnetWallet.sol:50
Custom Errors
FishnetWallet.sol:5-18
Security Considerations
Reentrancy: The contract marks nonces as used BEFORE making external calls, providing reentrancy protection.
Receive Function
FishnetWallet.sol:176