Enable Relaying Using EIP-7702
Last updated
Was this helpful?
Was this helpful?
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.28;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../core/Helpers.sol";
import "../core/BaseAccount.sol";
/**
* ZircuitRelayerAccount.sol
* A minimal account to be used with EIP‑7702 (for batching) and ERC‑4337 (for gas sponsoring)
*/
contract ZircuitRelayerAccount is BaseAccount, IERC165, IERC1271, ERC1155Holder, ERC721Holder {
address public immutable ZIRCUIT_TOKEN_ADDRESS;
address public immutable ZIRCUIT_PAYMASTER_ADDRESS;
IEntryPoint public immutable ENTRY_POINT;
constructor(address _zircuitTokenAddress, IEntryPoint _entryPoint, address _zircuitPaymasterAddress) {
ZIRCUIT_TOKEN_ADDRESS = _zircuitTokenAddress;
ENTRY_POINT = _entryPoint;
ZIRCUIT_PAYMASTER_ADDRESS = _zircuitPaymasterAddress;
}
// address of entryPoint v0.8
function entryPoint() public view override returns (IEntryPoint) {
return ENTRY_POINT;
}
/**
* Make this account callable through ERC‑4337 EntryPoint.
* The UserOperation should be signed by this account's private key.
*/
function _validateSignature(PackedUserOperation calldata userOp, bytes32 userOpHash)
internal
virtual
override
returns (uint256 validationData)
{
if (!_checkSignature(userOpHash, userOp.signature)) {
return SIG_VALIDATION_FAILED;
}
bytes calldata paymasterAndData = userOp.paymasterAndData;
// Check if paymasterAndData exists and has at least the 52‑byte prefix
if (paymasterAndData.length >= 52) {
// Extract paymaster address from prefix (needed for approve)
address paymasterAddress = address(bytes20(paymasterAndData[0:20]));
if (paymasterAddress == ZIRCUIT_PAYMASTER_ADDRESS) {
// Bytes 52‑83: Packed data (mode + validUntil + validAfter)
// Bytes 84‑115: Amount (32 bytes)
// Extract packed data (bytes 52‑83)
bytes32 packedData;
assembly {
packedData := calldataload(add(paymasterAndData.offset, 52))
}
// Extract mode from packed data (first byte)
uint8 mode = uint8(uint256(packedData) >> 248);
require(mode <= 1, "Unsupported paymaster mode");
// If mode is 1, approve the paymaster for the specified amount
if (mode == 1) {
require(paymasterAddress != address(0), "Invalid paymaster address"); // Sanity check
uint256 amount;
assembly {
amount := calldataload(add(paymasterAndData.offset, 84))
}
IERC20(ZIRCUIT_TOKEN_ADDRESS).approve(paymasterAddress, amount);
}
}
}
return SIG_VALIDATION_SUCCESS;
}
function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) {
return _checkSignature(hash, signature) ? this.isValidSignature.selector : bytes4(0xffffffff);
}
function _checkSignature(bytes32 hash, bytes memory signature) internal view returns (bool) {
return ECDSA.recover(hash, signature) == address(this);
}
function _requireForExecute() internal view virtual override {
require(msg.sender == address(this) || msg.sender == address(ENTRY_POINT), "not from self or EntryPoint");
}
function supportsInterface(bytes4 id) public pure override(ERC1155Holder, IERC165) returns (bool) {
return id == type(IERC165).interfaceId || id == type(IAccount).interfaceId || id == type(IERC1271).interfaceId
|| id == type(IERC1155Receiver).interfaceId || id == type(IERC721Receiver).interfaceId;
}
// accept incoming calls (with or without value), to mimic an EOA.
fallback() external payable {}
receive() external payable {}
}