Skip to main content

🔄 EigenLayer

To create a subgraph for EigenLayer that builds associations between operators, AVSs, and strategies we just need to create 3 files, events.sol, schema.sol and indexer.sol and in less than a minute after deploying we'll have a GraphQL endpoint that we can query

Declare Events

events.sol
struct OperatorDetails {
address earningsReceiver;
address delegationApprover;
uint32 stakerOptOutWindowBlocks;
}

interface Events {
// Delegation Manager
event OperatorDetailsModified(address indexed operator, OperatorDetails newOperatorDetails);
event OperatorMetadataURIUpdated(address indexed operator, string metadataURI);
event OperatorSharesDecreased(address indexed operator, address staker, address strategy, uint256 shares);
event OperatorSharesIncreased(address indexed operator, address staker, address strategy, uint256 shares);
event OperatorRegistered(address indexed operator, OperatorDetails operatorDetails);
event RegisterAsOperator(address indexed operator, address indexed delegationTerms);

// AVS Directory
event OperatorAVSRegistrationStatusUpdated(address indexed operator, address indexed avs, uint8 status);
event AVSMetadataURIUpdated(address indexed avs, string metadataURI);

// Strategy Manager
event StrategyAddedToDepositWhitelist(address strategy);
event StrategyRemovedFromDepositWhitelist(address strategy);
}

Declare Schema (using Solidity structs)

schema.sol
enum PoolType {
EIGENPOD,
STRATEGY
}

struct Operator {
address id;
address delegationTerms;
string metadataURI;
address earningsReceiver;
address delegationApprover;
uint32 stakerOptOutWindowBlocks;
}

struct OperatorShare {
string id; // operator + strategy
address operator;
address strategy;
uint256 shares;
@many(OperatorShareAction.operatorShareId) actions;
}

struct OperatorShareAction {
string id; // block + logIdx
address operator;
address strategy;
address staker;
int256 sharesDelta;
@belongsTo(OperatorShare.id) operatorShareId;
uint64 block;
bytes32 txHash;
uint32 logIdx;
uint32 timestamp;
}

struct Avs {
address id;
string metadataURI;
}

struct AvsOperator {
string id; // avs + operator
address avs;
address operator;
uint8 status;
}

struct Pool {
address id;
PoolType poolType;
bool active;
string name;
string symbol;
@belongsTo(Token.id) underlyingTokenId;

uint64 createdBlock;
bytes32 createdTxHash;
uint32 createdTimestamp;
}

struct Token {
address id;
string name;
string symbol;
uint8 decimals;
bool loaded;
}

Write Indexer Transformations

