🔄 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);
}