Skip to main content

EFP Social Graph

Ethereum Follow Protocol (EFP) Documentation

Ethereum Follow Protocol (EFP) Documentation

Resources

What is EFP?

Ethereum Follow Protocol (EFP) is an onchain social graph protocol for Ethereum accounts. It enables users to follow other Ethereum addresses, creating a decentralized social network layer. Unlike traditional social networks, EFP stores all relationship data onchain, making it composable and censorship-resistant.

Key features:

  • Decentralized Social Graph: All follow relationships stored onchain
  • Composable: Can be integrated into any application
  • Tag System: Support for custom tags like "top8", "mute", "block"
  • No Vendor Lock-in: Open protocol accessible by anyone

Component Library (React)

You can use the Ethereum Identity Kit to integrate EFP into your application:

Ethereum Identity Kit (EIK) - Comprehensive EFP Integration

Ethereum Identity Kit (EIK) - Comprehensive EFP Integration

API Integration

Basic User Stats

Get followers and following counts for any Ethereum address:

async function getEFPStats(address) {
try {
const response = await fetch(`https://api.ethfollow.xyz/api/v1/users/${address}/stats`)
const stats = await response.json()

return {
followers: stats.followers_count,
following: stats.following_count,
}
} catch (error) {
console.error('Error fetching EFP stats:', error)
return null
}
}

Get User's Following List

Retrieve the complete list of accounts a user follows:

async function getUserFollowing(address, limit = 100) {
try {
const response = await fetch(
`https://api.ethfollow.xyz/api/v1/users/${address}/following?limit=${limit}`
)
const data = await response.json()

return data.following.map(follow => ({
address: follow.address,
ens: follow.ens,
avatar: follow.avatar,
tags: follow.tags || []
}))
} catch (error) {
console.error('Error fetching following list:', error)
return []
}
}

Get User's Followers

Retrieve the list of accounts following a user:

async function getUserFollowers(address, limit = 100) {
try {
const response = await fetch(
`https://api.ethfollow.xyz/api/v1/users/${address}/followers?limit=${limit}`
)
const data = await response.json()

return data.followers.map(follower => ({
address: follower.address,
ens: follower.ens,
avatar: follower.avatar
}))
} catch (error) {
console.error('Error fetching followers list:', error)
return []
}
}

Implementation Steps

1. Update HTML (frontend/src/index.html)

Add a section for displaying EFP social graph data:

<div class="hidden" id="efpProfile">
<h3>EFP Social Graph:</h3>
<div id="efpLoader">Loading EFP data...</div>
<div id="efpContainer" class="hidden">
<div id="efpStats"></div>
<div id="efpConnections"></div>
</div>
</div>
<div class="hidden" id="noEFPProfile">No EFP Profile detected.</div>

2. Update JavaScript (frontend/src/index.js)

Add EFP profile resolution functionality:

const efpProfileElm = document.getElementById('efpProfile')
const noEFPProfileElm = document.getElementById('noEFPProfile')
const efpStatsElm = document.getElementById('efpStats')
const efpConnectionsElm = document.getElementById('efpConnections')

async function displayEFPProfile() {
try {
// Get basic EFP stats
const stats = await getEFPStats(address)

if (stats && (stats.followers > 0 || stats.following > 0)) {
efpProfileElm.classList = ''

// Display stats
efpStatsElm.innerHTML = `
<div class="efp-stats">
<div class="stat-item">
<span class="stat-number">${stats.followers}</span>
<span class="stat-label">Followers</span>
</div>
<div class="stat-item">
<span class="stat-number">${stats.following}</span>
<span class="stat-label">Following</span>
</div>
</div>
`

// Get and display some recent follows
if (stats.following > 0) {
const following = await getUserFollowing(address, 5)
let connectionsHTML = '<h4>Recent Follows:</h4><div class="connections-list">'

following.forEach(follow => {
connectionsHTML += `
<div class="connection-item">
${follow.avatar ? `<img src="${follow.avatar}" class="avatar-small" />` : ''}
<span class="connection-name">${follow.ens || formatAddress(follow.address)}</span>
${follow.tags.length > 0 ? `<span class="tags">${follow.tags.join(', ')}</span>` : ''}
</div>
`
})

connectionsHTML += '</div>'
efpConnectionsElm.innerHTML = connectionsHTML
}

document.getElementById('efpContainer').classList = ''
} else {
noEFPProfileElm.classList = ''
}

document.getElementById('efpLoader').style.display = 'none'
} catch (error) {
console.error('Error displaying EFP profile:', error)
noEFPProfileElm.classList = ''
document.getElementById('efpLoader').style.display = 'none'
}
}

