---
eip: 7884
title: Operation Router
description: A protocol that enables smart contracts to redirect write operations to external systems.
author: Lucas Picollo (@pikonha), Alex Netto (@alextnetto), Nick Johnson (@arachnid)
discussions-to: https://ethereum-magicians.org/t/operation-router/22633
status: Draft
type: Standards Track
category: ERC
created: 2025-01-23
---

## Abstract

This EIP introduces a protocol that enables smart contracts to redirect write operations to external systems. The protocol defines a standardized way for contracts to indicate that an operation should be handled by either a contract deployed to an L2 chain, to the L1, or an off-chain database, providing an entry point for easy developer experience and client implementations.

## Motivation

As the Ethereum ecosystem grows, there is an increasing need for efficient ways to manage data storage across different layers and systems.

This protocol addresses these challenges by:

- Providing a gas-efficient way to determine operation handlers through view functions
- Enabling seamless integration with L2 solutions and off-chain databases
- Maintaining strong security guarantees through typed signatures and standardized interfaces

## Specification

### Core Components

The protocol consists of three main components:

1. A view function named interface `getOperationHandler` for determining operation handlers that can be one of the following types:
a. `OperationHandledOnchain` for on-chain handlers
b. `OperationHandledOffchain` for off-chain handlers through a gateway
2. A standardized message format for off-chain storage authorization

### Interface

```solidity
interface OperationRouter {

  /** 
    * @dev Error to raise when an encoded function that is not supported
    * @dev is received on the getOperationHandler function
    */
  error FunctionNotSupported();

  /**
    * @dev Error to raise when mutations are being deferred onchain
    * that being the layer 1 or a layer 2
    * @param chainId Chain ID to perform the deferred mutation to.
    * @param contractAddress Contract Address at which the deferred mutation should transact with.
    */
  error OperationHandledOnchain(
      uint256 chainId,
      address contractAddress
  );

  /**
    * @notice Struct used to define the domain of the typed data signature, defined in EIP-712.
    * @param name The user friendly name of the contract that the signature corresponds to.
    * @param version The version of domain object being used.
    * @param chainId The ID of the chain that the signature corresponds to
    * @param verifyingContract The address of the contract that the signature pertains to.
    */
  struct DomainData {
      string name;
      string version;
      uint64 chainId;
      address verifyingContract;
  }

  /**
    * @notice Struct used to define the message context for off-chain storage authorization
    * @param data The original ABI encoded function call
    * @param sender The address of the user performing the mutation (msg.sender).
    * @param expirationTimestamp The timestamp at which the mutation will expire.
    */
  struct MessageData {
      bytes data;
      address sender;
      uint256 expirationTimestamp;
  }

  /**
    * @dev Error to raise when mutations are being deferred to an Offchain entity
    * @param sender the EIP-712 domain definition
    * @param url URL to request to perform the off-chain mutation
    * @param data The original ABI encoded function call along with authorization context
    */
  error OperationHandledOffchain(
      DomainData sender,
      string url,
      MessageData data
  );

  /**
    * @notice Determines the appropriate handler for an encoded function call
    * @param encodedFunction The ABI encoded function call
    */
  function getOperationHandler(bytes calldata encodedFunction) external view;
}

```

The onchain flow is specified as follows:

![](../assets/eip-7884/d1.svg)

It is important to notice that the `getOperationHandler` relies on the given argument, the encoded function, to specify which contract will the request be redirected to, therefore, it is unable to address `multicall` transactions that could lead to different destination contracts. That means that `multicall` that is known will be redirected to different contracts should be handled in a sequential way by first calling the `getOperationHandler` and then making the actual transaction to the returned contract.

#### Database flow

The HTTP request made to the gateway follows the same standard proposed by the [EIP-3668](./eip-3668) where the URL receives `/{sender}/{data}.json` enabling an API to behave just like an smart contract would. However, the [EIP-712 Typed Signature](./eip-712.md) was introduced to enable authentication.

![](../assets/eip-7884/d2.svg)

### Implementation Example

The contract deployed to the L1 MUST implement the `getOperationHandler` to act as a router redirecting the requests to the respective handler.

```solidity
contract OperationRouterExample {
    function getOperationHandler(bytes calldata encodedFunction) external view {
        bytes4 selector = bytes4(encodedFunction[:4]);

        if (selector == bytes4(keccak256("setText(bytes32, string)"))) {
            revert OperationHandledOffchain(
                DomainData(
                    "IdentityResolver",
                    "1",
                    1,
                    address(this)
                ),
                "https://api.example.com/profile",
                MessageData(
                    encodedFunction,
                    msg.sender,
                    block.timestamp + 1 hours
                )
            );
        }

        if (selector == bytes4(keccak256("setAddress(bytes32,address)"))) {
            revert OperationHandledOnchain(
                10,
                address(0x123...789)
            );
        }
    }
}

```

The client implementation would look as follows:

```tsx
 try {
  const calldata = {
    functionName: 'setText',
    abi,
    args: [key, value],
    address,
    account
  }
 
  await client.readContract({
    functionName: 'getOperationHandler',
    abi,
    args: [encodeFunctionData(calldata)],
  })
} catch (err) {
  const data = getRevertErrorData(err)

  switch (data?.errorName) {
    case 'OperationHandledOffchain': {
      const [domain, url, message] = errorResult.args as [
        DomainData,
        string,
        MessageData,
      ]
      await handleDBStorage({ domain, url, message, signer })
    }
    case 'OperationHandledOnchain': {
      const [chainId, contractAddress] = data.args as [bigint, `0x${string}`]

      const l2Client = createPublicClient({
        chain: getChain(Number(chainId)),
        transport: http(),
      }).extend(walletActions)

      const { request } = await l2Client.simulateContract({
        ...calldata,
        address: contractAddress,
      })
      await l2Client.writeContract(request)
    }
    default:
      console.error('error registering domain: ', { err })
  }
```

## Rationale

The standard aims to enable offchain writing operations, designed to be a complement for the CCIP-Read ([ERC-3668](./eip-3668)) which is already widely adopted by the community.

## Backwards Compatibility

This EIP is fully backward compatible as it:

- Introduces new interfaces that don't conflict with existing ones
- Uses view functions to gather offchain information
- Can be implemented alongside existing storage patterns

## Security Considerations

### Handler Validation

Off-chain handlers must:

- Verify EIP-712 signatures
- Implement proper access controls
- Handle concurrent modifications safely

### General Recommendations

- Implement rate limiting for off-chain handlers
- Use secure transport (HTTPS) for off-chain communications
- Monitor for unusual patterns that might indicate attacks
- Implement proper error handling for failed transactions

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
