> For the complete documentation index, see [llms.txt](https://docs.zircuit.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zircuit.com/infra/relayers/zircuit-relayer-service/enable-relaying-using-eip-7702.md).

# 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 {}
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zircuit.com/infra/relayers/zircuit-relayer-service/enable-relaying-using-eip-7702.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