indexer.sol
contract MyIndex is GhostGraph {
using StringHelpers for EventDetails;
using StringHelpers for uint256;

function registerHandles() external {
graph.registerHandle(0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A); // delgation manager
graph.registerHandle(0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF); // avs directory
graph.registerHandle(0x858646372CC42E1A627fcE94aa7A7033e7CF075A); // strategy manager
}

// --- Delegation -- //

function onOperatorDetailsModified(EventDetails memory details, OperatorDetailsModifiedEvent memory ev) external {
Operator memory operator = graph.getOperator(ev.operator);
operator.earningsReceiver = ev.newOperatorDetails.earningsReceiver;
operator.delegationApprover = ev.newOperatorDetails.delegationApprover;
operator.stakerOptOutWindowBlocks = ev.newOperatorDetails.stakerOptOutWindowBlocks;
graph.saveOperator(operator);
}

function onOperatorMetadataURIUpdated(
EventDetails memory details,
OperatorMetadataURIUpdatedEvent memory ev
) external {
Operator memory operator = graph.getOperator(ev.operator);
operator.metadataURI = ev.metadataURI;
graph.saveOperator(operator);
}

function onOperatorSharesIncreased(EventDetails memory details, OperatorSharesIncreasedEvent memory ev) external {
_shareDeltaHelper(details, ev.operator, ev.strategy, ev.staker, int256(ev.shares));
}

function onOperatorSharesDecreased(EventDetails memory details, OperatorSharesDecreasedEvent memory ev) external {
_shareDeltaHelper(details, ev.operator, ev.strategy, ev.staker, -int256(ev.shares));
}

function _shareDeltaHelper(
EventDetails memory details,
address operator,
address strategy,
address staker,
int256 sharesDelta
) private {
string memory id = string(abi.encodePacked("A", _addrToString(operator), ":0", _addrToString(strategy)));
OperatorShare memory os = graph.getOperatorShare(id);
os.operator = operator;
os.strategy = strategy;
if (sharesDelta > 0) {
os.shares += uint256(sharesDelta);
} else {
os.shares -= uint256(-1 * sharesDelta);
}

OperatorShareAction memory action = graph.getOperatorShareAction(details.uniqueId());
action.operator = operator;
action.strategy = strategy;
action.sharesDelta = sharesDelta;
action.staker = staker;
action.operatorShareId = id;
action.txHash = details.transactionHash;
action.timestamp = details.timestamp;
action.block = details.block;
action.logIdx = details.logIndex;

graph.saveOperatorShare(os);
graph.saveOperatorShareAction(action);
}

function onOperatorRegistered(EventDetails memory details, OperatorRegisteredEvent memory ev) external {
Operator memory operator = graph.getOperator(ev.operator);
operator.earningsReceiver = ev.operatorDetails.earningsReceiver;
operator.delegationApprover = ev.operatorDetails.delegationApprover;
operator.stakerOptOutWindowBlocks = ev.operatorDetails.stakerOptOutWindowBlocks;
graph.saveOperator(operator);
}

function onRegisterAsOperator(EventDetails memory details, RegisterAsOperatorEvent memory ev) external {
Operator memory operator = graph.getOperator(ev.operator);
operator.delegationTerms = ev.delegationTerms;
graph.saveOperator(operator);
}

// --- AVS Directory -- //

function onOperatorAVSRegistrationStatusUpdated(
EventDetails memory details,
OperatorAVSRegistrationStatusUpdatedEvent memory ev
) external {
string memory id = string(abi.encodePacked("A", _addrToString(ev.avs), ":0", _addrToString(ev.operator)));
AvsOperator memory ao = graph.getAvsOperator(id);
ao.avs = ev.avs;
ao.operator = ev.operator;
ao.status = ev.status;
graph.saveAvsOperator(ao);
}

function onAVSMetadataURIUpdated(EventDetails memory details, AVSMetadataURIUpdatedEvent memory ev) external {
Avs memory avs = graph.getAvs(ev.avs);
avs.metadataURI = ev.metadataURI;
graph.saveAvs(avs);
}

function onStrategyAddedToDepositWhitelist(
EventDetails memory details,
StrategyAddedToDepositWhitelistEvent memory ev
) external {
(bool ok, address underlyingAddress) = _getUnderlying(ev.strategy);
if (!ok) {
return;
}
Token memory token = _ensureToken(underlyingAddress);

Pool memory pool = graph.getPool(ev.strategy);
pool.poolType = PoolType.STRATEGY;
pool.name = string(abi.encodePacked("Strategy-", token.name));
pool.symbol = string(abi.encodePacked("S-", token.symbol));
pool.underlyingTokenId = token.id;
pool.active = true;
pool.createdBlock = details.block;
pool.createdTimestamp = details.timestamp;
pool.createdTxHash = details.transactionHash;

graph.savePool(pool);
}

// --- Strategy Manager -- //

function onStrategyRemovedFromDepositWhitelist(
EventDetails memory details,
StrategyRemovedFromDepositWhitelistEvent memory ev
) external {
Pool memory pool = graph.getPool(ev.strategy);
pool.active = false;
graph.savePool(pool);
}

// Helpers

function _ensureToken(address addr) private returns (Token memory) {
Token memory token = graph.getToken(addr);
if (token.loaded) {
return token;
}
token.name = _getName(addr);
token.symbol = _getSymbol(addr);
token.decimals = _getDecimals(addr);
token.loaded = true;
graph.saveToken(token);
return token;
}

function _getName(address token) private returns (string memory) {
try ierc20(token).name() returns (string memory name) {
return name;
} catch {
return "uknown";
}
}

function _getSymbol(address token) private returns (string memory) {
try ierc20(token).symbol() returns (string memory symbol) {
return symbol;
} catch {
return "uknown";
}
}

function _getDecimals(address token) private returns (uint8) {
try ierc20(token).decimals() returns (uint8 decimals) {
return decimals;
} catch {
return 0;
}
}

function _getUnderlying(address strat) private returns (bool, address) {
try istrategy(strat).underlyingToken() returns (address underlyingAddress) {
return (true, underlyingAddress);
} catch {
return (false, address(0));
}
}

function _addrToString(address a) private returns (string memory) {
return StringHelpers.toString(uint256(uint160(a)));
}
}

interface istrategy {
function underlyingToken() external view returns (address);
}

interface ierc20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}