1+ import {
2+ elizaLogger ,
3+ type IAgentRuntime ,
4+ type Memory ,
5+ type Provider ,
6+ type State ,
7+ } from "@elizaos/core" ;
8+ import { ethers } from "ethers" ;
9+
10+ // CNS Token ABI - including Transfer event and balanceOf function
11+ const CNS_TOKEN_ABI = [
12+ {
13+ "anonymous" : false ,
14+ "inputs" : [
15+ { "indexed" : true , "name" : "from" , "type" : "address" } ,
16+ { "indexed" : true , "name" : "to" , "type" : "address" } ,
17+ { "indexed" : false , "name" : "value" , "type" : "uint256" }
18+ ] ,
19+ "name" : "Transfer" ,
20+ "type" : "event"
21+ } ,
22+ {
23+ "inputs" : [
24+ { "internalType" : "address" , "name" : "account" , "type" : "address" }
25+ ] ,
26+ "name" : "balanceOf" ,
27+ "outputs" : [
28+ { "internalType" : "uint256" , "name" : "" , "type" : "uint256" }
29+ ] ,
30+ "stateMutability" : "view" ,
31+ "type" : "function"
32+ }
33+ ] ;
34+
35+ const CNS_TOKEN_ADDRESS = process . env . CNS_TOKEN_ADDRESS ;
36+
37+ const topHoldersProvider : Provider = {
38+ get : async ( _runtime : IAgentRuntime , _message : Memory , _state ?: State ) => {
39+ elizaLogger . info ( `⏳ Provider: Fetch top CNS token holders` ) ;
40+ try {
41+ const provider = new ethers . JsonRpcProvider ( process . env . EVM_PROVIDER_URL ) ;
42+ const contract = new ethers . Contract ( CNS_TOKEN_ADDRESS , CNS_TOKEN_ABI , provider ) ;
43+
44+ // Get all Transfer events
45+ const filter = contract . filters . Transfer ( ) ;
46+ const events = await contract . queryFilter ( filter , 0 , "latest" ) ;
47+
48+ // Create a map to track balances
49+ const balances = new Map < string , bigint > ( ) ;
50+
51+ // Process all transfer events to calculate current balances
52+ for ( const event of events ) {
53+ if ( event instanceof ethers . EventLog ) {
54+ const from = event . args [ 0 ] ;
55+ const to = event . args [ 1 ] ;
56+ const value = event . args [ 2 ] ;
57+
58+ // Subtract from sender
59+ if ( from !== ethers . ZeroAddress ) {
60+ balances . set ( from , ( balances . get ( from ) || BigInt ( 0 ) ) - value ) ;
61+ }
62+ // Add to recipient
63+ balances . set ( to , ( balances . get ( to ) || BigInt ( 0 ) ) + value ) ;
64+ }
65+ }
66+
67+ // Convert to array and sort by balance
68+ const sortedHolders = Array . from ( balances . entries ( ) )
69+ . filter ( ( [ address , balance ] ) => balance > BigInt ( 0 ) ) // Filter out zero balances
70+ . sort ( ( a , b ) => ( b [ 1 ] > a [ 1 ] ? 1 : - 1 ) )
71+ . slice ( 0 , 20 ) ; // Get top 20
72+
73+ // Format the response
74+ const formattedHolders = sortedHolders
75+ . map ( ( [ address , balance ] , index ) => {
76+ const formattedBalance = ethers . formatUnits ( balance , 18 ) ;
77+ return `${ index + 1 } . ${ address } : ${ formattedBalance } CNS` ;
78+ } )
79+ . join ( "\n" ) ;
80+ console . log ( `**Top 20 CNS Token Holders:**\n${ formattedHolders } ` ) ;
81+ return `**Top 20 CNS Token Holders:**\n${ formattedHolders } ` ;
82+ } catch ( error ) {
83+ elizaLogger . error ( `Error fetching top holders: ${ error } ` ) ;
84+ return "Failed to fetch top CNS token holders. Please try again later." ;
85+ }
86+ } ,
87+ } ;
88+
89+ export { topHoldersProvider } ;
0 commit comments