function formatAddress(address) {
return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`
}

3. CSS Styling

Add styles for the EFP profile display:

.efp-stats {
display: flex;
gap: 20px;
margin: 15px 0;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
}

.stat-item {
text-align: center;
}

.stat-number {
display: block;
font-size: 24px;
font-weight: bold;
color: #333;
}

.stat-label {
display: block;
font-size: 12px;
color: #666;
text-transform: uppercase;
}

.connections-list {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 10px;
}

.connection-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
background-color: #fff;
border: 1px solid #e0e0e0;
border-radius: 6px;
}

.avatar-small {
width: 24px;
height: 24px;
border-radius: 50%;
}

.connection-name {
font-weight: 500;
}

.tags {
font-size: 11px;
color: #666;
background-color: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
margin-left: auto;
}

.hidden {
display: none;
}

Advanced Features

Leaderboard Integration

Get top users by followers or mutual connections:

async function getEFPLeaderboard(sort = 'followers', limit = 10) {
try {
const response = await fetch(
`https://api.ethfollow.xyz/api/v1/leaderboard/ranked?sort=${sort}&limit=${limit}`
)
const data = await response.json()
return data.results
} catch (error) {
console.error('Error fetching leaderboard:', error)
return []
}
}

Search EFP Users

Search for users by ENS name or address:

async function searchEFPUsers(searchTerm) {
try {
const response = await fetch(
`https://api.ethfollow.xyz/api/v1/leaderboard/search?term=${encodeURIComponent(searchTerm)}`
)
const data = await response.json()
return data.results
} catch (error) {
console.error('Error searching EFP users:', error)
return []
}
}

Check Mutual Connections

Find mutual follows between two addresses:

async function getMutualConnections(address1, address2) {
try {
// Get following lists for both addresses
const [following1, following2] = await Promise.all([
getUserFollowing(address1),
getUserFollowing(address2)
])

// Find mutual connections
const mutuals = following1.filter(f1 =>
following2.some(f2 => f2.address.toLowerCase() === f1.address.toLowerCase())
)

return mutuals
} catch (error) {
console.error('Error getting mutual connections:', error)
return []
}
}

API Endpoints Reference

EndpointDescription
/users/{address}/statsGet follower/following counts
/users/{address}/followingGet list of addresses user follows
/users/{address}/followersGet list of user's followers
/leaderboard/rankedGet ranked users by various metrics
/leaderboard/searchSearch users by name or address

Error Handling

The implementation includes proper error handling for:

  • Network connectivity issues
  • Invalid or non-existent addresses
  • API rate limiting
  • Missing or empty social graph data

React Integration

For React applications, consider using the Ethereum Identity Kit:

import { useEFPProfile } from '@ethereum-identity-kit/core'

function UserProfile({ address }) {
const { data: efpData, loading, error } = useEFPProfile(address)

if (loading) return <div>Loading EFP data...</div>
if (error) return <div>Error loading social graph</div>

return (
<div>
<h3>Social Graph</h3>
<div>Followers: {efpData.followers}</div>
<div>Following: {efpData.following}</div>
</div>
)
}