# Enable Relaying Using EIP-7702

To batch transactions and use gas‑sponsored UserOperations with **Zircuit** you must first **delegate your account to the Zircuit Relayer Account** via an **EIP‑7702 `SetCode` transaction**. The guide below shows how to do this securely with Foundry’s `cast` CLI—without ever pasting your private key into the browser.

## Why Delegation Is Required

EIP‑7702 lets you temporarily replace your externally‑owned account’s code for a single transaction batch—perfect for ERC‑4337 wallets. Until mainstream wallets such as MetaMask or Rabby add native support for sending a `SetCode` transaction to *arbitrary* addresses, you have to perform the delegation yourself.

## Security Principles

* **Never** paste your private key into any website form or browser console.
* Export the key only in a hardened, preferably offline shell session.
* Once delegation is confirmed, remove the key from your environment.

## Prerequisites

Foundry must be installed as described in the official installation guide [here](https://getfoundry.sh/introduction/installation/).

{% stepper %}
{% step %}
**Export your private key in a local terminal**

```bash
export PRIVATE_KEY=0xYOUR_PRIVATE_KEY
```

{% endstep %}

{% step %}
**Send the SetCode transaction to delegate to ZircuitRelayerAccount**

{% code overflow="wrap" %}

```bash
cast send $(cast az) --auth <ZIRCUIT_RELAYER_ADDRESS> --private-key $PRIVATE_KEY --rpc-url https://mainnet.zircuit.com
```

{% endcode %}
{% endstep %}

{% step %}
**Verify the delegation on** [**https://explorer.zircuit.com**](https://explorer.zircuit.com/)**, verifying your address has been successfully delegated**
{% endstep %}
{% endstepper %}

## Next Steps

### Contract Details

**Zircuit Relayer Account address**:

Garfield Testnet: [0xD4f99Ef25e5aAB3A0575D9C2dB2E4f09f18442D8](https://explorer.garfield-testnet.zircuit.com/address/0xD4f99Ef25e5aAB3A0575D9C2dB2E4f09f18442D8?activeTab=3)

```solidity
// 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 {}
}
```
