1
+ import { Connection } from "@solana/web3.js" ;
1
2
// import fetch from "cross-fetch";
3
+ import { IAgentRuntime , Memory , Provider , State } from "../core/types" ;
4
+ import settings from "../core/settings" ;
2
5
import BigNumber from "bignumber.js" ;
3
- import * as fs from "fs" ;
4
- import NodeCache from "node-cache" ;
5
- import * as path from "path" ;
6
- import settings from "../core/settings.ts" ;
7
- import { IAgentRuntime , Memory , Provider , State } from "../core/types.ts" ;
8
6
import {
9
- DexScreenerData ,
10
- HolderData ,
11
7
ProcessedTokenData ,
12
8
TokenSecurityData ,
13
9
TokenTradeData ,
14
- } from "../types/token.ts" ;
15
- import { fileURLToPath } from "url" ;
10
+ DexScreenerData ,
11
+ // DexScreenerPair,
12
+ HolderData ,
13
+ } from "../types/token" ;
14
+ import NodeCache from "node-cache" ;
15
+ import * as fs from "fs" ;
16
+ import * as path from "path" ;
16
17
17
18
const PROVIDER_CONFIG = {
18
19
BIRDEYE_API : "https://public-api.birdeye.so" ,
@@ -39,25 +40,28 @@ export class TokenProvider {
39
40
private tokenAddress : string
40
41
) {
41
42
this . cache = new NodeCache ( { stdTTL : 300 } ) ; // 5 minutes cache
42
- const __filename = fileURLToPath ( import . meta. url ) ;
43
-
44
- const __dirname = path . dirname ( __filename ) ;
45
-
46
- this . cacheDir = path . join ( __dirname , "../../tokencache" ) ;
43
+ this . cacheDir = path . join ( __dirname , "cache" ) ;
47
44
if ( ! fs . existsSync ( this . cacheDir ) ) {
48
45
fs . mkdirSync ( this . cacheDir ) ;
49
46
}
50
47
}
51
48
52
49
private readCacheFromFile < T > ( cacheKey : string ) : T | null {
53
50
const filePath = path . join ( this . cacheDir , `${ cacheKey } .json` ) ;
51
+ console . log ( { filePath } ) ;
54
52
if ( fs . existsSync ( filePath ) ) {
55
53
const fileContent = fs . readFileSync ( filePath , "utf-8" ) ;
56
54
const parsed = JSON . parse ( fileContent ) ;
57
55
const now = Date . now ( ) ;
58
56
if ( now < parsed . expiry ) {
57
+ console . log (
58
+ `Reading cached data from file for key: ${ cacheKey } `
59
+ ) ;
59
60
return parsed . data as T ;
60
61
} else {
62
+ console . log (
63
+ `Cache expired for key: ${ cacheKey } . Deleting file.`
64
+ ) ;
61
65
fs . unlinkSync ( filePath ) ;
62
66
}
63
67
}
@@ -71,6 +75,7 @@ export class TokenProvider {
71
75
expiry : Date . now ( ) + 300000 , // 5 minutes in milliseconds
72
76
} ;
73
77
fs . writeFileSync ( filePath , JSON . stringify ( cacheData ) , "utf-8" ) ;
78
+ console . log ( `Cached data written to file for key: ${ cacheKey } ` ) ;
74
79
}
75
80
76
81
private getCachedData < T > ( cacheKey : string ) : T | null {
@@ -102,6 +107,7 @@ export class TokenProvider {
102
107
private async fetchWithRetry (
103
108
url : string ,
104
109
options : RequestInit = { }
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
111
) : Promise < any > {
106
112
let lastError : Error ;
107
113
@@ -127,9 +133,11 @@ export class TokenProvider {
127
133
const data = await response . json ( ) ;
128
134
return data ;
129
135
} catch ( error ) {
136
+ console . error ( `Attempt ${ i + 1 } failed:` , error ) ;
130
137
lastError = error as Error ;
131
138
if ( i < PROVIDER_CONFIG . MAX_RETRIES - 1 ) {
132
139
const delay = PROVIDER_CONFIG . RETRY_DELAY * Math . pow ( 2 , i ) ;
140
+ console . log ( `Waiting ${ delay } ms before retrying...` ) ;
133
141
await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
134
142
continue ;
135
143
}
@@ -147,6 +155,9 @@ export class TokenProvider {
147
155
const cacheKey = `tokenSecurity_${ this . tokenAddress } ` ;
148
156
const cachedData = this . getCachedData < TokenSecurityData > ( cacheKey ) ;
149
157
if ( cachedData ) {
158
+ console . log (
159
+ `Returning cached token security data for ${ this . tokenAddress } .`
160
+ ) ;
150
161
return cachedData ;
151
162
}
152
163
const url = `${ PROVIDER_CONFIG . BIRDEYE_API } ${ PROVIDER_CONFIG . TOKEN_SECURITY_ENDPOINT } ${ this . tokenAddress } ` ;
@@ -165,14 +176,18 @@ export class TokenProvider {
165
176
top10HolderPercent : data . data . top10HolderPercent ,
166
177
} ;
167
178
this . setCachedData ( cacheKey , security ) ;
179
+ console . log ( `Token security data cached for ${ this . tokenAddress } .` ) ;
168
180
169
181
return security ;
170
182
}
171
183
172
- async fetchTokenTradeData ( runtime : IAgentRuntime ) : Promise < TokenTradeData > {
184
+ async fetchTokenTradeData ( ) : Promise < TokenTradeData > {
173
185
const cacheKey = `tokenTradeData_${ this . tokenAddress } ` ;
174
186
const cachedData = this . getCachedData < TokenTradeData > ( cacheKey ) ;
175
187
if ( cachedData ) {
188
+ console . log (
189
+ `Returning cached token trade data for ${ this . tokenAddress } .`
190
+ ) ;
176
191
return cachedData ;
177
192
}
178
193
@@ -181,7 +196,7 @@ export class TokenProvider {
181
196
method : "GET" ,
182
197
headers : {
183
198
accept : "application/json" ,
184
- "X-API-KEY" : runtime . getSetting ( " BIRDEYE_API_KEY" ) || "" ,
199
+ "X-API-KEY" : settings . BIRDEYE_API_KEY || "" ,
185
200
} ,
186
201
} ;
187
202
@@ -405,11 +420,15 @@ export class TokenProvider {
405
420
const cacheKey = `dexScreenerData_${ this . tokenAddress } ` ;
406
421
const cachedData = this . getCachedData < DexScreenerData > ( cacheKey ) ;
407
422
if ( cachedData ) {
423
+ console . log ( "Returning cached DexScreener data." ) ;
408
424
return cachedData ;
409
425
}
410
426
411
427
const url = `https://api.dexscreener.com/latest/dex/search?q=${ this . tokenAddress } ` ;
412
428
try {
429
+ console . log (
430
+ `Fetching DexScreener data for token: ${ this . tokenAddress } `
431
+ ) ;
413
432
const data = await fetch ( url )
414
433
. then ( ( res ) => res . json ( ) )
415
434
. catch ( ( err ) => {
@@ -488,6 +507,7 @@ export class TokenProvider {
488
507
const cacheKey = `holderList_${ this . tokenAddress } ` ;
489
508
const cachedData = this . getCachedData < HolderData [ ] > ( cacheKey ) ;
490
509
if ( cachedData ) {
510
+ console . log ( "Returning cached holder list." ) ;
491
511
return cachedData ;
492
512
}
493
513
@@ -497,8 +517,10 @@ export class TokenProvider {
497
517
let cursor ;
498
518
//HELIOUS_API_KEY needs to be added
499
519
const url = `https://mainnet.helius-rpc.com/?api-key=${ settings . HELIOUS_API_KEY || "" } ` ;
520
+ console . log ( { url } ) ;
500
521
501
522
try {
523
+ // eslint-disable-next-line no-constant-condition
502
524
while ( true ) {
503
525
const params = {
504
526
limit : limit ,
@@ -509,7 +531,7 @@ export class TokenProvider {
509
531
if ( cursor != undefined ) {
510
532
params . cursor = cursor ;
511
533
}
512
-
534
+ console . log ( `Fetching holders - Page ${ page } ` ) ;
513
535
if ( page > 2 ) {
514
536
break ;
515
537
}
@@ -534,9 +556,17 @@ export class TokenProvider {
534
556
! data . result . token_accounts ||
535
557
data . result . token_accounts . length === 0
536
558
) {
559
+ console . log (
560
+ `No more holders found. Total pages fetched: ${ page - 1 } `
561
+ ) ;
537
562
break ;
538
563
}
539
564
565
+ console . log (
566
+ `Processing ${ data . result . token_accounts . length } holders from page ${ page } `
567
+ ) ;
568
+
569
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
540
570
data . result . token_accounts . forEach ( ( account : any ) => {
541
571
const owner = account . owner ;
542
572
const balance = parseFloat ( account . amount ) ;
@@ -561,6 +591,8 @@ export class TokenProvider {
561
591
balance : balance . toString ( ) ,
562
592
} ) ) ;
563
593
594
+ console . log ( `Total unique holders fetched: ${ holders . length } ` ) ;
595
+
564
596
// Cache the result
565
597
this . setCachedData ( cacheKey , holders ) ;
566
598
@@ -620,27 +652,47 @@ export class TokenProvider {
620
652
}
621
653
}
622
654
623
- async getProcessedTokenData (
624
- runtime : IAgentRuntime
625
- ) : Promise < ProcessedTokenData > {
655
+ async getProcessedTokenData ( ) : Promise < ProcessedTokenData > {
626
656
try {
657
+ console . log (
658
+ `Fetching security data for token: ${ this . tokenAddress } `
659
+ ) ;
627
660
const security = await this . fetchTokenSecurity ( ) ;
628
661
629
- const tradeData = await this . fetchTokenTradeData ( runtime ) ;
662
+ console . log ( `Fetching trade data for token: ${ this . tokenAddress } ` ) ;
663
+ const tradeData = await this . fetchTokenTradeData ( ) ;
630
664
665
+ console . log (
666
+ `Fetching DexScreener data for token: ${ this . tokenAddress } `
667
+ ) ;
631
668
const dexData = await this . fetchDexScreenerData ( ) ;
632
669
670
+ console . log (
671
+ `Analyzing holder distribution for token: ${ this . tokenAddress } `
672
+ ) ;
633
673
const holderDistributionTrend =
634
674
await this . analyzeHolderDistribution ( tradeData ) ;
635
675
676
+ console . log (
677
+ `Filtering high-value holders for token: ${ this . tokenAddress } `
678
+ ) ;
636
679
const highValueHolders =
637
680
await this . filterHighValueHolders ( tradeData ) ;
638
681
682
+ console . log (
683
+ `Checking recent trades for token: ${ this . tokenAddress } `
684
+ ) ;
639
685
const recentTrades = await this . checkRecentTrades ( tradeData ) ;
640
686
687
+ console . log (
688
+ `Counting high-supply holders for token: ${ this . tokenAddress } `
689
+ ) ;
641
690
const highSupplyHoldersCount =
642
691
await this . countHighSupplyHolders ( security ) ;
643
692
693
+ console . log (
694
+ `Determining DexScreener listing status for token: ${ this . tokenAddress } `
695
+ ) ;
644
696
const isDexScreenerListed = dexData . pairs . length > 0 ;
645
697
const isDexScreenerPaid = dexData . pairs . some (
646
698
( pair ) => pair . boosts && pair . boosts . active > 0
@@ -658,6 +710,7 @@ export class TokenProvider {
658
710
isDexScreenerPaid,
659
711
} ;
660
712
713
+ // console.log("Processed token data:", processedData);
661
714
return processedData ;
662
715
} catch ( error ) {
663
716
console . error ( "Error processing token data:" , error ) ;
@@ -725,12 +778,14 @@ export class TokenProvider {
725
778
}
726
779
output += `\n` ;
727
780
781
+ console . log ( "Formatted token data:" , output ) ;
728
782
return output ;
729
783
}
730
784
731
- async getFormattedTokenReport ( runtime : IAgentRuntime ) : Promise < string > {
785
+ async getFormattedTokenReport ( ) : Promise < string > {
732
786
try {
733
- const processedData = await this . getProcessedTokenData ( runtime ) ;
787
+ console . log ( "Generating formatted token report..." ) ;
788
+ const processedData = await this . getProcessedTokenData ( ) ;
734
789
return this . formatTokenData ( processedData ) ;
735
790
} catch ( error ) {
736
791
console . error ( "Error generating token report:" , error ) ;
@@ -740,15 +795,18 @@ export class TokenProvider {
740
795
}
741
796
742
797
const tokenAddress = PROVIDER_CONFIG . TOKEN_ADDRESSES . Example ;
798
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
799
+ const connection = new Connection ( PROVIDER_CONFIG . DEFAULT_RPC ) ;
743
800
const tokenProvider : Provider = {
744
801
get : async (
802
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
745
803
runtime : IAgentRuntime ,
746
804
_message : Memory ,
747
805
_state ?: State
748
806
) : Promise < string > => {
749
807
try {
750
808
const provider = new TokenProvider ( tokenAddress ) ;
751
- return provider . getFormattedTokenReport ( runtime ) ;
809
+ return provider . getFormattedTokenReport ( ) ;
752
810
} catch ( error ) {
753
811
console . error ( "Error fetching token data:" , error ) ;
754
812
return "Unable to fetch token information. Please try again later." ;
0 commit comments