Title: Operation_Router
Category: EIPs

Author: pikonha
Date: 2025-01-23T12:55:51.146Z

eip: TBD
title: “Operation Router”
description: The standard for routing transactions to on and offchain handlers.
author: Lucas Picollo (@pikonha), Alex T. Netto (@alextnetto.eth), Nick Johnson (@arachnid)
status: Draft
type: Standards Track
category: EIP
created: 2025-01-23
requires: 712
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:

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
A standardized message format for off-chain storage authorization

Interface
Protocol Flow
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:
image669×332 14 KB
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 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 where the URL receives /{sender}/{data}.json enabling an API to behave just like an smart contract would. However, the EIP-712 Typed Signature was introduced to enable authentication.
image655×556 24.9 KB
Implementation Example
The contract deployed to the L1 MUST implement the getOperationHandler to act as a router redirecting the requests to the respective handler.
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:
 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) 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.

Author: xinbenlv
Date: 2025-02-04T19:17:01.621Z

pikonha:

getOperationHandler


Interesting proposal. Do you envision the offchain handler being permission-less or permissioned?

Author: pikonha
Date: 2025-02-10T14:00:22.785Z

It depends on the use case, but the standard was created to allow both implementations with the permissioned version being handled by the EIP-712 signature.