From 667126549df9c9de1657e57822707f1ad7c76f78 Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Tue, 29 Oct 2024 21:12:50 +0200 Subject: [PATCH 1/6] trustScore database and provider Signed-off-by: MarcoMandar --- src/adapters/trustScoreDatabase.ts | 661 ++++++++++++++++++++++++++++ src/providers/token.ts | 5 +- src/providers/trustScoreProvider.ts | 217 +++++++++ 3 files changed, 880 insertions(+), 3 deletions(-) create mode 100644 src/adapters/trustScoreDatabase.ts create mode 100644 src/providers/trustScoreProvider.ts diff --git a/src/adapters/trustScoreDatabase.ts b/src/adapters/trustScoreDatabase.ts new file mode 100644 index 00000000..614a7b25 --- /dev/null +++ b/src/adapters/trustScoreDatabase.ts @@ -0,0 +1,661 @@ +// src/adapters/sqlite/trustScoreDatabase.ts + +import { Database } from "better-sqlite3"; +import { v4 as uuidv4 } from "uuid"; +import { load } from "./sqlite/sqlite_vec.ts"; + +// Define interfaces +export interface Recommender { + id: string; // UUID + address: string; + solanaPubkey?: string; + telegramId?: string; + discordId?: string; + twitterId?: string; + ip?: string; +} + +export interface RecommenderMetrics { + recommenderId: string; + overallTrustScore: number; + totalRecommendations: number; + successfulRecs: number; + avgTokenPerformance: number; + riskScore: number; + consistencyScore: number; + lastUpdated: Date; +} + +export interface TokenPerformance { + tokenAddress: string; + priceChange24h: number; + volumeChange24h: number; + trade_24h_change: number; + liquidity: number; + liquidityChange24h: number; + holderChange24h: number; + rugPull: boolean; + isScam: boolean; + marketCapChange24h: number; + sustainedGrowth: boolean; + rapidDump: boolean; + suspiciousVolume: boolean; + lastUpdated: Date; +} + +export interface TokenRecommendation { + id: string; // UUID + recommenderId: string; + tokenAddress: string; + timestamp: Date; + initialMarketCap?: number; + initialLiquidity?: number; + initialPrice?: number; +} +export interface RecommenderMetricsHistory { + historyId: string; // UUID + recommenderId: string; + overallTrustScore: number; + totalRecommendations: number; + successfulRecs: number; + avgTokenPerformance: number; + riskScore: number; + consistencyScore: number; + recordedAt: Date; +} + +interface RecommenderMetricsRow { + recommender_id: string; + overall_trust_score: number; + total_recommendations: number; + successful_recs: number; + avg_token_performance: number; + risk_score: number; + consistency_score: number; + last_updated: string; +} + +interface TokenPerformanceRow { + token_address: string; + price_change_24h: number; + volume_change_24h: number; + trade_24h_change: number; + liquidity: number; + liquidity_change_24h: number; + holder_change_24h: number; + rug_pull: number; + is_scam: number; + market_cap_change24h: number; + sustained_growth: number; + rapid_dump: number; + suspicious_volume: number; + last_updated: string; +} + +export class TrustScoreDatabase { + private db: Database; + + constructor(db: Database) { + this.db = db; + load(db); + // check if the tables exist, if not create them + const tables = this.db + .prepare( + "SELECT name FROM sqlite_master WHERE type='table' AND name IN ('recommenders', 'recommender_metrics', 'token_performance', 'token_recommendations', 'recommender_metrics_history');" + ) + .all(); + if (tables.length !== 5) { + this.initializeSchema(); + } + } + + private initializeSchema() { + // Enable Foreign Key Support + this.db.exec(`PRAGMA foreign_keys = ON;`); + + // Create Recommenders Table + this.db.exec(` + CREATE TABLE IF NOT EXISTS recommenders ( + id TEXT PRIMARY KEY, + address TEXT UNIQUE NOT NULL, + solana_pubkey TEXT UNIQUE, + telegram_id TEXT UNIQUE, + discord_id TEXT UNIQUE, + twitter_id TEXT UNIQUE, + ip TEXT + ); + `); + + // Create RecommenderMetrics Table + this.db.exec(` + CREATE TABLE IF NOT EXISTS recommender_metrics ( + recommender_id TEXT PRIMARY KEY, + overall_trust_score REAL DEFAULT 0, + total_recommendations INTEGER DEFAULT 0, + successful_recs INTEGER DEFAULT 0, + avg_token_performance REAL DEFAULT 0, + risk_score REAL DEFAULT 0, + consistency_score REAL DEFAULT 0, + last_updated DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE + ); + `); + + // Create TokenPerformance Table + this.db.exec(` + CREATE TABLE IF NOT EXISTS token_performance ( + token_address TEXT PRIMARY KEY, + price_change_24h REAL, + volume_change_24h REAL, + trade_24h_change REAL, + liquidity REAL, + liquidity_change_24h REAL, + holder_change_24h REAL, + rug_pull BOOLEAN DEFAULT FALSE, + is_scam BOOLEAN DEFAULT FALSE, + market_cap_change24h REAL, + sustained_growth BOOLEAN DEFAULT FALSE, + rapid_dump BOOLEAN DEFAULT FALSE, + suspicious_volume BOOLEAN DEFAULT FALSE, + last_updated DATETIME DEFAULT CURRENT_TIMESTAMP + ); + `); + + // Create TokenRecommendations Table + this.db.exec(` + CREATE TABLE IF NOT EXISTS token_recommendations ( + id TEXT PRIMARY KEY, + recommender_id TEXT NOT NULL, + token_address TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + initial_market_cap REAL, + initial_liquidity REAL, + initial_price REAL, + FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE, + FOREIGN KEY (token_address) REFERENCES token_performance(token_address) ON DELETE CASCADE + ); + `); + + // ----- Create RecommenderMetricsHistory Table ----- + this.db.exec(` + CREATE TABLE IF NOT EXISTS recommender_metrics_history ( + history_id TEXT PRIMARY KEY, + recommender_id TEXT NOT NULL, + overall_trust_score REAL, + total_recommendations INTEGER, + successful_recs INTEGER, + avg_token_performance REAL, + risk_score REAL, + consistency_score REAL, + recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE + ); + `); + } + + /** + * Adds a new recommender to the database. + * @param recommender Recommender object + * @returns boolean indicating success + */ + addRecommender(recommender: Recommender): string | null { + const sql = ` + INSERT INTO recommenders (id, address, solana_pubkey, telegram_id, discord_id, twitter_id, ip) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(address) DO NOTHING; + `; + try { + const id = recommender.id || uuidv4(); + const result = this.db + .prepare(sql) + .run( + id, + recommender.address, + recommender.solanaPubkey || null, + recommender.telegramId || null, + recommender.discordId || null, + recommender.twitterId || null, + recommender.ip || null + ); + return result.changes > 0 ? id : null; + } catch (error) { + console.error("Error adding recommender:", error); + return null; + } + } + + /** + * Retrieves a recommender by any identifier. + * @param identifier Any of the recommender's identifiers + * @returns Recommender object or null + */ + getRecommender(identifier: string): Recommender | null { + const sql = ` + SELECT * FROM recommenders + WHERE id = ? OR address = ? OR solana_pubkey = ? OR telegram_id = ? OR discord_id = ? OR twitter_id = ?; + `; + const recommender = this.db + .prepare(sql) + .get( + identifier, + identifier, + identifier, + identifier, + identifier, + identifier + ) as Recommender | undefined; + return recommender || null; + } + + /** + * Initializes metrics for a recommender if not present. + * @param recommenderId Recommender's UUID + */ + initializeRecommenderMetrics(recommenderId: string): boolean { + const sql = ` + INSERT OR IGNORE INTO recommender_metrics (recommender_id) + VALUES (?); + `; + try { + const result = this.db.prepare(sql).run(recommenderId); + return result.changes > 0; + } catch (error) { + console.error("Error initializing recommender metrics:", error); + return false; + } + } + + /** + * Retrieves metrics for a recommender. + * @param recommenderId Recommender's UUID + * @returns RecommenderMetrics object or null + */ + getRecommenderMetrics(recommenderId: string): RecommenderMetrics | null { + const sql = `SELECT * FROM recommender_metrics WHERE recommender_id = ?;`; + const row = this.db.prepare(sql).get(recommenderId) as + | RecommenderMetricsRow + | undefined; + if (!row) return null; + + return { + recommenderId: row.recommender_id, + overallTrustScore: row.overall_trust_score, + totalRecommendations: row.total_recommendations, + successfulRecs: row.successful_recs, + avgTokenPerformance: row.avg_token_performance, + riskScore: row.risk_score, + consistencyScore: row.consistency_score, + lastUpdated: new Date(row.last_updated), + }; + } + + /** + * Logs the current metrics of a recommender into the history table. + * @param recommenderId Recommender's UUID + */ + logRecommenderMetricsHistory(recommenderId: string): void { + // Retrieve current metrics + const currentMetrics = this.getRecommenderMetrics(recommenderId); + if (!currentMetrics) { + console.warn(`No metrics found for recommender ID: ${recommenderId}`); + return; + } + + // Create a history entry + const history: RecommenderMetricsHistory = { + historyId: uuidv4(), + recommenderId: currentMetrics.recommenderId, + overallTrustScore: currentMetrics.overallTrustScore, + totalRecommendations: currentMetrics.totalRecommendations, + successfulRecs: currentMetrics.successfulRecs, + avgTokenPerformance: currentMetrics.avgTokenPerformance, + riskScore: currentMetrics.riskScore, + consistencyScore: currentMetrics.consistencyScore, + recordedAt: new Date(), // Current timestamp + }; + + // Insert into recommender_metrics_history table + const sql = ` + INSERT INTO recommender_metrics_history ( + history_id, + recommender_id, + overall_trust_score, + total_recommendations, + successful_recs, + avg_token_performance, + risk_score, + consistency_score, + recorded_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + `; + try { + this.db + .prepare(sql) + .run( + history.historyId, + history.recommenderId, + history.overallTrustScore, + history.totalRecommendations, + history.successfulRecs, + history.avgTokenPerformance, + history.riskScore, + history.consistencyScore, + history.recordedAt.toISOString() + ); + console.log( + `Logged metrics history for recommender ID: ${recommenderId}` + ); + } catch (error) { + console.error("Error logging recommender metrics history:", error); + } + } + + /** + * Updates metrics for a recommender. + * @param metrics RecommenderMetrics object + */ + updateRecommenderMetrics(metrics: RecommenderMetrics): void { + // Log current metrics before updating + this.logRecommenderMetricsHistory(metrics.recommenderId); + + const sql = ` + UPDATE recommender_metrics + SET overall_trust_score = ?, + total_recommendations = ?, + successful_recs = ?, + avg_token_performance = ?, + risk_score = ?, + consistency_score = ?, + last_updated = CURRENT_TIMESTAMP + WHERE recommender_id = ?; + `; + try { + this.db + .prepare(sql) + .run( + metrics.overallTrustScore, + metrics.totalRecommendations, + metrics.successfulRecs, + metrics.avgTokenPerformance, + metrics.riskScore, + metrics.consistencyScore, + metrics.recommenderId + ); + console.log( + `Updated metrics for recommender ID: ${metrics.recommenderId}` + ); + } catch (error) { + console.error("Error updating recommender metrics:", error); + } + } + + // ----- TokenPerformance Methods ----- + + /** + * Adds or updates token performance metrics. + * @param performance TokenPerformance object + */ + upsertTokenPerformance(performance: TokenPerformance): boolean { + const sql = ` + INSERT INTO token_performance ( + token_address, + price_change_24h, + volume_change_24h, + trade_24h_change, + liquidity, + liquidity_change_24h, + holder_change_24h, + rug_pull, + is_scam, + market_cap_change24h, + sustained_growth, + rapid_dump, + suspicious_volume, + last_updated + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + ON CONFLICT(token_address) DO UPDATE SET + price_change_24h = excluded.price_change_24h, + volume_change_24h = excluded.volume_change_24h, + trade_24h_change = excluded.trade_24h_change, + liquidity = excluded.liquidity, + liquidity_change_24h = excluded.liquidity_change_24h, + holder_change_24h = excluded.holder_change_24h, + rug_pull = excluded.rug_pull, + is_scam = excluded.is_scam, + market_cap_change24h = excluded.market_cap_change24h, + sustained_growth = excluded.sustained_growth, + rapid_dump = excluded.rapid_dump, + suspicious_volume = excluded.suspicious_volume, + last_updated = CURRENT_TIMESTAMP; + `; + try { + this.db.prepare(sql).run( + performance.tokenAddress, + performance.priceChange24h, + performance.volumeChange24h, + performance.liquidityChange24h, + performance.holderChange24h, // Ensure column name matches schema + performance.rugPull ? 1 : 0, + performance.isScam ? 1 : 0, + performance.marketCapChange24h, + performance.sustainedGrowth ? 1 : 0, + performance.rapidDump ? 1 : 0, + performance.suspiciousVolume ? 1 : 0 + ); + console.log(`Upserted token performance for ${performance.tokenAddress}`); + return true; + } catch (error) { + console.error("Error upserting token performance:", error); + return false; + } + } + + /** + * Retrieves token performance metrics. + * @param tokenAddress Token's address + * @returns TokenPerformance object or null + */ + getTokenPerformance(tokenAddress: string): TokenPerformance | null { + const sql = `SELECT * FROM token_performance WHERE token_address = ?;`; + const row = this.db.prepare(sql).get(tokenAddress) as + | TokenPerformanceRow + | undefined; + if (!row) return null; + + return { + tokenAddress: row.token_address, + priceChange24h: row.price_change_24h, + volumeChange24h: row.volume_change_24h, + trade_24h_change: row.trade_24h_change, + liquidity: row.liquidity, + liquidityChange24h: row.liquidity_change_24h, + holderChange24h: row.holder_change_24h, + rugPull: row.rug_pull === 1, + isScam: row.is_scam === 1, + marketCapChange24h: row.market_cap_change24h, + sustainedGrowth: row.sustained_growth === 1, + rapidDump: row.rapid_dump === 1, + suspiciousVolume: row.suspicious_volume === 1, + lastUpdated: new Date(row.last_updated), + }; + } + + // ----- TokenRecommendations Methods ----- + + /** + * Adds a new token recommendation. + * @param recommendation TokenRecommendation object + * @returns boolean indicating success + */ + addTokenRecommendation(recommendation: TokenRecommendation): boolean { + const sql = ` + INSERT INTO token_recommendations ( + id, + recommender_id, + token_address, + timestamp, + initial_market_cap, + initial_liquidity, + initial_price + ) VALUES (?, ?, ?, ?, ?, ?, ?); + `; + try { + this.db + .prepare(sql) + .run( + recommendation.id || uuidv4(), + recommendation.recommenderId, + recommendation.tokenAddress, + recommendation.timestamp || new Date(), + recommendation.initialMarketCap || null, + recommendation.initialLiquidity || null, + recommendation.initialPrice || null + ); + return true; + } catch (error) { + console.error("Error adding token recommendation:", error); + return false; + } + } + + /** + * Retrieves all recommendations made by a recommender. + * @param recommenderId Recommender's UUID + * @returns Array of TokenRecommendation objects + */ + getRecommendationsByRecommender( + recommenderId: string + ): TokenRecommendation[] { + const sql = `SELECT * FROM token_recommendations WHERE recommender_id = ? ORDER BY timestamp DESC;`; + const rows = this.db.prepare(sql).all(recommenderId) as Array<{ + id: string; + recommender_id: string; + token_address: string; + timestamp: string; + initial_market_cap: number | null; + initial_liquidity: number | null; + initial_price: number | null; + }>; + + return rows.map((row) => ({ + id: row.id, + recommenderId: row.recommender_id, + tokenAddress: row.token_address, + timestamp: new Date(row.timestamp), + initialMarketCap: row.initial_market_cap, + initialLiquidity: row.initial_liquidity, + initialPrice: row.initial_price, + })); + } + + /** + * Retrieves all recommendations for a specific token. + * @param tokenAddress Token's address + * @returns Array of TokenRecommendation objects + */ + getRecommendationsByToken(tokenAddress: string): TokenRecommendation[] { + const sql = `SELECT * FROM token_recommendations WHERE token_address = ? ORDER BY timestamp DESC;`; + const rows = this.db.prepare(sql).all(tokenAddress) as Array<{ + id: string; + recommender_id: string; + token_address: string; + timestamp: string; + initial_market_cap: number | null; + initial_liquidity: number | null; + initial_price: number | null; + }>; + + return rows.map((row) => ({ + id: row.id, + recommenderId: row.recommender_id, + tokenAddress: row.token_address, + timestamp: new Date(row.timestamp), + initialMarketCap: row.initial_market_cap ?? undefined, + initialLiquidity: row.initial_liquidity ?? undefined, + initialPrice: row.initial_price ?? undefined, + })); + } + + /** + * Retrieves all recommendations within a specific timeframe. + * @param startDate Start date + * @param endDate End date + * @returns Array of TokenRecommendation objects + */ + getRecommendationsByDateRange( + startDate: Date, + endDate: Date + ): TokenRecommendation[] { + const sql = ` + SELECT * FROM token_recommendations + WHERE timestamp BETWEEN ? AND ? + ORDER BY timestamp DESC; + `; + const rows = this.db + .prepare(sql) + .all(startDate.toISOString(), endDate.toISOString()) as Array<{ + id: string; + recommender_id: string; + token_address: string; + timestamp: string; + initial_market_cap: number | null; + initial_liquidity: number | null; + initial_price: number | null; + }>; + + return rows.map((row) => ({ + id: row.id, + recommenderId: row.recommender_id, + tokenAddress: row.token_address, + timestamp: new Date(row.timestamp), + initialMarketCap: row.initial_market_cap ?? undefined, + initialLiquidity: row.initial_liquidity ?? undefined, + initialPrice: row.initial_price ?? undefined, + })); + } + + /** + * Retrieves historical metrics for a recommender. + * @param recommenderId Recommender's UUID + * @returns Array of RecommenderMetricsHistory objects + */ + getRecommenderMetricsHistory( + recommenderId: string + ): RecommenderMetricsHistory[] { + const sql = ` + SELECT * FROM recommender_metrics_history + WHERE recommender_id = ? + ORDER BY recorded_at DESC; + `; + const rows = this.db.prepare(sql).all(recommenderId) as Array<{ + history_id: string; + recommender_id: string; + overall_trust_score: number; + total_recommendations: number; + successful_recs: number; + avg_token_performance: number; + risk_score: number; + consistency_score: number; + recorded_at: string; + }>; + + return rows.map((row) => ({ + historyId: row.history_id, + recommenderId: row.recommender_id, + overallTrustScore: row.overall_trust_score, + totalRecommendations: row.total_recommendations, + successfulRecs: row.successful_recs, + avgTokenPerformance: row.avg_token_performance, + riskScore: row.risk_score, + consistencyScore: row.consistency_score, + recordedAt: new Date(row.recorded_at), + })); + } + + /** + * Close the database connection gracefully. + */ + closeConnection(): void { + this.db.close(); + } +} diff --git a/src/providers/token.ts b/src/providers/token.ts index 315f9f3c..db0c326f 100644 --- a/src/providers/token.ts +++ b/src/providers/token.ts @@ -1,9 +1,8 @@ -import { Connection, PublicKey, ParsedAccountData } from "@solana/web3.js"; +import { Connection } from "@solana/web3.js"; // import fetch from "cross-fetch"; import { IAgentRuntime, Memory, Provider, State } from "../core/types"; import settings from "../core/settings"; import BigNumber from "bignumber.js"; -import { TOKEN_PROGRAM_ID, AccountLayout } from "@solana/spl-token"; import { ProcessedTokenData, TokenSecurityData, @@ -757,7 +756,7 @@ const tokenProvider: Provider = { _state?: State ): Promise => { try { - const provider = new TokenProvider(/*connection,*/ tokenAddress); + const provider = new TokenProvider(tokenAddress); return provider.getFormattedTokenReport(); } catch (error) { console.error("Error fetching token data:", error); diff --git a/src/providers/trustScoreProvider.ts b/src/providers/trustScoreProvider.ts new file mode 100644 index 00000000..b04a9a16 --- /dev/null +++ b/src/providers/trustScoreProvider.ts @@ -0,0 +1,217 @@ +import { + ProcessedTokenData, + TokenSecurityData, + TokenTradeData, + DexScreenerData, + DexScreenerPair, + HolderData, +} from "../types/token"; +import { Connection } from "@solana/web3.js"; +import { TokenProvider } from "./token"; +import { + TrustScoreDatabase, + RecommenderMetrics, + TokenPerformance, +} from "../adapters/trustScoreDatabase"; + +export class TrustScoreProvider { + private tokenProvider: TokenProvider; + private trustScoreDb: TrustScoreDatabase; + + constructor(tokenProvider: TokenProvider, trustScoreDb: TrustScoreDatabase) { + this.tokenProvider = tokenProvider; + this.trustScoreDb = trustScoreDb; + } + /** + * Generates and saves trust score based on processed token data and user recommendations. + * @param tokenAddress The address of the token to analyze. + * @param recommenderId The UUID of the recommender. + * @returns An object containing TokenPerformance and RecommenderMetrics. + */ + async generateTrustScore( + tokenAddress: string, + recommenderId: string + ): Promise<{ + tokenPerformance: TokenPerformance; + recommenderMetrics: RecommenderMetrics; + }> { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + const recommenderMetrics = + await this.trustScoreDb.getRecommenderMetrics(recommenderId); + + const isRapidDump = await this.isRapidDump(tokenAddress); + const sustainedGrowth = await this.sustainedGrowth(tokenAddress); + const suspiciousVolume = await this.suspiciousVolume(tokenAddress); + + return { + tokenPerformance: { + tokenAddress: + processedData.dexScreenerData.pairs[0]?.baseToken.address || "", + priceChange24h: processedData.tradeData.price_change_24h_percent, + volumeChange24h: processedData.tradeData.volume_24h, + trade_24h_change: processedData.tradeData.trade_24h_change_percent, + liquidity: processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + liquidityChange24h: 0, + holderChange24h: + processedData.tradeData.unique_wallet_24h_change_percent, + rugPull: false, // TODO: Implement rug pull detection + isScam: false, // TODO: Implement scam detection + marketCapChange24h: 0, // TODO: Implement market cap change + sustainedGrowth: sustainedGrowth, + rapidDump: isRapidDump, + suspiciousVolume: suspiciousVolume, + lastUpdated: new Date(), + }, + recommenderMetrics: { + recommenderId: recommenderId, + overallTrustScore: recommenderMetrics.overallTrustScore, + totalRecommendations: recommenderMetrics.totalRecommendations, + successfulRecs: recommenderMetrics.successfulRecs, + avgTokenPerformance: recommenderMetrics.avgTokenPerformance, + riskScore: recommenderMetrics.riskScore, + consistencyScore: recommenderMetrics.consistencyScore, + lastUpdated: new Date(), + }, + }; + } + + async updateRecommenderMetrics( + recommenderId: string, + tokenPerformance: TokenPerformance + ): Promise { + const recommenderMetrics = + await this.trustScoreDb.getRecommenderMetrics(recommenderId); + + const totalRecommendations = recommenderMetrics.totalRecommendations + 1; + const successfulRecs = tokenPerformance.rugPull + ? recommenderMetrics.successfulRecs + : recommenderMetrics.successfulRecs + 1; + const avgTokenPerformance = + (recommenderMetrics.avgTokenPerformance * + recommenderMetrics.totalRecommendations + + tokenPerformance.priceChange24h) / + totalRecommendations; + + const overallTrustScore = this.calculateTrustScore( + tokenPerformance, + recommenderMetrics + ); + const riskScore = this.calculateOverallRiskScore( + tokenPerformance, + recommenderMetrics + ); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + const newRecommenderMetrics: RecommenderMetrics = { + recommenderId: recommenderId, + overallTrustScore: overallTrustScore, + totalRecommendations: totalRecommendations, + successfulRecs: successfulRecs, + avgTokenPerformance: avgTokenPerformance, + riskScore: riskScore, + consistencyScore: consistencyScore, + lastUpdated: new Date(), + }; + + await this.trustScoreDb.updateRecommenderMetrics(recommenderMetrics); + } + + calculateTrustScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ): number { + const riskScore = this.calculateRiskScore(tokenPerformance); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + + return (riskScore + consistencyScore) / 2; + } + + calculateOverallRiskScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ) { + const riskScore = this.calculateRiskScore(tokenPerformance); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + + return (riskScore + consistencyScore) / 2; + } + + calculateRiskScore(tokenPerformance: TokenPerformance): number { + let riskScore = 0; + if (tokenPerformance.rugPull) { + riskScore += 10; + } + if (tokenPerformance.isScam) { + riskScore += 10; + } + if (tokenPerformance.rapidDump) { + riskScore += 5; + } + if (tokenPerformance.suspiciousVolume) { + riskScore += 5; + } + return riskScore; + } + + calculateConsistencyScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ): number { + const avgTokenPerformance = recommenderMetrics.avgTokenPerformance; + const priceChange24h = tokenPerformance.priceChange24h; + + return Math.abs(priceChange24h - avgTokenPerformance); + } + + async suspiciousVolume(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const unique_wallet_24h = processedData.tradeData.unique_wallet_24h; + const volume_24h = processedData.tradeData.volume_24h; + const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5; + console.log(`Fetched processed token data for token: ${tokenAddress}`); + return suspiciousVolume; + } + + async sustainedGrowth(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return processedData.tradeData.volume_24h_change_percent > 50; + } + + async isRapidDump(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return processedData.tradeData.trade_24h_change_percent < -50; + } + + async checkTrustScore(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return { + ownerBalance: processedData.security.ownerBalance, + creatorBalance: processedData.security.creatorBalance, + ownerPercentage: processedData.security.ownerPercentage, + creatorPercentage: processedData.security.creatorPercentage, + top10HolderBalance: processedData.security.top10HolderBalance, + top10HolderPercent: processedData.security.top10HolderPercent, + }; + } +} From 80296c812150ad0f8e81d595aa070abbaf31ee0b Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Wed, 30 Oct 2024 16:04:05 +0200 Subject: [PATCH 2/6] add token performance and simulation token performance Signed-off-by: MarcoMandar --- src/adapters/trustScoreDatabase.ts | 299 ++++++++++++++++++++++++++++ src/providers/trustScoreProvider.ts | 136 ++++++++++++- 2 files changed, 434 insertions(+), 1 deletion(-) diff --git a/src/adapters/trustScoreDatabase.ts b/src/adapters/trustScoreDatabase.ts index 614a7b25..3bdf7174 100644 --- a/src/adapters/trustScoreDatabase.ts +++ b/src/adapters/trustScoreDatabase.ts @@ -64,6 +64,31 @@ export interface RecommenderMetricsHistory { recordedAt: Date; } +export interface TradePerformance { + token_address: string; + recommender_id: string; + buy_price: number; + sell_price: number; + buy_timeStamp: string; + sell_timeStamp: string; + buy_amount: number; + sell_amount: number; + buy_sol: number; + received_sol: number; + buy_value_usd: number; + sell_value_usd: number; + profit_usd: number; + profit_percent: number; + buy_market_cap: number; + sell_market_cap: number; + market_cap_change: number; + buy_liquidity: number; + sell_liquidity: number; + liquidity_change: number; + last_updated: string; + rapidDump: boolean; +} + interface RecommenderMetricsRow { recommender_id: string; overall_trust_score: number; @@ -191,6 +216,67 @@ export class TrustScoreDatabase { FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE ); `); + + // ----- Create TradePerformance Tables ----- + this.db.exec(` + CREATE TABLE IF NOT EXISTS trade ( + token_address TEXT NOT NULL, + recommender_id TEXT NOT NULL, + buy_price REAL NOT NULL, + sell_price REAL, + buy_timeStamp TEXT NOT NULL, + sell_timeStamp TEXT, + buy_amount REAL NOT NULL, + sell_amount REAL, + buy_sol REAL NOT NULL, + received_sol REAL, + buy_value_usd REAL NOT NULL, + sell_value_usd REAL, + profit_usd REAL, + profit_percent REAL, + buy_market_cap REAL NOT NULL, + sell_market_cap REAL, + market_cap_change REAL, + buy_liquidity REAL NOT NULL, + sell_liquidity REAL, + liquidity_change REAL, + last_updated TEXT DEFAULT (datetime('now')), + rapidDump BOOLEAN DEFAULT FALSE, + PRIMARY KEY (token_address, recommender_id, buy_timeStamp), + FOREIGN KEY (token_address) REFERENCES token_performance(token_address) ON DELETE CASCADE, + FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE + ); + `); + // create trade simulation table + this.db.exec(` + CREATE TABLE IF NOT EXISTS simulation_trade ( + token_address TEXT NOT NULL, + recommender_id TEXT NOT NULL, + buy_price REAL NOT NULL, + sell_price REAL, + buy_timeStamp TEXT NOT NULL, + sell_timeStamp TEXT, + buy_amount REAL NOT NULL, + sell_amount REAL, + buy_sol REAL NOT NULL, + received_sol REAL, + buy_value_usd REAL NOT NULL, + sell_value_usd REAL, + profit_usd REAL, + profit_percent REAL, + buy_market_cap REAL NOT NULL, + sell_market_cap REAL, + market_cap_change REAL, + buy_liquidity REAL NOT NULL, + sell_liquidity REAL, + liquidity_change REAL, + last_updated TEXT DEFAULT (datetime('now')), + rapidDump BOOLEAN DEFAULT FALSE, + PRIMARY KEY (token_address, recommender_id, buy_timeStamp), + FOREIGN KEY (token_address) REFERENCES token_performance(token_address) ON DELETE CASCADE, + FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE + ); + `); } /** @@ -652,6 +738,219 @@ export class TrustScoreDatabase { })); } + /** + * Inserts a new trade performance into the specified table. + * @param trade The TradePerformance object containing trade details. + * @param isSimulation Whether the trade is a simulation. If true, inserts into simulation_trade; otherwise, into trade. + * @returns boolean indicating success. + */ + addTradePerformance(trade: TradePerformance, isSimulation: boolean): boolean { + const tableName = isSimulation ? "simulation_trade" : "trade"; + const sql = ` + INSERT INTO ${tableName} ( + token_address, + recommender_id, + buy_price, + sell_price, + buy_timeStamp, + sell_timeStamp, + buy_amount, + sell_amount, + buy_sol, + received_sol, + buy_value_usd, + sell_value_usd, + profit_usd, + profit_percent, + buy_market_cap, + sell_market_cap, + market_cap_change, + buy_liquidity, + sell_liquidity, + liquidity_change, + last_updated, + rapidDump + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + `; + try { + this.db + .prepare(sql) + .run( + trade.token_address, + trade.recommender_id, + trade.buy_price, + trade.sell_price || null, + trade.buy_timeStamp, + trade.sell_timeStamp || null, + trade.buy_amount, + trade.sell_amount || null, + trade.buy_sol, + trade.received_sol || null, + trade.buy_value_usd, + trade.sell_value_usd || null, + trade.profit_usd || null, + trade.profit_percent || null, + trade.buy_market_cap, + trade.sell_market_cap || null, + trade.market_cap_change || null, + trade.buy_liquidity, + trade.sell_liquidity || null, + trade.liquidity_change || null, + trade.last_updated || new Date().toISOString(), + trade.rapidDump ? 1 : 0 + ); + console.log(`Inserted trade into ${tableName}:`, trade); + return true; + } catch (error) { + console.error(`Error inserting trade into ${tableName}:`, error); + return false; + } + } + + /** + * Updates an existing trade with sell details. + * @param tokenAddress The address of the token. + * @param recommenderId The UUID of the recommender. + * @param buyTimeStamp The timestamp when the buy occurred. + * @param sellDetails An object containing sell-related details. + * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. + * @returns boolean indicating success. + */ + + updateTradePerformanceOnSell( + tokenAddress: string, + recommenderId: string, + buyTimeStamp: string, + sellDetails: { + sell_price: number; + sell_timeStamp: string; + sell_amount: number; + received_sol: number; + sell_value_usd: number; + profit_usd: number; + profit_percent: number; + sell_market_cap: number; + market_cap_change: number; + sell_liquidity: number; + liquidity_change: number; + rapidDump: boolean; + }, + isSimulation: boolean + ): boolean { + const tableName = isSimulation ? "simulation_trade" : "trade"; + const sql = ` + UPDATE ${tableName} + SET + sell_price = ?, + sell_timeStamp = ?, + sell_amount = ?, + received_sol = ?, + sell_value_usd = ?, + profit_usd = ?, + profit_percent = ?, + sell_market_cap = ?, + market_cap_change = ?, + sell_liquidity = ?, + liquidity_change = ?, + rapidDump = ? + WHERE + token_address = ? + AND recommender_id = ? + AND buy_timeStamp = ?; + `; + try { + const result = this.db + .prepare(sql) + .run( + sellDetails.sell_price, + sellDetails.sell_timeStamp, + sellDetails.sell_amount, + sellDetails.received_sol, + sellDetails.sell_value_usd, + sellDetails.profit_usd, + sellDetails.profit_percent, + sellDetails.sell_market_cap, + sellDetails.market_cap_change, + sellDetails.sell_liquidity, + sellDetails.liquidity_change, + sellDetails.rapidDump ? 1 : 0, + tokenAddress, + recommenderId, + buyTimeStamp + ); + + if (result.changes === 0) { + console.warn( + `No trade found to update in ${tableName} for token: ${tokenAddress}, recommender: ${recommenderId}, buyTimeStamp: ${buyTimeStamp}` + ); + return false; + } + + console.log(`Updated trade in ${tableName}:`, { + token_address: tokenAddress, + recommender_id: recommenderId, + buy_timeStamp: buyTimeStamp, + ...sellDetails, + }); + return true; + } catch (error) { + console.error(`Error updating trade in ${tableName}:`, error); + return false; + } + } + + //getTradePerformance + + /** + * Retrieves trade performance metrics. + * @param tokenAddress Token's address + * @param recommenderId Recommender's UUID + * @param buyTimeStamp Timestamp when the buy occurred + * @param isSimulation Whether the trade is a simulation. If true, retrieves from simulation_trade; otherwise, from trade. + * @returns TradePerformance object or null + */ + + getTradePerformance( + tokenAddress: string, + recommenderId: string, + buyTimeStamp: string, + isSimulation: boolean + ): TradePerformance | null { + const tableName = isSimulation ? "simulation_trade" : "trade"; + const sql = `SELECT * FROM ${tableName} WHERE token_address = ? AND recommender_id = ? AND buy_timeStamp = ?;`; + const row = this.db + .prepare(sql) + .get(tokenAddress, recommenderId, buyTimeStamp) as + | TradePerformance + | undefined; + if (!row) return null; + + return { + token_address: row.token_address, + recommender_id: row.recommender_id, + buy_price: row.buy_price, + sell_price: row.sell_price, + buy_timeStamp: row.buy_timeStamp, + sell_timeStamp: row.sell_timeStamp, + buy_amount: row.buy_amount, + sell_amount: row.sell_amount, + buy_sol: row.buy_sol, + received_sol: row.received_sol, + buy_value_usd: row.buy_value_usd, + sell_value_usd: row.sell_value_usd, + profit_usd: row.profit_usd, + profit_percent: row.profit_percent, + buy_market_cap: row.buy_market_cap, + sell_market_cap: row.sell_market_cap, + market_cap_change: row.market_cap_change, + buy_liquidity: row.buy_liquidity, + sell_liquidity: row.sell_liquidity, + liquidity_change: row.liquidity_change, + last_updated: row.last_updated, + rapidDump: row.rapidDump, + }; + } + /** * Close the database connection gracefully. */ diff --git a/src/providers/trustScoreProvider.ts b/src/providers/trustScoreProvider.ts index b04a9a16..023aa92a 100644 --- a/src/providers/trustScoreProvider.ts +++ b/src/providers/trustScoreProvider.ts @@ -6,14 +6,26 @@ import { DexScreenerPair, HolderData, } from "../types/token"; -import { Connection } from "@solana/web3.js"; +import { Connection, PublicKey } from "@solana/web3.js"; + import { TokenProvider } from "./token"; +import WalletProvider from "./balances"; import { TrustScoreDatabase, RecommenderMetrics, TokenPerformance, + TradePerformance, } from "../adapters/trustScoreDatabase"; +import settings from "../core/settings"; +const Wallet = settings.MAIN_WALLET_ADDRESS; +interface TradeData { + buy_amount: number; + is_simulation: boolean; +} +interface sellDetails { + sell_amount: number; +} export class TrustScoreProvider { private tokenProvider: TokenProvider; private trustScoreDb: TrustScoreDatabase; @@ -214,4 +226,126 @@ export class TrustScoreProvider { top10HolderPercent: processedData.security.top10HolderPercent, }; } + + /** + * Creates a TradePerformance object based on token data and recommender. + * @param tokenAddress The address of the token. + * @param recommenderId The UUID of the recommender. + * @param data ProcessedTokenData. + * @returns TradePerformance object. + */ + async createTradePerformance( + tokenAddress: string, + recommenderId: string, + data: TradeData + ): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const wallet = new WalletProvider( + new Connection("https://api.mainnet-beta.solana.com"), + new PublicKey(Wallet!) + ); + const prices = await wallet.fetchPrices(); + const solPrice = prices.solana.usd; + const buySol = data.buy_amount / parseFloat(solPrice); + const buy_value_usd = data.buy_amount * processedData.tradeData.price; + + const creationData = { + token_address: tokenAddress, + recommender_id: recommenderId, + buy_price: processedData.tradeData.price, + sell_price: 0, + buy_timeStamp: new Date().toISOString(), + sell_timeStamp: "", + buy_amount: data.buy_amount, + sell_amount: 0, + buy_sol: buySol, + received_sol: 0, + buy_value_usd: buy_value_usd, + sell_value_usd: 0, + profit_usd: 0, + profit_percent: 0, + buy_market_cap: processedData.dexScreenerData.pairs[0]?.marketCap || 0, + sell_market_cap: 0, + market_cap_change: 0, + buy_liquidity: processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + sell_liquidity: 0, + liquidity_change: 0, + last_updated: new Date().toISOString(), + rapidDump: false, + }; + this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); + return creationData; + } + + /** + * Updates a trade with sell details. + * @param tokenAddress The address of the token. + * @param recommenderId The UUID of the recommender. + * @param buyTimeStamp The timestamp when the buy occurred. + * @param sellDetails An object containing sell-related details. + * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. + * @returns boolean indicating success. + */ + + async updateSellDetails( + tokenAddress: string, + recommenderId: string, + sellTimeStamp: string, + sellDetails: sellDetails, + isSimulation: boolean, + buyTimeStamp: string + ) { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const wallet = new WalletProvider( + new Connection("https://api.mainnet-beta.solana.com"), + new PublicKey(Wallet!) + ); + const prices = await wallet.fetchPrices(); + const solPrice = prices.solana.usd; + const sellSol = sellDetails.sell_amount / parseFloat(solPrice); + const sell_value_usd = + sellDetails.sell_amount * processedData.tradeData.price; + const trade = await this.trustScoreDb.getTradePerformance( + tokenAddress, + recommenderId, + buyTimeStamp, + isSimulation + ); + const marketCap = processedData.dexScreenerData.pairs[0]?.marketCap || 0; + const liquidity = + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; + const sell_price = processedData.tradeData.price; + const profit_usd = sell_value_usd - trade.buy_value_usd; + const profit_percent = (profit_usd / trade.buy_value_usd) * 100; + + const market_cap_change = marketCap - trade.buy_market_cap; + const liquidity_change = liquidity - trade.buy_liquidity; + + const isRapidDump = await this.isRapidDump(tokenAddress); + + const sellDetailsData = { + sell_price: sell_price, + sell_timeStamp: sellTimeStamp, + sell_amount: sellDetails.sell_amount, + received_sol: sellSol, + sell_value_usd: sell_value_usd, + profit_usd: profit_usd, + profit_percent: profit_percent, + sell_market_cap: marketCap, + market_cap_change: market_cap_change, + sell_liquidity: liquidity, + liquidity_change: liquidity_change, + rapidDump: isRapidDump, + }; + this.trustScoreDb.updateTradePerformanceOnSell( + tokenAddress, + recommenderId, + buyTimeStamp, + sellDetailsData, + isSimulation + ); + return sellDetailsData; + } } From 9880a538af2035eaea5d06c99aa08a1f86841f12 Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Fri, 1 Nov 2024 18:28:22 +0200 Subject: [PATCH 3/6] merge Signed-off-by: MarcoMandar --- src/providers/token.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/providers/token.test.ts diff --git a/src/providers/token.test.ts b/src/providers/token.test.ts new file mode 100644 index 00000000..e69de29b From dd3d4a63b99b02dfbf5269ca96aca8192529374e Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Fri, 1 Nov 2024 23:02:30 +0200 Subject: [PATCH 4/6] adding virtualConfidence and chainging score Signed-off-by: MarcoMandar --- src/adapters/trustScoreDatabase.ts | 38 +++++++++++++++++++---------- src/providers/trustScoreProvider.ts | 10 +++++--- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/adapters/trustScoreDatabase.ts b/src/adapters/trustScoreDatabase.ts index 3bdf7174..a5d707ba 100644 --- a/src/adapters/trustScoreDatabase.ts +++ b/src/adapters/trustScoreDatabase.ts @@ -17,12 +17,13 @@ export interface Recommender { export interface RecommenderMetrics { recommenderId: string; - overallTrustScore: number; + trustScore: number; totalRecommendations: number; successfulRecs: number; avgTokenPerformance: number; riskScore: number; consistencyScore: number; + virtualConfidence: number; lastUpdated: Date; } @@ -55,12 +56,13 @@ export interface TokenRecommendation { export interface RecommenderMetricsHistory { historyId: string; // UUID recommenderId: string; - overallTrustScore: number; + trustScore: number; totalRecommendations: number; successfulRecs: number; avgTokenPerformance: number; riskScore: number; consistencyScore: number; + virtualConfidence: number; recordedAt: Date; } @@ -91,12 +93,13 @@ export interface TradePerformance { interface RecommenderMetricsRow { recommender_id: string; - overall_trust_score: number; + trust_score: number; total_recommendations: number; successful_recs: number; avg_token_performance: number; risk_score: number; consistency_score: number; + virtual_confidence: number; last_updated: string; } @@ -155,12 +158,13 @@ export class TrustScoreDatabase { this.db.exec(` CREATE TABLE IF NOT EXISTS recommender_metrics ( recommender_id TEXT PRIMARY KEY, - overall_trust_score REAL DEFAULT 0, + trust_score REAL DEFAULT 0, total_recommendations INTEGER DEFAULT 0, successful_recs INTEGER DEFAULT 0, avg_token_performance REAL DEFAULT 0, risk_score REAL DEFAULT 0, consistency_score REAL DEFAULT 0, + virtual_confidence REAL DEFAULT 0, last_updated DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE ); @@ -206,12 +210,13 @@ export class TrustScoreDatabase { CREATE TABLE IF NOT EXISTS recommender_metrics_history ( history_id TEXT PRIMARY KEY, recommender_id TEXT NOT NULL, - overall_trust_score REAL, + trust_score REAL, total_recommendations INTEGER, successful_recs INTEGER, avg_token_performance REAL, risk_score REAL, consistency_score REAL, + virtual_confidence REAL DEFAULT 0, recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE ); @@ -222,6 +227,7 @@ export class TrustScoreDatabase { CREATE TABLE IF NOT EXISTS trade ( token_address TEXT NOT NULL, recommender_id TEXT NOT NULL, + sell_recommender_id TEXT, buy_price REAL NOT NULL, sell_price REAL, buy_timeStamp TEXT NOT NULL, @@ -365,12 +371,13 @@ export class TrustScoreDatabase { return { recommenderId: row.recommender_id, - overallTrustScore: row.overall_trust_score, + trustScore: row.trust_score, totalRecommendations: row.total_recommendations, successfulRecs: row.successful_recs, avgTokenPerformance: row.avg_token_performance, riskScore: row.risk_score, consistencyScore: row.consistency_score, + virtualConfidence: row.virtual_confidence, lastUpdated: new Date(row.last_updated), }; } @@ -391,12 +398,13 @@ export class TrustScoreDatabase { const history: RecommenderMetricsHistory = { historyId: uuidv4(), recommenderId: currentMetrics.recommenderId, - overallTrustScore: currentMetrics.overallTrustScore, + trustScore: currentMetrics.trustScore, totalRecommendations: currentMetrics.totalRecommendations, successfulRecs: currentMetrics.successfulRecs, avgTokenPerformance: currentMetrics.avgTokenPerformance, riskScore: currentMetrics.riskScore, consistencyScore: currentMetrics.consistencyScore, + virtualConfidence: currentMetrics.virtualConfidence, recordedAt: new Date(), // Current timestamp }; @@ -405,7 +413,7 @@ export class TrustScoreDatabase { INSERT INTO recommender_metrics_history ( history_id, recommender_id, - overall_trust_score, + trust_score, total_recommendations, successful_recs, avg_token_performance, @@ -420,7 +428,7 @@ export class TrustScoreDatabase { .run( history.historyId, history.recommenderId, - history.overallTrustScore, + history.trustScore, history.totalRecommendations, history.successfulRecs, history.avgTokenPerformance, @@ -446,7 +454,7 @@ export class TrustScoreDatabase { const sql = ` UPDATE recommender_metrics - SET overall_trust_score = ?, + SET trust_score = ?, total_recommendations = ?, successful_recs = ?, avg_token_performance = ?, @@ -459,7 +467,7 @@ export class TrustScoreDatabase { this.db .prepare(sql) .run( - metrics.overallTrustScore, + metrics.trustScore, metrics.totalRecommendations, metrics.successfulRecs, metrics.avgTokenPerformance, @@ -716,24 +724,26 @@ export class TrustScoreDatabase { const rows = this.db.prepare(sql).all(recommenderId) as Array<{ history_id: string; recommender_id: string; - overall_trust_score: number; + trust_score: number; total_recommendations: number; successful_recs: number; avg_token_performance: number; risk_score: number; consistency_score: number; + virtual_confidence: number; recorded_at: string; }>; return rows.map((row) => ({ historyId: row.history_id, recommenderId: row.recommender_id, - overallTrustScore: row.overall_trust_score, + trustScore: row.trust_score, totalRecommendations: row.total_recommendations, successfulRecs: row.successful_recs, avgTokenPerformance: row.avg_token_performance, riskScore: row.risk_score, consistencyScore: row.consistency_score, + virtualConfidence: row.virtual_confidence, recordedAt: new Date(row.recorded_at), })); } @@ -834,6 +844,7 @@ export class TrustScoreDatabase { sell_liquidity: number; liquidity_change: number; rapidDump: boolean; + sell_recommender_id: string | null; }, isSimulation: boolean ): boolean { @@ -853,6 +864,7 @@ export class TrustScoreDatabase { sell_liquidity = ?, liquidity_change = ?, rapidDump = ? + sell_recommender_id = ? WHERE token_address = ? AND recommender_id = ? diff --git a/src/providers/trustScoreProvider.ts b/src/providers/trustScoreProvider.ts index 023aa92a..7c60b1ac 100644 --- a/src/providers/trustScoreProvider.ts +++ b/src/providers/trustScoreProvider.ts @@ -25,6 +25,7 @@ interface TradeData { } interface sellDetails { sell_amount: number; + sell_recommender_id: string | null; } export class TrustScoreProvider { private tokenProvider: TokenProvider; @@ -79,12 +80,13 @@ export class TrustScoreProvider { }, recommenderMetrics: { recommenderId: recommenderId, - overallTrustScore: recommenderMetrics.overallTrustScore, + trustScore: recommenderMetrics.trustScore, totalRecommendations: recommenderMetrics.totalRecommendations, successfulRecs: recommenderMetrics.successfulRecs, avgTokenPerformance: recommenderMetrics.avgTokenPerformance, riskScore: recommenderMetrics.riskScore, consistencyScore: recommenderMetrics.consistencyScore, + virtualConfidence: recommenderMetrics.virtualConfidence, lastUpdated: new Date(), }, }; @@ -121,16 +123,17 @@ export class TrustScoreProvider { ); const newRecommenderMetrics: RecommenderMetrics = { recommenderId: recommenderId, - overallTrustScore: overallTrustScore, + trustScore: overallTrustScore, totalRecommendations: totalRecommendations, successfulRecs: successfulRecs, avgTokenPerformance: avgTokenPerformance, riskScore: riskScore, consistencyScore: consistencyScore, + virtualConfidence: recommenderMetrics.virtualConfidence, lastUpdated: new Date(), }; - await this.trustScoreDb.updateRecommenderMetrics(recommenderMetrics); + await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics); } calculateTrustScore( @@ -338,6 +341,7 @@ export class TrustScoreProvider { sell_liquidity: liquidity, liquidity_change: liquidity_change, rapidDump: isRapidDump, + sell_recommender_id: sellDetails.sell_recommender_id || null, }; this.trustScoreDb.updateTradePerformanceOnSell( tokenAddress, From f5f8ee7087e6dc2ff92f87d42d92da64d3752388 Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Fri, 1 Nov 2024 23:13:06 +0200 Subject: [PATCH 5/6] types Signed-off-by: MarcoMandar --- core/src/types/token.ts | 512 ++++++++++++++++++++-------------------- 1 file changed, 256 insertions(+), 256 deletions(-) diff --git a/core/src/types/token.ts b/core/src/types/token.ts index 95751e7b..6e12bde6 100644 --- a/core/src/types/token.ts +++ b/core/src/types/token.ts @@ -1,274 +1,274 @@ export interface TokenSecurityData { - ownerBalance: string; - creatorBalance: string; - ownerPercentage: number; - creatorPercentage: number; - top10HolderBalance: string; - top10HolderPercent: number; + ownerBalance: string; + creatorBalance: string; + ownerPercentage: number; + creatorPercentage: number; + top10HolderBalance: string; + top10HolderPercent: number; } export interface TokenTradeData { - address: string; - holder: number; - market: number; - last_trade_unix_time: number; - last_trade_human_time: string; - price: number; - history_30m_price: number; - price_change_30m_percent: number; - history_1h_price: number; - price_change_1h_percent: number; - history_2h_price: number; - price_change_2h_percent: number; - history_4h_price: number; - price_change_4h_percent: number; - history_6h_price: number; - price_change_6h_percent: number; - history_8h_price: number; - price_change_8h_percent: number; - history_12h_price: number; - price_change_12h_percent: number; - history_24h_price: number; - price_change_24h_percent: number; - unique_wallet_30m: number; - unique_wallet_history_30m: number; - unique_wallet_30m_change_percent: number; - unique_wallet_1h: number; - unique_wallet_history_1h: number; - unique_wallet_1h_change_percent: number; - unique_wallet_2h: number; - unique_wallet_history_2h: number; - unique_wallet_2h_change_percent: number; - unique_wallet_4h: number; - unique_wallet_history_4h: number; - unique_wallet_4h_change_percent: number; - unique_wallet_8h: number; - unique_wallet_history_8h: number | null; - unique_wallet_8h_change_percent: number | null; - unique_wallet_24h: number; - unique_wallet_history_24h: number | null; - unique_wallet_24h_change_percent: number | null; - trade_30m: number; - trade_history_30m: number; - trade_30m_change_percent: number; - sell_30m: number; - sell_history_30m: number; - sell_30m_change_percent: number; - buy_30m: number; - buy_history_30m: number; - buy_30m_change_percent: number; - volume_30m: number; - volume_30m_usd: number; - volume_history_30m: number; - volume_history_30m_usd: number; - volume_30m_change_percent: number; - volume_buy_30m: number; - volume_buy_30m_usd: number; - volume_buy_history_30m: number; - volume_buy_history_30m_usd: number; - volume_buy_30m_change_percent: number; - volume_sell_30m: number; - volume_sell_30m_usd: number; - volume_sell_history_30m: number; - volume_sell_history_30m_usd: number; - volume_sell_30m_change_percent: number; - trade_1h: number; - trade_history_1h: number; - trade_1h_change_percent: number; - sell_1h: number; - sell_history_1h: number; - sell_1h_change_percent: number; - buy_1h: number; - buy_history_1h: number; - buy_1h_change_percent: number; - volume_1h: number; - volume_1h_usd: number; - volume_history_1h: number; - volume_history_1h_usd: number; - volume_1h_change_percent: number; - volume_buy_1h: number; - volume_buy_1h_usd: number; - volume_buy_history_1h: number; - volume_buy_history_1h_usd: number; - volume_buy_1h_change_percent: number; - volume_sell_1h: number; - volume_sell_1h_usd: number; - volume_sell_history_1h: number; - volume_sell_history_1h_usd: number; - volume_sell_1h_change_percent: number; - trade_2h: number; - trade_history_2h: number; - trade_2h_change_percent: number; - sell_2h: number; - sell_history_2h: number; - sell_2h_change_percent: number; - buy_2h: number; - buy_history_2h: number; - buy_2h_change_percent: number; - volume_2h: number; - volume_2h_usd: number; - volume_history_2h: number; - volume_history_2h_usd: number; - volume_2h_change_percent: number; - volume_buy_2h: number; - volume_buy_2h_usd: number; - volume_buy_history_2h: number; - volume_buy_history_2h_usd: number; - volume_buy_2h_change_percent: number; - volume_sell_2h: number; - volume_sell_2h_usd: number; - volume_sell_history_2h: number; - volume_sell_history_2h_usd: number; - volume_sell_2h_change_percent: number; - trade_4h: number; - trade_history_4h: number; - trade_4h_change_percent: number; - sell_4h: number; - sell_history_4h: number; - sell_4h_change_percent: number; - buy_4h: number; - buy_history_4h: number; - buy_4h_change_percent: number; - volume_4h: number; - volume_4h_usd: number; - volume_history_4h: number; - volume_history_4h_usd: number; - volume_4h_change_percent: number; - volume_buy_4h: number; - volume_buy_4h_usd: number; - volume_buy_history_4h: number; - volume_buy_history_4h_usd: number; - volume_buy_4h_change_percent: number; - volume_sell_4h: number; - volume_sell_4h_usd: number; - volume_sell_history_4h: number; - volume_sell_history_4h_usd: number; - volume_sell_4h_change_percent: number; - trade_8h: number; - trade_history_8h: number | null; - trade_8h_change_percent: number | null; - sell_8h: number; - sell_history_8h: number | null; - sell_8h_change_percent: number | null; - buy_8h: number; - buy_history_8h: number | null; - buy_8h_change_percent: number | null; - volume_8h: number; - volume_8h_usd: number; - volume_history_8h: number; - volume_history_8h_usd: number; - volume_8h_change_percent: number | null; - volume_buy_8h: number; - volume_buy_8h_usd: number; - volume_buy_history_8h: number; - volume_buy_history_8h_usd: number; - volume_buy_8h_change_percent: number | null; - volume_sell_8h: number; - volume_sell_8h_usd: number; - volume_sell_history_8h: number; - volume_sell_history_8h_usd: number; - volume_sell_8h_change_percent: number | null; - trade_24h: number; - trade_history_24h: number; - trade_24h_change_percent: number | null; - sell_24h: number; - sell_history_24h: number; - sell_24h_change_percent: number | null; - buy_24h: number; - buy_history_24h: number; - buy_24h_change_percent: number | null; - volume_24h: number; - volume_24h_usd: number; - volume_history_24h: number; - volume_history_24h_usd: number; - volume_24h_change_percent: number | null; - volume_buy_24h: number; - volume_buy_24h_usd: number; - volume_buy_history_24h: number; - volume_buy_history_24h_usd: number; - volume_buy_24h_change_percent: number | null; - volume_sell_24h: number; - volume_sell_24h_usd: number; - volume_sell_history_24h: number; - volume_sell_history_24h_usd: number; - volume_sell_24h_change_percent: number | null; + address: string; + holder: number; + market: number; + last_trade_unix_time: number; + last_trade_human_time: string; + price: number; + history_30m_price: number; + price_change_30m_percent: number; + history_1h_price: number; + price_change_1h_percent: number; + history_2h_price: number; + price_change_2h_percent: number; + history_4h_price: number; + price_change_4h_percent: number; + history_6h_price: number; + price_change_6h_percent: number; + history_8h_price: number; + price_change_8h_percent: number; + history_12h_price: number; + price_change_12h_percent: number; + history_24h_price: number; + price_change_24h_percent: number; + unique_wallet_30m: number; + unique_wallet_history_30m: number; + unique_wallet_30m_change_percent: number; + unique_wallet_1h: number; + unique_wallet_history_1h: number; + unique_wallet_1h_change_percent: number; + unique_wallet_2h: number; + unique_wallet_history_2h: number; + unique_wallet_2h_change_percent: number; + unique_wallet_4h: number; + unique_wallet_history_4h: number; + unique_wallet_4h_change_percent: number; + unique_wallet_8h: number; + unique_wallet_history_8h: number | null; + unique_wallet_8h_change_percent: number | null; + unique_wallet_24h: number; + unique_wallet_history_24h: number | null; + unique_wallet_24h_change_percent: number | null; + trade_30m: number; + trade_history_30m: number; + trade_30m_change_percent: number; + sell_30m: number; + sell_history_30m: number; + sell_30m_change_percent: number; + buy_30m: number; + buy_history_30m: number; + buy_30m_change_percent: number; + volume_30m: number; + volume_30m_usd: number; + volume_history_30m: number; + volume_history_30m_usd: number; + volume_30m_change_percent: number; + volume_buy_30m: number; + volume_buy_30m_usd: number; + volume_buy_history_30m: number; + volume_buy_history_30m_usd: number; + volume_buy_30m_change_percent: number; + volume_sell_30m: number; + volume_sell_30m_usd: number; + volume_sell_history_30m: number; + volume_sell_history_30m_usd: number; + volume_sell_30m_change_percent: number; + trade_1h: number; + trade_history_1h: number; + trade_1h_change_percent: number; + sell_1h: number; + sell_history_1h: number; + sell_1h_change_percent: number; + buy_1h: number; + buy_history_1h: number; + buy_1h_change_percent: number; + volume_1h: number; + volume_1h_usd: number; + volume_history_1h: number; + volume_history_1h_usd: number; + volume_1h_change_percent: number; + volume_buy_1h: number; + volume_buy_1h_usd: number; + volume_buy_history_1h: number; + volume_buy_history_1h_usd: number; + volume_buy_1h_change_percent: number; + volume_sell_1h: number; + volume_sell_1h_usd: number; + volume_sell_history_1h: number; + volume_sell_history_1h_usd: number; + volume_sell_1h_change_percent: number; + trade_2h: number; + trade_history_2h: number; + trade_2h_change_percent: number; + sell_2h: number; + sell_history_2h: number; + sell_2h_change_percent: number; + buy_2h: number; + buy_history_2h: number; + buy_2h_change_percent: number; + volume_2h: number; + volume_2h_usd: number; + volume_history_2h: number; + volume_history_2h_usd: number; + volume_2h_change_percent: number; + volume_buy_2h: number; + volume_buy_2h_usd: number; + volume_buy_history_2h: number; + volume_buy_history_2h_usd: number; + volume_buy_2h_change_percent: number; + volume_sell_2h: number; + volume_sell_2h_usd: number; + volume_sell_history_2h: number; + volume_sell_history_2h_usd: number; + volume_sell_2h_change_percent: number; + trade_4h: number; + trade_history_4h: number; + trade_4h_change_percent: number; + sell_4h: number; + sell_history_4h: number; + sell_4h_change_percent: number; + buy_4h: number; + buy_history_4h: number; + buy_4h_change_percent: number; + volume_4h: number; + volume_4h_usd: number; + volume_history_4h: number; + volume_history_4h_usd: number; + volume_4h_change_percent: number; + volume_buy_4h: number; + volume_buy_4h_usd: number; + volume_buy_history_4h: number; + volume_buy_history_4h_usd: number; + volume_buy_4h_change_percent: number; + volume_sell_4h: number; + volume_sell_4h_usd: number; + volume_sell_history_4h: number; + volume_sell_history_4h_usd: number; + volume_sell_4h_change_percent: number; + trade_8h: number; + trade_history_8h: number | null; + trade_8h_change_percent: number | null; + sell_8h: number; + sell_history_8h: number | null; + sell_8h_change_percent: number | null; + buy_8h: number; + buy_history_8h: number | null; + buy_8h_change_percent: number | null; + volume_8h: number; + volume_8h_usd: number; + volume_history_8h: number; + volume_history_8h_usd: number; + volume_8h_change_percent: number | null; + volume_buy_8h: number; + volume_buy_8h_usd: number; + volume_buy_history_8h: number; + volume_buy_history_8h_usd: number; + volume_buy_8h_change_percent: number | null; + volume_sell_8h: number; + volume_sell_8h_usd: number; + volume_sell_history_8h: number; + volume_sell_history_8h_usd: number; + volume_sell_8h_change_percent: number | null; + trade_24h: number; + trade_history_24h: number; + trade_24h_change_percent: number | null; + sell_24h: number; + sell_history_24h: number; + sell_24h_change_percent: number | null; + buy_24h: number; + buy_history_24h: number; + buy_24h_change_percent: number | null; + volume_24h: number; + volume_24h_usd: number; + volume_history_24h: number; + volume_history_24h_usd: number; + volume_24h_change_percent: number | null; + volume_buy_24h: number; + volume_buy_24h_usd: number; + volume_buy_history_24h: number; + volume_buy_history_24h_usd: number; + volume_buy_24h_change_percent: number | null; + volume_sell_24h: number; + volume_sell_24h_usd: number; + volume_sell_history_24h: number; + volume_sell_history_24h_usd: number; + volume_sell_24h_change_percent: number | null; } export interface HolderData { - address: string; - balance: string; + address: string; + balance: string; } export interface ProcessedTokenData { - security: TokenSecurityData; - tradeData: TokenTradeData; - holderDistributionTrend: string; // 'increasing' | 'decreasing' | 'stable' - highValueHolders: Array<{ - holderAddress: string; - balanceUsd: string; - }>; - recentTrades: boolean; - highSupplyHoldersCount: number; - dexScreenerData: DexScreenerData; + security: TokenSecurityData; + tradeData: TokenTradeData; + holderDistributionTrend: string; // 'increasing' | 'decreasing' | 'stable' + highValueHolders: Array<{ + holderAddress: string; + balanceUsd: string; + }>; + recentTrades: boolean; + highSupplyHoldersCount: number; + dexScreenerData: DexScreenerData; - isDexScreenerListed: boolean; - isDexScreenerPaid: boolean; + isDexScreenerListed: boolean; + isDexScreenerPaid: boolean; } export interface DexScreenerPair { - chainId: string; - dexId: string; - url: string; - pairAddress: string; - baseToken: { - address: string; - name: string; - symbol: string; - }; - quoteToken: { - address: string; - name: string; - symbol: string; - }; - priceNative: string; - priceUsd: string; - txns: { - m5: { buys: number; sells: number }; - h1: { buys: number; sells: number }; - h6: { buys: number; sells: number }; - h24: { buys: number; sells: number }; - }; - volume: { - h24: number; - h6: number; - h1: number; - m5: number; - }; - priceChange: { - m5: number; - h1: number; - h6: number; - h24: number; - }; - liquidity: { - usd: number; - base: number; - quote: number; - }; - fdv: number; - marketCap: number; - pairCreatedAt: number; - info: { - imageUrl: string; - websites: { label: string; url: string }[]; - socials: { type: string; url: string }[]; - }; - boosts: { - active: number; - }; + chainId: string; + dexId: string; + url: string; + pairAddress: string; + baseToken: { + address: string; + name: string; + symbol: string; + }; + quoteToken: { + address: string; + name: string; + symbol: string; + }; + priceNative: string; + priceUsd: string; + txns: { + m5: { buys: number; sells: number }; + h1: { buys: number; sells: number }; + h6: { buys: number; sells: number }; + h24: { buys: number; sells: number }; + }; + volume: { + h24: number; + h6: number; + h1: number; + m5: number; + }; + priceChange: { + m5: number; + h1: number; + h6: number; + h24: number; + }; + liquidity: { + usd: number; + base: number; + quote: number; + }; + fdv: number; + marketCap: number; + pairCreatedAt: number; + info: { + imageUrl: string; + websites: { label: string; url: string }[]; + socials: { type: string; url: string }[]; + }; + boosts: { + active: number; + }; } export interface DexScreenerData { - schemaVersion: string; - pairs: DexScreenerPair[]; + schemaVersion: string; + pairs: DexScreenerPair[]; } From 73564bcd7e55bfd7e852df712a2a5704c808e62a Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Fri, 1 Nov 2024 23:19:37 +0200 Subject: [PATCH 6/6] fix types Signed-off-by: MarcoMandar --- core/src/providers/token.ts | 106 +++- core/src/providers/trustScoreProvider.ts | 627 ++++++++++++----------- 2 files changed, 401 insertions(+), 332 deletions(-) diff --git a/core/src/providers/token.ts b/core/src/providers/token.ts index 62739d8f..ac44d728 100644 --- a/core/src/providers/token.ts +++ b/core/src/providers/token.ts @@ -1,18 +1,19 @@ +import { Connection } from "@solana/web3.js"; // import fetch from "cross-fetch"; +import { IAgentRuntime, Memory, Provider, State } from "../core/types"; +import settings from "../core/settings"; import BigNumber from "bignumber.js"; -import * as fs from "fs"; -import NodeCache from "node-cache"; -import * as path from "path"; -import settings from "../core/settings.ts"; -import { IAgentRuntime, Memory, Provider, State } from "../core/types.ts"; import { - DexScreenerData, - HolderData, ProcessedTokenData, TokenSecurityData, TokenTradeData, -} from "../types/token.ts"; -import { fileURLToPath } from "url"; + DexScreenerData, + // DexScreenerPair, + HolderData, +} from "../types/token"; +import NodeCache from "node-cache"; +import * as fs from "fs"; +import * as path from "path"; const PROVIDER_CONFIG = { BIRDEYE_API: "https://public-api.birdeye.so", @@ -39,11 +40,7 @@ export class TokenProvider { private tokenAddress: string ) { this.cache = new NodeCache({ stdTTL: 300 }); // 5 minutes cache - const __filename = fileURLToPath(import.meta.url); - - const __dirname = path.dirname(__filename); - - this.cacheDir = path.join(__dirname, "../../tokencache"); + this.cacheDir = path.join(__dirname, "cache"); if (!fs.existsSync(this.cacheDir)) { fs.mkdirSync(this.cacheDir); } @@ -51,13 +48,20 @@ export class TokenProvider { private readCacheFromFile(cacheKey: string): T | null { const filePath = path.join(this.cacheDir, `${cacheKey}.json`); + console.log({ filePath }); if (fs.existsSync(filePath)) { const fileContent = fs.readFileSync(filePath, "utf-8"); const parsed = JSON.parse(fileContent); const now = Date.now(); if (now < parsed.expiry) { + console.log( + `Reading cached data from file for key: ${cacheKey}` + ); return parsed.data as T; } else { + console.log( + `Cache expired for key: ${cacheKey}. Deleting file.` + ); fs.unlinkSync(filePath); } } @@ -71,6 +75,7 @@ export class TokenProvider { expiry: Date.now() + 300000, // 5 minutes in milliseconds }; fs.writeFileSync(filePath, JSON.stringify(cacheData), "utf-8"); + console.log(`Cached data written to file for key: ${cacheKey}`); } private getCachedData(cacheKey: string): T | null { @@ -102,6 +107,7 @@ export class TokenProvider { private async fetchWithRetry( url: string, options: RequestInit = {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { let lastError: Error; @@ -127,9 +133,11 @@ export class TokenProvider { const data = await response.json(); return data; } catch (error) { + console.error(`Attempt ${i + 1} failed:`, error); lastError = error as Error; if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); + console.log(`Waiting ${delay}ms before retrying...`); await new Promise((resolve) => setTimeout(resolve, delay)); continue; } @@ -147,6 +155,9 @@ export class TokenProvider { const cacheKey = `tokenSecurity_${this.tokenAddress}`; const cachedData = this.getCachedData(cacheKey); if (cachedData) { + console.log( + `Returning cached token security data for ${this.tokenAddress}.` + ); return cachedData; } const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_SECURITY_ENDPOINT}${this.tokenAddress}`; @@ -165,14 +176,18 @@ export class TokenProvider { top10HolderPercent: data.data.top10HolderPercent, }; this.setCachedData(cacheKey, security); + console.log(`Token security data cached for ${this.tokenAddress}.`); return security; } - async fetchTokenTradeData(runtime: IAgentRuntime): Promise { + async fetchTokenTradeData(): Promise { const cacheKey = `tokenTradeData_${this.tokenAddress}`; const cachedData = this.getCachedData(cacheKey); if (cachedData) { + console.log( + `Returning cached token trade data for ${this.tokenAddress}.` + ); return cachedData; } @@ -181,7 +196,7 @@ export class TokenProvider { method: "GET", headers: { accept: "application/json", - "X-API-KEY": runtime.getSetting("BIRDEYE_API_KEY") || "", + "X-API-KEY": settings.BIRDEYE_API_KEY || "", }, }; @@ -405,11 +420,15 @@ export class TokenProvider { const cacheKey = `dexScreenerData_${this.tokenAddress}`; const cachedData = this.getCachedData(cacheKey); if (cachedData) { + console.log("Returning cached DexScreener data."); return cachedData; } const url = `https://api.dexscreener.com/latest/dex/search?q=${this.tokenAddress}`; try { + console.log( + `Fetching DexScreener data for token: ${this.tokenAddress}` + ); const data = await fetch(url) .then((res) => res.json()) .catch((err) => { @@ -488,6 +507,7 @@ export class TokenProvider { const cacheKey = `holderList_${this.tokenAddress}`; const cachedData = this.getCachedData(cacheKey); if (cachedData) { + console.log("Returning cached holder list."); return cachedData; } @@ -497,8 +517,10 @@ export class TokenProvider { let cursor; //HELIOUS_API_KEY needs to be added const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIOUS_API_KEY || ""}`; + console.log({ url }); try { + // eslint-disable-next-line no-constant-condition while (true) { const params = { limit: limit, @@ -509,7 +531,7 @@ export class TokenProvider { if (cursor != undefined) { params.cursor = cursor; } - + console.log(`Fetching holders - Page ${page}`); if (page > 2) { break; } @@ -534,9 +556,17 @@ export class TokenProvider { !data.result.token_accounts || data.result.token_accounts.length === 0 ) { + console.log( + `No more holders found. Total pages fetched: ${page - 1}` + ); break; } + console.log( + `Processing ${data.result.token_accounts.length} holders from page ${page}` + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any data.result.token_accounts.forEach((account: any) => { const owner = account.owner; const balance = parseFloat(account.amount); @@ -561,6 +591,8 @@ export class TokenProvider { balance: balance.toString(), })); + console.log(`Total unique holders fetched: ${holders.length}`); + // Cache the result this.setCachedData(cacheKey, holders); @@ -620,27 +652,47 @@ export class TokenProvider { } } - async getProcessedTokenData( - runtime: IAgentRuntime - ): Promise { + async getProcessedTokenData(): Promise { try { + console.log( + `Fetching security data for token: ${this.tokenAddress}` + ); const security = await this.fetchTokenSecurity(); - const tradeData = await this.fetchTokenTradeData(runtime); + console.log(`Fetching trade data for token: ${this.tokenAddress}`); + const tradeData = await this.fetchTokenTradeData(); + console.log( + `Fetching DexScreener data for token: ${this.tokenAddress}` + ); const dexData = await this.fetchDexScreenerData(); + console.log( + `Analyzing holder distribution for token: ${this.tokenAddress}` + ); const holderDistributionTrend = await this.analyzeHolderDistribution(tradeData); + console.log( + `Filtering high-value holders for token: ${this.tokenAddress}` + ); const highValueHolders = await this.filterHighValueHolders(tradeData); + console.log( + `Checking recent trades for token: ${this.tokenAddress}` + ); const recentTrades = await this.checkRecentTrades(tradeData); + console.log( + `Counting high-supply holders for token: ${this.tokenAddress}` + ); const highSupplyHoldersCount = await this.countHighSupplyHolders(security); + console.log( + `Determining DexScreener listing status for token: ${this.tokenAddress}` + ); const isDexScreenerListed = dexData.pairs.length > 0; const isDexScreenerPaid = dexData.pairs.some( (pair) => pair.boosts && pair.boosts.active > 0 @@ -658,6 +710,7 @@ export class TokenProvider { isDexScreenerPaid, }; + // console.log("Processed token data:", processedData); return processedData; } catch (error) { console.error("Error processing token data:", error); @@ -725,12 +778,14 @@ export class TokenProvider { } output += `\n`; + console.log("Formatted token data:", output); return output; } - async getFormattedTokenReport(runtime: IAgentRuntime): Promise { + async getFormattedTokenReport(): Promise { try { - const processedData = await this.getProcessedTokenData(runtime); + console.log("Generating formatted token report..."); + const processedData = await this.getProcessedTokenData(); return this.formatTokenData(processedData); } catch (error) { console.error("Error generating token report:", error); @@ -740,15 +795,18 @@ export class TokenProvider { } const tokenAddress = PROVIDER_CONFIG.TOKEN_ADDRESSES.Example; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC); const tokenProvider: Provider = { get: async ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars runtime: IAgentRuntime, _message: Memory, _state?: State ): Promise => { try { const provider = new TokenProvider(tokenAddress); - return provider.getFormattedTokenReport(runtime); + return provider.getFormattedTokenReport(); } catch (error) { console.error("Error fetching token data:", error); return "Unable to fetch token information. Please try again later."; diff --git a/core/src/providers/trustScoreProvider.ts b/core/src/providers/trustScoreProvider.ts index 7c60b1ac..db20dc52 100644 --- a/core/src/providers/trustScoreProvider.ts +++ b/core/src/providers/trustScoreProvider.ts @@ -1,355 +1,366 @@ import { - ProcessedTokenData, - TokenSecurityData, - TokenTradeData, - DexScreenerData, - DexScreenerPair, - HolderData, + ProcessedTokenData, + TokenSecurityData, + // TokenTradeData, + // DexScreenerData, + // DexScreenerPair, + // HolderData, } from "../types/token"; import { Connection, PublicKey } from "@solana/web3.js"; import { TokenProvider } from "./token"; import WalletProvider from "./balances"; import { - TrustScoreDatabase, - RecommenderMetrics, - TokenPerformance, - TradePerformance, + TrustScoreDatabase, + RecommenderMetrics, + TokenPerformance, + TradePerformance, } from "../adapters/trustScoreDatabase"; import settings from "../core/settings"; const Wallet = settings.MAIN_WALLET_ADDRESS; interface TradeData { - buy_amount: number; - is_simulation: boolean; + buy_amount: number; + is_simulation: boolean; } interface sellDetails { - sell_amount: number; - sell_recommender_id: string | null; + sell_amount: number; + sell_recommender_id: string | null; } export class TrustScoreProvider { - private tokenProvider: TokenProvider; - private trustScoreDb: TrustScoreDatabase; + private tokenProvider: TokenProvider; + private trustScoreDb: TrustScoreDatabase; - constructor(tokenProvider: TokenProvider, trustScoreDb: TrustScoreDatabase) { - this.tokenProvider = tokenProvider; - this.trustScoreDb = trustScoreDb; - } - /** - * Generates and saves trust score based on processed token data and user recommendations. - * @param tokenAddress The address of the token to analyze. - * @param recommenderId The UUID of the recommender. - * @returns An object containing TokenPerformance and RecommenderMetrics. - */ - async generateTrustScore( - tokenAddress: string, - recommenderId: string - ): Promise<{ - tokenPerformance: TokenPerformance; - recommenderMetrics: RecommenderMetrics; - }> { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); - - const recommenderMetrics = - await this.trustScoreDb.getRecommenderMetrics(recommenderId); + constructor( + tokenProvider: TokenProvider, + trustScoreDb: TrustScoreDatabase + ) { + this.tokenProvider = tokenProvider; + this.trustScoreDb = trustScoreDb; + } + /** + * Generates and saves trust score based on processed token data and user recommendations. + * @param tokenAddress The address of the token to analyze. + * @param recommenderId The UUID of the recommender. + * @returns An object containing TokenPerformance and RecommenderMetrics. + */ + async generateTrustScore( + tokenAddress: string, + recommenderId: string + ): Promise<{ + tokenPerformance: TokenPerformance; + recommenderMetrics: RecommenderMetrics; + }> { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); - const isRapidDump = await this.isRapidDump(tokenAddress); - const sustainedGrowth = await this.sustainedGrowth(tokenAddress); - const suspiciousVolume = await this.suspiciousVolume(tokenAddress); + const recommenderMetrics = + await this.trustScoreDb.getRecommenderMetrics(recommenderId); - return { - tokenPerformance: { - tokenAddress: - processedData.dexScreenerData.pairs[0]?.baseToken.address || "", - priceChange24h: processedData.tradeData.price_change_24h_percent, - volumeChange24h: processedData.tradeData.volume_24h, - trade_24h_change: processedData.tradeData.trade_24h_change_percent, - liquidity: processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - liquidityChange24h: 0, - holderChange24h: - processedData.tradeData.unique_wallet_24h_change_percent, - rugPull: false, // TODO: Implement rug pull detection - isScam: false, // TODO: Implement scam detection - marketCapChange24h: 0, // TODO: Implement market cap change - sustainedGrowth: sustainedGrowth, - rapidDump: isRapidDump, - suspiciousVolume: suspiciousVolume, - lastUpdated: new Date(), - }, - recommenderMetrics: { - recommenderId: recommenderId, - trustScore: recommenderMetrics.trustScore, - totalRecommendations: recommenderMetrics.totalRecommendations, - successfulRecs: recommenderMetrics.successfulRecs, - avgTokenPerformance: recommenderMetrics.avgTokenPerformance, - riskScore: recommenderMetrics.riskScore, - consistencyScore: recommenderMetrics.consistencyScore, - virtualConfidence: recommenderMetrics.virtualConfidence, - lastUpdated: new Date(), - }, - }; - } + const isRapidDump = await this.isRapidDump(tokenAddress); + const sustainedGrowth = await this.sustainedGrowth(tokenAddress); + const suspiciousVolume = await this.suspiciousVolume(tokenAddress); - async updateRecommenderMetrics( - recommenderId: string, - tokenPerformance: TokenPerformance - ): Promise { - const recommenderMetrics = - await this.trustScoreDb.getRecommenderMetrics(recommenderId); + return { + tokenPerformance: { + tokenAddress: + processedData.dexScreenerData.pairs[0]?.baseToken.address || + "", + priceChange24h: + processedData.tradeData.price_change_24h_percent, + volumeChange24h: processedData.tradeData.volume_24h, + trade_24h_change: + processedData.tradeData.trade_24h_change_percent, + liquidity: + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + liquidityChange24h: 0, + holderChange24h: + processedData.tradeData.unique_wallet_24h_change_percent, + rugPull: false, // TODO: Implement rug pull detection + isScam: false, // TODO: Implement scam detection + marketCapChange24h: 0, // TODO: Implement market cap change + sustainedGrowth: sustainedGrowth, + rapidDump: isRapidDump, + suspiciousVolume: suspiciousVolume, + lastUpdated: new Date(), + }, + recommenderMetrics: { + recommenderId: recommenderId, + trustScore: recommenderMetrics.trustScore, + totalRecommendations: recommenderMetrics.totalRecommendations, + successfulRecs: recommenderMetrics.successfulRecs, + avgTokenPerformance: recommenderMetrics.avgTokenPerformance, + riskScore: recommenderMetrics.riskScore, + consistencyScore: recommenderMetrics.consistencyScore, + virtualConfidence: recommenderMetrics.virtualConfidence, + lastUpdated: new Date(), + }, + }; + } - const totalRecommendations = recommenderMetrics.totalRecommendations + 1; - const successfulRecs = tokenPerformance.rugPull - ? recommenderMetrics.successfulRecs - : recommenderMetrics.successfulRecs + 1; - const avgTokenPerformance = - (recommenderMetrics.avgTokenPerformance * - recommenderMetrics.totalRecommendations + - tokenPerformance.priceChange24h) / - totalRecommendations; + async updateRecommenderMetrics( + recommenderId: string, + tokenPerformance: TokenPerformance + ): Promise { + const recommenderMetrics = + await this.trustScoreDb.getRecommenderMetrics(recommenderId); - const overallTrustScore = this.calculateTrustScore( - tokenPerformance, - recommenderMetrics - ); - const riskScore = this.calculateOverallRiskScore( - tokenPerformance, - recommenderMetrics - ); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - const newRecommenderMetrics: RecommenderMetrics = { - recommenderId: recommenderId, - trustScore: overallTrustScore, - totalRecommendations: totalRecommendations, - successfulRecs: successfulRecs, - avgTokenPerformance: avgTokenPerformance, - riskScore: riskScore, - consistencyScore: consistencyScore, - virtualConfidence: recommenderMetrics.virtualConfidence, - lastUpdated: new Date(), - }; + const totalRecommendations = + recommenderMetrics.totalRecommendations + 1; + const successfulRecs = tokenPerformance.rugPull + ? recommenderMetrics.successfulRecs + : recommenderMetrics.successfulRecs + 1; + const avgTokenPerformance = + (recommenderMetrics.avgTokenPerformance * + recommenderMetrics.totalRecommendations + + tokenPerformance.priceChange24h) / + totalRecommendations; - await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics); - } + const overallTrustScore = this.calculateTrustScore( + tokenPerformance, + recommenderMetrics + ); + const riskScore = this.calculateOverallRiskScore( + tokenPerformance, + recommenderMetrics + ); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + const newRecommenderMetrics: RecommenderMetrics = { + recommenderId: recommenderId, + trustScore: overallTrustScore, + totalRecommendations: totalRecommendations, + successfulRecs: successfulRecs, + avgTokenPerformance: avgTokenPerformance, + riskScore: riskScore, + consistencyScore: consistencyScore, + virtualConfidence: recommenderMetrics.virtualConfidence, + lastUpdated: new Date(), + }; - calculateTrustScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ): number { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); + await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics); + } - return (riskScore + consistencyScore) / 2; - } + calculateTrustScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ): number { + const riskScore = this.calculateRiskScore(tokenPerformance); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); - calculateOverallRiskScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ) { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); + return (riskScore + consistencyScore) / 2; + } - return (riskScore + consistencyScore) / 2; - } + calculateOverallRiskScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ) { + const riskScore = this.calculateRiskScore(tokenPerformance); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); - calculateRiskScore(tokenPerformance: TokenPerformance): number { - let riskScore = 0; - if (tokenPerformance.rugPull) { - riskScore += 10; - } - if (tokenPerformance.isScam) { - riskScore += 10; + return (riskScore + consistencyScore) / 2; } - if (tokenPerformance.rapidDump) { - riskScore += 5; - } - if (tokenPerformance.suspiciousVolume) { - riskScore += 5; + + calculateRiskScore(tokenPerformance: TokenPerformance): number { + let riskScore = 0; + if (tokenPerformance.rugPull) { + riskScore += 10; + } + if (tokenPerformance.isScam) { + riskScore += 10; + } + if (tokenPerformance.rapidDump) { + riskScore += 5; + } + if (tokenPerformance.suspiciousVolume) { + riskScore += 5; + } + return riskScore; } - return riskScore; - } - calculateConsistencyScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ): number { - const avgTokenPerformance = recommenderMetrics.avgTokenPerformance; - const priceChange24h = tokenPerformance.priceChange24h; + calculateConsistencyScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ): number { + const avgTokenPerformance = recommenderMetrics.avgTokenPerformance; + const priceChange24h = tokenPerformance.priceChange24h; - return Math.abs(priceChange24h - avgTokenPerformance); - } + return Math.abs(priceChange24h - avgTokenPerformance); + } - async suspiciousVolume(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const unique_wallet_24h = processedData.tradeData.unique_wallet_24h; - const volume_24h = processedData.tradeData.volume_24h; - const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5; - console.log(`Fetched processed token data for token: ${tokenAddress}`); - return suspiciousVolume; - } + async suspiciousVolume(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const unique_wallet_24h = processedData.tradeData.unique_wallet_24h; + const volume_24h = processedData.tradeData.volume_24h; + const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5; + console.log(`Fetched processed token data for token: ${tokenAddress}`); + return suspiciousVolume; + } - async sustainedGrowth(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); + async sustainedGrowth(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); - return processedData.tradeData.volume_24h_change_percent > 50; - } + return processedData.tradeData.volume_24h_change_percent > 50; + } - async isRapidDump(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); + async isRapidDump(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); - return processedData.tradeData.trade_24h_change_percent < -50; - } + return processedData.tradeData.trade_24h_change_percent < -50; + } - async checkTrustScore(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); + async checkTrustScore(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); - return { - ownerBalance: processedData.security.ownerBalance, - creatorBalance: processedData.security.creatorBalance, - ownerPercentage: processedData.security.ownerPercentage, - creatorPercentage: processedData.security.creatorPercentage, - top10HolderBalance: processedData.security.top10HolderBalance, - top10HolderPercent: processedData.security.top10HolderPercent, - }; - } + return { + ownerBalance: processedData.security.ownerBalance, + creatorBalance: processedData.security.creatorBalance, + ownerPercentage: processedData.security.ownerPercentage, + creatorPercentage: processedData.security.creatorPercentage, + top10HolderBalance: processedData.security.top10HolderBalance, + top10HolderPercent: processedData.security.top10HolderPercent, + }; + } - /** - * Creates a TradePerformance object based on token data and recommender. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param data ProcessedTokenData. - * @returns TradePerformance object. - */ - async createTradePerformance( - tokenAddress: string, - recommenderId: string, - data: TradeData - ): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - new Connection("https://api.mainnet-beta.solana.com"), - new PublicKey(Wallet!) - ); - const prices = await wallet.fetchPrices(); - const solPrice = prices.solana.usd; - const buySol = data.buy_amount / parseFloat(solPrice); - const buy_value_usd = data.buy_amount * processedData.tradeData.price; + /** + * Creates a TradePerformance object based on token data and recommender. + * @param tokenAddress The address of the token. + * @param recommenderId The UUID of the recommender. + * @param data ProcessedTokenData. + * @returns TradePerformance object. + */ + async createTradePerformance( + tokenAddress: string, + recommenderId: string, + data: TradeData + ): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const wallet = new WalletProvider( + new Connection("https://api.mainnet-beta.solana.com"), + new PublicKey(Wallet!) + ); + const prices = await wallet.fetchPrices(); + const solPrice = prices.solana.usd; + const buySol = data.buy_amount / parseFloat(solPrice); + const buy_value_usd = data.buy_amount * processedData.tradeData.price; - const creationData = { - token_address: tokenAddress, - recommender_id: recommenderId, - buy_price: processedData.tradeData.price, - sell_price: 0, - buy_timeStamp: new Date().toISOString(), - sell_timeStamp: "", - buy_amount: data.buy_amount, - sell_amount: 0, - buy_sol: buySol, - received_sol: 0, - buy_value_usd: buy_value_usd, - sell_value_usd: 0, - profit_usd: 0, - profit_percent: 0, - buy_market_cap: processedData.dexScreenerData.pairs[0]?.marketCap || 0, - sell_market_cap: 0, - market_cap_change: 0, - buy_liquidity: processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - sell_liquidity: 0, - liquidity_change: 0, - last_updated: new Date().toISOString(), - rapidDump: false, - }; - this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); - return creationData; - } + const creationData = { + token_address: tokenAddress, + recommender_id: recommenderId, + buy_price: processedData.tradeData.price, + sell_price: 0, + buy_timeStamp: new Date().toISOString(), + sell_timeStamp: "", + buy_amount: data.buy_amount, + sell_amount: 0, + buy_sol: buySol, + received_sol: 0, + buy_value_usd: buy_value_usd, + sell_value_usd: 0, + profit_usd: 0, + profit_percent: 0, + buy_market_cap: + processedData.dexScreenerData.pairs[0]?.marketCap || 0, + sell_market_cap: 0, + market_cap_change: 0, + buy_liquidity: + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + sell_liquidity: 0, + liquidity_change: 0, + last_updated: new Date().toISOString(), + rapidDump: false, + }; + this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); + return creationData; + } - /** - * Updates a trade with sell details. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param buyTimeStamp The timestamp when the buy occurred. - * @param sellDetails An object containing sell-related details. - * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. - * @returns boolean indicating success. - */ + /** + * Updates a trade with sell details. + * @param tokenAddress The address of the token. + * @param recommenderId The UUID of the recommender. + * @param buyTimeStamp The timestamp when the buy occurred. + * @param sellDetails An object containing sell-related details. + * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. + * @returns boolean indicating success. + */ - async updateSellDetails( - tokenAddress: string, - recommenderId: string, - sellTimeStamp: string, - sellDetails: sellDetails, - isSimulation: boolean, - buyTimeStamp: string - ) { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - new Connection("https://api.mainnet-beta.solana.com"), - new PublicKey(Wallet!) - ); - const prices = await wallet.fetchPrices(); - const solPrice = prices.solana.usd; - const sellSol = sellDetails.sell_amount / parseFloat(solPrice); - const sell_value_usd = - sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getTradePerformance( - tokenAddress, - recommenderId, - buyTimeStamp, - isSimulation - ); - const marketCap = processedData.dexScreenerData.pairs[0]?.marketCap || 0; - const liquidity = - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; - const sell_price = processedData.tradeData.price; - const profit_usd = sell_value_usd - trade.buy_value_usd; - const profit_percent = (profit_usd / trade.buy_value_usd) * 100; + async updateSellDetails( + tokenAddress: string, + recommenderId: string, + sellTimeStamp: string, + sellDetails: sellDetails, + isSimulation: boolean, + buyTimeStamp: string + ) { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const wallet = new WalletProvider( + new Connection("https://api.mainnet-beta.solana.com"), + new PublicKey(Wallet!) + ); + const prices = await wallet.fetchPrices(); + const solPrice = prices.solana.usd; + const sellSol = sellDetails.sell_amount / parseFloat(solPrice); + const sell_value_usd = + sellDetails.sell_amount * processedData.tradeData.price; + const trade = await this.trustScoreDb.getTradePerformance( + tokenAddress, + recommenderId, + buyTimeStamp, + isSimulation + ); + const marketCap = + processedData.dexScreenerData.pairs[0]?.marketCap || 0; + const liquidity = + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; + const sell_price = processedData.tradeData.price; + const profit_usd = sell_value_usd - trade.buy_value_usd; + const profit_percent = (profit_usd / trade.buy_value_usd) * 100; - const market_cap_change = marketCap - trade.buy_market_cap; - const liquidity_change = liquidity - trade.buy_liquidity; + const market_cap_change = marketCap - trade.buy_market_cap; + const liquidity_change = liquidity - trade.buy_liquidity; - const isRapidDump = await this.isRapidDump(tokenAddress); + const isRapidDump = await this.isRapidDump(tokenAddress); - const sellDetailsData = { - sell_price: sell_price, - sell_timeStamp: sellTimeStamp, - sell_amount: sellDetails.sell_amount, - received_sol: sellSol, - sell_value_usd: sell_value_usd, - profit_usd: profit_usd, - profit_percent: profit_percent, - sell_market_cap: marketCap, - market_cap_change: market_cap_change, - sell_liquidity: liquidity, - liquidity_change: liquidity_change, - rapidDump: isRapidDump, - sell_recommender_id: sellDetails.sell_recommender_id || null, - }; - this.trustScoreDb.updateTradePerformanceOnSell( - tokenAddress, - recommenderId, - buyTimeStamp, - sellDetailsData, - isSimulation - ); - return sellDetailsData; - } + const sellDetailsData = { + sell_price: sell_price, + sell_timeStamp: sellTimeStamp, + sell_amount: sellDetails.sell_amount, + received_sol: sellSol, + sell_value_usd: sell_value_usd, + profit_usd: profit_usd, + profit_percent: profit_percent, + sell_market_cap: marketCap, + market_cap_change: market_cap_change, + sell_liquidity: liquidity, + liquidity_change: liquidity_change, + rapidDump: isRapidDump, + sell_recommender_id: sellDetails.sell_recommender_id || null, + }; + this.trustScoreDb.updateTradePerformanceOnSell( + tokenAddress, + recommenderId, + buyTimeStamp, + sellDetailsData, + isSimulation + ); + return sellDetailsData; + } }