Skip to content

unruggable-labs/EEP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EEP — Ethereum Endorsement Protocol

A minimal, upgradeable on-chain registry where anyone can record an endorsement of any target. An endorsement is a tuple of (endorsementType, targetType, target, data).

EEP generalizes the concept of "starring" to arbitrary endorsement types with flexible cross-chain targets. It is designed for AI agents endorsing other agents, domain names, smart contracts, or any addressable entity.

Inspired by Ethereum Follow Protocol (EFP) and its prefix-byte encoding approach. EEP goes further by supporting ERC-7930 cross-chain addresses as a target type from day one.

Architecture

  • UUPS upgradeable proxy pattern (OpenZeppelin 5.x)
  • AccessControlDEFAULT_ADMIN_ROLE authorizes upgrades. No owner, no pause.
  • Storage key: hashedTarget = keccak256(endorsementType || targetType || target) — endorsements of the same target but different types are tracked separately.
  • Deploy target: Base mainnet initially; the same contract can be deployed on any EVM chain.

Unified Counter & Sparse Mapping

Storage uses a mapping-of-mapping pattern with a unified counter:

mapping(bytes32 => mapping(uint256 => Endorsement)) _endorsements;
mapping(bytes32 => uint256) _counts;

_counts[hashedTarget] is a single counter that advances for every endorsement (methods 1 and 2). It doubles as the next write index. Method 1 (endorse) writes the full endorsement struct at the current index before incrementing. Method 2 (endorseCount) only increments the counter, leaving a gap — reading _endorsements[hashedTarget][thatIndex] returns the zero struct.

This gives every endorsement a deterministic sequence number regardless of method. Indexers can iterate 0..count-1, distinguishing stored endorsements (endorser != address(0)) from counter-only gaps.

Encoding Specification

endorsementType (bytes1)

Value Meaning
0x01 Star
0x02+ Reserved for future endorsement types

targetType (bytes1)

Value Meaning
0x01 Domain — target is UTF-8 bytes of a domain name (e.g. vitalik.eth, example.com)
0x02 ERC-7930 address — target is an ERC-7930 encoded cross-chain address

target (variable bytes)

Contents depend on targetType:

  • Domain (0x01): UTF-8 encoded domain name, e.g. "vitalik.eth" (11 bytes), "example.com" (11 bytes). The protocol does not resolve or validate the domain — that is the consumer's job.
  • ERC-7930 (0x02): Binary cross-chain address per the ERC-7930 spec

data (variable bytes, optional)

For Star endorsements: UTF-8 comment text. Empty bytes allowed.

Size Check: ERC-7930 Ethereum Address

An Ethereum L1 address encoded in ERC-7930:

Field Bytes Value (example)
Version 2 0x0001
ChainType 2 0x0000 (eip155)
ChainRefLength 1 0x01
ChainReference 1 0x01 (chain ID 1)
AddressLength 1 0x14 (20)
Address 20 20-byte Ethereum address
Total 27

Full endorsement key components for hashing:

Component Bytes
endorsementType 1
targetType 1
target (ERC-7930 eth addr) 27
Total key input 29

For a Base address (chain ID 8453 = 0x2105), ChainReference is 2 bytes → target is 28 bytes, total key input is 30 bytes. The 2-byte saving from shrinking both type fields to bytes1 gives headroom for chains with larger chain IDs or non-Ethereum address schemes.

Three Endorsement Methods

Method 1: Full On-Chain (endorse)

function endorse(bytes1 endorsementType, bytes1 targetType, bytes target, bytes data) external;

Stores the complete endorsement at the current sequence index, then increments the unified counter. Emits Endorsed event with full data.

Use when: You need on-chain queryability of individual endorsements (who endorsed, when, with what comment).

Example — Star a domain name:

// ENS domain
registry.endorse(
    0x01,                      // Star
    0x01,                      // Domain target
    bytes("vitalik.eth"),      // target
    bytes("legend")            // comment
);

// DNS domain — same target type, different string
registry.endorse(
    0x01,                      // Star
    0x01,                      // Domain target
    bytes("example.com"),      // target
    bytes("useful site")       // comment
);

