Skip to content

Integration Options

Contract Interaction

Protocol Integration Guide

This guide explains how to integrate the ECP protocol into your own smart contracts. The protocol consists of two main contracts:

  1. CommentManager - Handles comment creation, editing, and deletion
  2. ChannelManager - Manages comment channels and their associated hooks

Core Concepts

Channels

Channels are the primary organizational unit in the ECP protocol. Each channel:

  • Is represented as an NFT (ERC721)
  • Can have one custom hook attached to it
  • Has its own metadata
  • Can enforce specific rules for comments

Hooks

A hook is a smart contract that can be attached to a channel (one hook per channel) to:

  • Validate comments before they're posted
  • Add custom metadata to comments
  • Implement custom business logic
  • Monetize channels through various mechanisms

The hook is set during channel creation or can be updated later using the setHook function in the ChannelManager contract. When a hook is set, it can define which operations it wants to participate in through its permissions.

Integration Methods

1. Direct Contract Calls

The simplest way to integrate is by directly calling the protocol contracts:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
import "@ecp.eth/protocol/src/interfaces/ICommentManager.sol";
import "@ecp.eth/protocol/src/interfaces/IChannelManager.sol";
import "@ecp.eth/protocol/src/types/Comments.sol";
import "@ecp.eth/protocol/src/types/Metadata.sol";
 
contract MyProtocol {
  ICommentManager public commentManager;
  IChannelManager public channelManager;
 
  constructor(address _commentManager, address _channelManager) {
    commentManager = ICommentManager(_commentManager);
    channelManager = IChannelManager(_channelManager);
  }
 
  /// @notice Posts a comment through the protocol
  /// @param commentData The comment data including content, metadata, and other parameters
  /// @return The ID of the created comment
  function postComment(
    Comments.CreateComment memory commentData,
    bytes memory authorSignature
  ) external payable returns (bytes32) {
    require(address(this) == commentData.app, "Only the app can post comments");
 
    // When msg.sender equals app (address(this)), the protocol skips app signature verification
    // This is why we can pass an empty string as appSignature
    return
      commentManager.postCommentWithSig{ value: msg.value }(
        commentData,
        authorSignature,
        ""
      );
  }
}

2. Custom Hook Implementation

For more advanced integration, you can implement a custom hook:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
import "@ecp.eth/protocol/src/interfaces/IHook.sol";
import "@ecp.eth/protocol/src/types/Hooks.sol";
import "@ecp.eth/protocol/src/types/Comments.sol";
import "@ecp.eth/protocol/src/types/Channels.sol";
import "@ecp.eth/protocol/src/types/Metadata.sol";
 
contract MyCustomHook is IHook {
  function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
    return interfaceId == type(IHook).interfaceId;
  }
 
  function getHookPermissions()
    external
    pure
    returns (Hooks.Permissions memory)
  {
    return
      Hooks.Permissions({
        onInitialize: false,
        onCommentAdd: true,
        onCommentDelete: false,
        onCommentEdit: false,
        onChannelUpdate: false,
        onCommentHookDataUpdate: false
      });
  }
 
  /// @notice Execute after a hook is initialized on a channel
  function onInitialize(
    address channelManager,
    Channels.Channel memory channelData,
    uint256 channelId,
    Metadata.MetadataEntry[] calldata metadata
  ) external returns (bool success) {
    // Your custom initialization logic here
    // For example, set up initial state for the channel
    return true;
  }
 
  /// @notice Execute after a comment is processed
  function onCommentAdd(
    Comments.Comment calldata comment,
    Metadata.MetadataEntry[] calldata metadata,
    address caller,
    bytes32 commentId
  ) external payable returns (Metadata.MetadataEntry[] memory) {
    // Your custom logic here
    // For example, validate comments, add metadata, etc.
 
    Metadata.MetadataEntry[] memory hookMetadata = new Metadata.MetadataEntry[](
      1
    );
    hookMetadata[0] = Metadata.MetadataEntry({
      key: "string custom_data",
      value: abi.encode("my custom data")
    });
 
    return hookMetadata;
  }
 
  /// @notice Execute after a comment is deleted
  function onCommentDelete(
    Comments.Comment calldata comment,
    Metadata.MetadataEntry[] calldata metadata,
    Metadata.MetadataEntry[] calldata hookMetadata,
    address caller,
    bytes32 commentId
  ) external returns (bool success) {
    // Your custom deletion logic here
    // For example, clean up associated data, update counters, etc.
    return true;
  }
 
  /// @notice Execute after a comment is edited
  function onCommentEdit(
    Comments.Comment calldata comment,
    Metadata.MetadataEntry[] calldata metadata,
    address caller,
    bytes32 commentId
  ) external payable returns (Metadata.MetadataEntry[] memory) {
    // Your custom edit logic here
    // For example, validate edits, update metadata, etc.
    return new Metadata.MetadataEntry[](0);
  }
 
  /// @notice Execute after a channel is updated
  function onChannelUpdate(
    address channel,
    uint256 channelId,
    Channels.Channel calldata channelData,
    Metadata.MetadataEntry[] calldata metadata
  ) external returns (bool success) {
    // Your custom channel update logic here
    // For example, update hook state, validate changes, etc.
    return true;
  }
 
  /// @notice Execute to update hook data for an existing comment
  function onCommentHookDataUpdate(
    Comments.Comment calldata comment,
    Metadata.MetadataEntry[] calldata metadata,
    Metadata.MetadataEntry[] calldata hookMetadata,
    address caller,
    bytes32 commentId
  ) external returns (Metadata.MetadataEntryOp[] memory operations) {
    // Your custom hook data update logic here
    // For example, modify existing hook metadata, add new entries, etc.
    return new Metadata.MetadataEntryOp[](0);
  }
}

Note: Metadata keys should be UTF-8 encoded strings in the format "type key" (e.g., "string custom_data", "uint256 count", "bool verified"). In Solidity, you can use string literals directly for bytes32 fields - no explicit conversion is needed.

Protocol Fees

The protocol implements several fee mechanisms:

  1. Channel Creation Fee (default: 0.02 ETH)

    • Paid when creating a new channel
    • Can be adjusted by protocol owner
  2. Comment Creation Fee (default: 0)

    • Can be enabled for spam prevention
    • Set by protocol owner
  3. Hook Transaction Fee (default: 2%)

    • Applied to ETH sent to hooks
    • Basis points (1 bp = 0.01%)
    • Maximum 100%

Best Practices

  1. Security
    • Always verify channel existence before posting comments
    • Implement proper access control in your hooks
    • Handle protocol fees correctly
    • Use reentrancy guards when necessary
  2. Gas Optimization
    • Batch metadata operations when possible
    • Use efficient data structures for hook storage
    • Consider gas costs when implementing hook logic
  3. Error Handling
    • Implement proper error handling for all protocol interactions
    • Handle failed transactions gracefully
    • Consider implementing retry mechanisms
  4. Testing
    • Test your integration with the protocol thoroughly
    • Use the provided test hooks (NoopHook) for development
    • Test with different fee configurations

Example Use Cases

  1. Token-Gated Comments
    • Create a channel for a specific token
    • Implement a hook that verifies token ownership
    • Allow only token holders to comment
  2. Monetized Channels
    • Create a channel with a custom hook
    • Implement payment logic in the hook
    • Share revenue with channel owners
  3. Moderated Comments
    • Create a channel with moderation rules
    • Implement a hook that enforces moderation
    • Allow only approved addresses to comment

Additional Resources