diff --git a/core/src/actions/swap.ts b/core/src/actions/swap.ts index 978c13a046f..6a5a72e3093 100644 --- a/core/src/actions/swap.ts +++ b/core/src/actions/swap.ts @@ -23,6 +23,10 @@ import settings from "../core/settings.ts"; import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes/index.js"; import BigNumber from "bignumber.js"; import { WalletProvider } from "../providers/wallet.ts"; +import { TrustScoreProvider } from "../providers/trustScoreProvider"; +import { TokenProvider } from "../providers/token"; +import { TrustScoreDatabase } from "../adapters/trustScoreDatabase"; +import { v4 as uuidv4 } from "uuid"; async function swapToken( connection: Connection, @@ -155,7 +159,7 @@ async function getTokensInWallet(runtime: IAgentRuntime) { } // check if the token symbol is in the wallet -async function checkTokenInWallet(runtime: IAgentRuntime, tokenSymbol: string) { +async function getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) { try { const items = await getTokensInWallet(runtime); const token = items.find((item) => item.symbol === tokenSymbol); @@ -212,6 +216,8 @@ export const executeSwap: Action = { }); console.log("Response:", response); + const type = + response.inputTokenSymbol?.toUpperCase() === "SOL" ? "buy" : "sell"; // Add SOL handling logic if (response.inputTokenSymbol?.toUpperCase() === "SOL") { @@ -227,7 +233,7 @@ export const executeSwap: Action = { console.log( `Attempting to resolve CA for input token symbol: ${response.inputTokenSymbol}` ); - response.inputTokenCA = await checkTokenInWallet( + response.inputTokenCA = await getTokenFromWallet( runtime, response.inputTokenSymbol ); @@ -247,7 +253,7 @@ export const executeSwap: Action = { console.log( `Attempting to resolve CA for output token symbol: ${response.outputTokenSymbol}` ); - response.outputTokenCA = await checkTokenInWallet( + response.outputTokenCA = await getTokenFromWallet( runtime, response.outputTokenSymbol ); @@ -387,6 +393,69 @@ export const executeSwap: Action = { ); } + if (type === "buy") { + const tokenProvider = new TokenProvider(response.outputTokenCA); + const module = await import("better-sqlite3"); + const Database = module.default; + const trustScoreDb = new TrustScoreDatabase( + new Database(":memory:") + ); + // add or get recommender + const uuid = uuidv4(); + const recommender = await trustScoreDb.getOrCreateRecommender({ + id: uuid, + address: walletPublicKey.toString(), + solanaPubkey: walletPublicKey.toString(), + }); + + const trustScoreDatabase = new TrustScoreProvider( + tokenProvider, + trustScoreDb + ); + // save the trade + const tradeData = { + buy_amount: response.amount, + is_simulation: false, + }; + await trustScoreDatabase.createTradePerformance( + response.outputTokenCA, + recommender.id, + tradeData + ); + } else if (type === "sell") { + const tokenProvider = new TokenProvider(response.inputTokenCA); + const module = await import("better-sqlite3"); + const Database = module.default; + const trustScoreDb = new TrustScoreDatabase( + new Database(":memory:") + ); + // add or get recommender + const uuid = uuidv4(); + const recommender = await trustScoreDb.getOrCreateRecommender({ + id: uuid, + address: walletPublicKey.toString(), + solanaPubkey: walletPublicKey.toString(), + }); + + const trustScoreDatabase = new TrustScoreProvider( + tokenProvider, + trustScoreDb + ); + // save the trade + const sellDetails = { + sell_amount: response.amount, + sell_recommender_id: recommender.id, + }; + const sellTimeStamp = new Date().getTime().toString(); + await trustScoreDatabase.updateSellDetails( + response.inputTokenCA, + recommender.id, + sellTimeStamp, + sellDetails, + false + ); + } + console.log("Swap completed successfully!"); console.log(`Transaction ID: ${txid}`); diff --git a/core/src/adapters/trustScoreDatabase.ts b/core/src/adapters/trustScoreDatabase.ts index b206fd1923e..5ef6231f5e0 100644 --- a/core/src/adapters/trustScoreDatabase.ts +++ b/core/src/adapters/trustScoreDatabase.ts @@ -172,7 +172,7 @@ export class TrustScoreDatabase { risk_score REAL DEFAULT 0, consistency_score REAL DEFAULT 0, virtual_confidence REAL DEFAULT 0, - last_active_date DATETIME DEFAULT CURRENT_TIMESTAMP + last_active_date DATETIME DEFAULT CURRENT_TIMESTAMP, trust_decay REAL DEFAULT 0, last_updated DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (recommender_id) REFERENCES recommenders(id) ON DELETE CASCADE @@ -349,6 +349,61 @@ export class TrustScoreDatabase { return recommender || null; } + /** + * Retrieves an existing recommender or creates a new one if not found. + * Also initializes metrics for the recommender if they haven't been initialized yet. + * @param recommender Recommender object containing at least one identifier + * @returns Recommender object with all details, or null if failed + */ + getOrCreateRecommender(recommender: Recommender): Recommender | null { + try { + // Begin a transaction + const transaction = this.db.transaction(() => { + // Attempt to retrieve the recommender + const existingRecommender = this.getRecommender( + recommender.address + ); + if (existingRecommender) { + // Recommender exists, ensure metrics are initialized + this.initializeRecommenderMetrics(existingRecommender.id!); + return existingRecommender; + } + + // Recommender does not exist, create a new one + const newRecommenderId = this.addRecommender(recommender); + if (!newRecommenderId) { + throw new Error("Failed to add new recommender."); + } + + // Initialize metrics for the new recommender + const metricsInitialized = + this.initializeRecommenderMetrics(newRecommenderId); + if (!metricsInitialized) { + throw new Error( + "Failed to initialize recommender metrics." + ); + } + + // Retrieve and return the newly created recommender + const newRecommender = this.getRecommender(newRecommenderId); + if (!newRecommender) { + throw new Error( + "Failed to retrieve the newly created recommender." + ); + } + + return newRecommender; + }); + + // Execute the transaction and return the recommender + const recommenderResult = transaction(); + return recommenderResult; + } catch (error) { + console.error("Error in getOrCreateRecommender:", error); + return null; + } + } + /** * Initializes metrics for a recommender if not present. * @param recommenderId Recommender's UUID @@ -548,6 +603,8 @@ export class TrustScoreDatabase { performance.tokenAddress, performance.priceChange24h, performance.volumeChange24h, + performance.trade_24h_change, + performance.liquidity, performance.liquidityChange24h, performance.holderChange24h, // Ensure column name matches schema performance.rugPull ? 1 : 0, @@ -916,7 +973,7 @@ export class TrustScoreDatabase { market_cap_change = ?, sell_liquidity = ?, liquidity_change = ?, - rapidDump = ? + rapidDump = ?, sell_recommender_id = ? WHERE token_address = ? @@ -1016,6 +1073,56 @@ export class TrustScoreDatabase { }; } + /** + * Retrieves the latest trade performance metrics without requiring buyTimeStamp. + * @param tokenAddress Token's address + * @param recommenderId Recommender's UUID + * @param isSimulation Whether the trade is a simulation. If true, retrieves from simulation_trade; otherwise, from trade. + * @returns TradePerformance object or null + */ + getLatestTradePerformance( + tokenAddress: string, + recommenderId: string, + isSimulation: boolean + ): TradePerformance | null { + const tableName = isSimulation ? "simulation_trade" : "trade"; + const sql = ` + SELECT * FROM ${tableName} + WHERE token_address = ? AND recommender_id = ? + ORDER BY buy_timeStamp DESC + LIMIT 1; + `; + const row = this.db.prepare(sql).get(tokenAddress, recommenderId) 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/core/src/providers/trustScoreProvider.ts b/core/src/providers/trustScoreProvider.ts index 1932d9ba587..f941111ac29 100644 --- a/core/src/providers/trustScoreProvider.ts +++ b/core/src/providers/trustScoreProvider.ts @@ -363,8 +363,7 @@ export class TrustScoreProvider { recommenderId: string, sellTimeStamp: string, sellDetails: sellDetails, - isSimulation: boolean, - buyTimeStamp: string + isSimulation: boolean ) { const processedData: ProcessedTokenData = await this.tokenProvider.getProcessedTokenData(); @@ -377,12 +376,12 @@ export class TrustScoreProvider { const sellSol = sellDetails.sell_amount / parseFloat(solPrice); const sell_value_usd = sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getTradePerformance( + const trade = await this.trustScoreDb.getLatestTradePerformance( tokenAddress, recommenderId, - buyTimeStamp, isSimulation ); + const buyTimeStamp = trade.buy_timeStamp; const marketCap = processedData.dexScreenerData.pairs[0]?.marketCap || 0; const liquidity =