Method 2: Counter + Event (endorseCount)

function endorseCount(bytes1 endorsementType, bytes1 targetType, bytes target, bytes data) external;

Increments the unified counter and emits EndorsedCount event. Does not store endorsement data — the mapping slot at this sequence index remains the zero struct. Cheaper gas than method 1.

Use when: You only need the count on-chain and can reconstruct details from event logs.

Example — Star an ERC-7930 cross-chain address:

bytes memory target = abi.encodePacked(
    bytes2(0x0001),  // ERC-7930 version
    bytes2(0x0000),  // chainType: eip155
    bytes1(0x01),    // chainRefLen
    bytes1(0x01),    // chainRef: chain 1
    bytes1(0x14),    // addrLen: 20
    targetAddress    // 20-byte address
);
registry.endorseCount(0x01, 0x02, target, bytes(""));

Method 3: Inscription-Style (fallback)

Send a raw transaction to the registry contract with calldata that does not match any function selector. The calldata itself is the endorsement.

No event emitted. No counter incremented. Cheapest gas cost. Indexers must scan transaction calldata directly.

Encoding: abi.encode(bytes1, bytes1, bytes, bytes) — full ABI encoding with length prefixes, so the boundary between the variable-length target and data fields is unambiguous.

Use when: Minimizing gas is the priority and you have an off-chain indexer.

Example — Raw calldata star:

// Star vitalik.eth via inscription (no function, raw calldata)
(bool ok, ) = address(registry).call(
    abi.encode(
        bytes1(0x01),             // endorsementType = Star
        bytes1(0x01),             // targetType = Domain
        bytes("vitalik.eth"),     // target
        bytes("inscribed star")   // data
    )
);

Indexer Parsing

Methods 1 & 2 are standard function calls: calldata is 4-byte selector || abi.encode(bytes1, bytes1, bytes, bytes). Indexers already have the ABI and can decode via the normal event log or calldata-trace path.

Method 3 inscriptions: calldata is abi.encode(bytes1, bytes1, bytes, bytes) directly, with no selector prefix. Indexers scan transactions sent to the registry whose calldata does not start with a known function selector (endorse or endorseCount), then decode:

(bytes1 endorsementType, bytes1 targetType, bytes memory target, bytes memory data) =
    abi.decode(calldata, (bytes1, bytes1, bytes, bytes));

Distinguish valid inscriptions from accidental transfers by verifying the decoded endorsementType and targetType correspond to registered type values.

Reading Endorsements

// Get unified count (methods 1 + 2) — also the next sequence index
bytes32 hashed = registry.hashTarget(0x01, 0x01, bytes("vitalik.eth"));
uint256 count = registry.endorsementCount(hashed);

// Iterate all sequence indices — sparse mapping means some are zero structs
for (uint256 i = 0; i < count; i++) {
    IEEPRegistry.Endorsement memory e = registry.getEndorsement(hashed, i);
    if (e.endorser != address(0)) {
        // Full on-chain endorsement (method 1)
    } else {
        // Counter-only gap (method 2) — data lives in EndorsedCount event logs
    }
}

Deployment

Prerequisites: Foundry

# Build
forge build

# Test
forge test -v

# Deploy to Base mainnet (dry run)
forge script script/DeployEEPRegistry.s.sol --rpc-url base

# Deploy to Base mainnet (broadcast)
forge script script/DeployEEPRegistry.s.sol --rpc-url base --broadcast --verify

Set environment variables:

  • BASE_RPC_URL — Base mainnet RPC endpoint
  • BASESCAN_API_KEY — for contract verification

The deployer address becomes the admin (DEFAULT_ADMIN_ROLE) and can authorize future upgrades.

Project Structure

src/
├── EEPRegistry.sol              # Main registry contract (UUPS upgradeable)
└── interfaces/
    └── IEEPRegistry.sol         # Interface with structs, events, errors
test/
└── EEPRegistry.t.sol            # Foundry tests (27 tests)
script/
└── DeployEEPRegistry.s.sol      # Deploy script (ERC1967 proxy)

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors