From f0c561757c71f42753ce7745508afc43353a4ed5 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Nov 2024 13:40:03 -0800 Subject: [PATCH 1/7] Support system prompt and google models --- packages/core/src/generation.ts | 27 +++++++++++++++++++++++---- packages/core/src/models.ts | 9 ++++----- packages/core/src/runtime.ts | 13 ++++++++++--- packages/core/src/types.ts | 7 ++++++- pnpm-lock.yaml | 27 --------------------------- 5 files changed, 43 insertions(+), 40 deletions(-) diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index f6cdbf7227..d9c084d473 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -11,6 +11,7 @@ import { default as tiktoken, TiktokenModel } from "tiktoken"; import Together from "together-ai"; import { elizaLogger } from "./index.ts"; import models from "./models.ts"; +import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { parseBooleanFromText, parseJsonArrayFromText, @@ -64,6 +65,7 @@ export async function generateText({ const presence_penalty = models[provider].settings.presence_penalty; const max_context_length = models[provider].settings.maxInputTokens; const max_response_length = models[provider].settings.maxOutputTokens; + const systemPrompt = runtime.systemPrompt; const apiKey = runtime.token; @@ -104,6 +106,22 @@ export async function generateText({ break; } + case ModelProviderName.GOOGLE: + const google = createGoogleGenerativeAI(); + + const { text: anthropicResponse } = await aiGenerateText({ + model: google(model), + prompt: context, + ...(systemPrompt ? { system: systemPrompt } : {}), + temperature: temperature, + maxTokens: max_response_length, + frequencyPenalty: frequency_penalty, + presencePenalty: presence_penalty, + }); + + response = anthropicResponse; + break; + case ModelProviderName.ANTHROPIC: { elizaLogger.log("Initializing Anthropic model."); @@ -214,7 +232,6 @@ export async function generateText({ break; } - case ModelProviderName.OPENROUTER: { elizaLogger.log("Initializing OpenRouter model."); const serverUrl = models[provider].endpoint; @@ -238,7 +255,6 @@ export async function generateText({ break; } - case ModelProviderName.OLLAMA: { console.log("Initializing Ollama model."); @@ -425,10 +441,13 @@ export async function generateTrueOrFalse({ modelClass: string; }): Promise { let retryDelay = 1000; - console.log("modelClass", modelClass) + console.log("modelClass", modelClass); const stop = Array.from( - new Set([...(models[runtime.modelProvider].settings.stop || []), ["\n"]]) + new Set([ + ...(models[runtime.modelProvider].settings.stop || []), + ["\n"], + ]) ) as string[]; while (true) { diff --git a/packages/core/src/models.ts b/packages/core/src/models.ts index 4112c1b0d1..0b3d5de47c 100644 --- a/packages/core/src/models.ts +++ b/packages/core/src/models.ts @@ -137,9 +137,9 @@ const models: Models = { temperature: 0.3, }, model: { - [ModelClass.SMALL]: "gemini-1.5-flash", - [ModelClass.MEDIUM]: "gemini-1.5-flash", - [ModelClass.LARGE]: "gemini-1.5-pro", + [ModelClass.SMALL]: "gemini-1.5-flash-latest", + [ModelClass.MEDIUM]: "gemini-1.5-flash-latest", + [ModelClass.LARGE]: "gemini-1.5-pro-latest", [ModelClass.EMBEDDING]: "text-embedding-004", }, }, @@ -187,8 +187,7 @@ const models: Models = { settings.LARGE_OPENROUTER_MODEL || settings.OPENROUTER_MODEL || "nousresearch/hermes-3-llama-3.1-405b", - [ModelClass.EMBEDDING]: - "text-embedding-3-small", + [ModelClass.EMBEDDING]: "text-embedding-3-small", }, }, [ModelProviderName.OLLAMA]: { diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 2731ed0ec4..22182af2cf 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -92,6 +92,12 @@ export class AgentRuntime implements IAgentRuntime { */ modelProvider: ModelProviderName; + /** + * + * The system prompt to use for the agent. + */ + systemPrompt?: string; + /** * Fetch function to use * Some environments may not have access to the global fetch function and need a custom fetch override. @@ -207,6 +213,7 @@ export class AgentRuntime implements IAgentRuntime { fetch?: typeof fetch | unknown; speechModelPath?: string; }) { + this.systemPrompt = opts.character.system; this.#conversationLength = opts.conversationLength ?? this.#conversationLength; this.databaseAdapter = opts.databaseAdapter; @@ -498,14 +505,14 @@ export class AgentRuntime implements IAgentRuntime { * @returns The results of the evaluation. */ async evaluate(message: Memory, state?: State, didRespond?: boolean) { - console.log("Evaluate: ", didRespond) + console.log("Evaluate: ", didRespond); const evaluatorPromises = this.evaluators.map( async (evaluator: Evaluator) => { - console.log("Evaluating", evaluator.name) + console.log("Evaluating", evaluator.name); if (!evaluator.handler) { return null; } - if(!didRespond && !evaluator.alwaysRun) { + if (!didRespond && !evaluator.alwaysRun) { return null; } const result = await evaluator.validate(this, message, state); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 4fa54fcb90..0c5caba3d9 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -526,6 +526,7 @@ export interface IAgentRuntime { providers: Provider[]; actions: Action[]; evaluators: Evaluator[]; + systemPrompt?: string; messageManager: IMemoryManager; descriptionManager: IMemoryManager; @@ -550,7 +551,11 @@ export interface IAgentRuntime { state?: State, callback?: HandlerCallback ): Promise; - evaluate(message: Memory, state?: State, didRespond?: boolean): Promise; + evaluate( + message: Memory, + state?: State, + didRespond?: boolean + ): Promise; ensureParticipantExists(userId: UUID, roomId: UUID): Promise; ensureUserExists( userId: UUID, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ac65575ff..89da935c5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -709,33 +709,6 @@ importers: specifier: 7.1.0 version: 7.1.0 - packages/test: - dependencies: - '@ai16z/adapter-sqlite': - specifier: workspace:* - version: link:../adapter-sqlite - '@ai16z/adapter-sqljs': - specifier: workspace:* - version: link:../adapter-sqljs - '@ai16z/adapter-supabase': - specifier: workspace:* - version: link:../adapter-supabase - '@ai16z/eliza': - specifier: workspace:* - version: link:../core - '@ai16z/plugin-bootstrap': - specifier: workspace:* - version: link:../plugin-bootstrap - '@ai16z/plugin-node': - specifier: workspace:* - version: link:../plugin-node - tsup: - specifier: ^8.3.5 - version: 8.3.5(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) - whatwg-url: - specifier: 7.1.0 - version: 7.1.0 - packages: '@ai-sdk/anthropic@0.0.53': From 09095cc4d5e99683abbc65f16cdcc5166bbb7d1f Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Nov 2024 13:43:05 -0800 Subject: [PATCH 2/7] Remove system prompt since already implemented. --- packages/core/src/generation.ts | 6 ++++-- packages/core/src/runtime.ts | 7 ------- packages/core/src/types.ts | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index d9c084d473..43cd2e240a 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -65,7 +65,6 @@ export async function generateText({ const presence_penalty = models[provider].settings.presence_penalty; const max_context_length = models[provider].settings.maxInputTokens; const max_response_length = models[provider].settings.maxOutputTokens; - const systemPrompt = runtime.systemPrompt; const apiKey = runtime.token; @@ -112,7 +111,10 @@ export async function generateText({ const { text: anthropicResponse } = await aiGenerateText({ model: google(model), prompt: context, - ...(systemPrompt ? { system: systemPrompt } : {}), + system: + runtime.character.system ?? + settings.SYSTEM_PROMPT ?? + undefined, temperature: temperature, maxTokens: max_response_length, frequencyPenalty: frequency_penalty, diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 22182af2cf..6ec9494704 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -92,12 +92,6 @@ export class AgentRuntime implements IAgentRuntime { */ modelProvider: ModelProviderName; - /** - * - * The system prompt to use for the agent. - */ - systemPrompt?: string; - /** * Fetch function to use * Some environments may not have access to the global fetch function and need a custom fetch override. @@ -213,7 +207,6 @@ export class AgentRuntime implements IAgentRuntime { fetch?: typeof fetch | unknown; speechModelPath?: string; }) { - this.systemPrompt = opts.character.system; this.#conversationLength = opts.conversationLength ?? this.#conversationLength; this.databaseAdapter = opts.databaseAdapter; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 0c5caba3d9..7d61a241f5 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -526,7 +526,6 @@ export interface IAgentRuntime { providers: Provider[]; actions: Action[]; evaluators: Evaluator[]; - systemPrompt?: string; messageManager: IMemoryManager; descriptionManager: IMemoryManager; From c7e9bf0935b05affb3c37a7f9ca3c68e90c1aad9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Nov 2024 13:45:35 -0800 Subject: [PATCH 3/7] Update env example and readme with gemini key --- .env.example | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.env.example b/.env.example index 444bf1a79a..081f92f279 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,7 @@ OPENAI_API_KEY=sk-* # OpenAI API key, starting with sk- REDPILL_API_KEY= # REDPILL API Key GROQ_API_KEY=gsk_* OPENROUTER_API_KEY= +GOOGLE_GENERATIVE_AI_API_KEY= # Gemini API key ELEVENLABS_XI_API_KEY= # API key from elevenlabs diff --git a/README.md b/README.md index 3786057ef1..c8c2b32637 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ DISCORD_APPLICATION_ID= DISCORD_API_TOKEN= # Bot token OPENAI_API_KEY=sk-* # OpenAI API key, starting with sk- ELEVENLABS_XI_API_KEY= # API key from elevenlabs +GOOGLE_GENERATIVE_AI_API_KEY= # Gemini API key # ELEVENLABS SETTINGS ELEVENLABS_MODEL_ID=eleven_multilingual_v2 From f4dbc3e0b04c30eb64b6e8eb469cc75255c2450c Mon Sep 17 00:00:00 2001 From: cvartanian <64427702+cvartanian@users.noreply.github.com> Date: Sat, 9 Nov 2024 19:38:15 -0500 Subject: [PATCH 4/7] Working PostGres Adapter --- packages/adapter-postgres/src/index.ts | 215 +++++++++++++------------ 1 file changed, 108 insertions(+), 107 deletions(-) diff --git a/packages/adapter-postgres/src/index.ts b/packages/adapter-postgres/src/index.ts index b081e50aea..00d61867da 100644 --- a/packages/adapter-postgres/src/index.ts +++ b/packages/adapter-postgres/src/index.ts @@ -26,7 +26,6 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { connectionTimeoutMillis: 2000, }); - // Register error handler for pool this.pool.on("error", (err) => { console.error("Unexpected error on idle client", err); }); @@ -37,22 +36,15 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { async testConnection(): Promise { let client; try { - // Attempt to get a client from the pool client = await this.pool.connect(); - - // Test the connection with a simple query const result = await client.query("SELECT NOW()"); console.log("Database connection test successful:", result.rows[0]); - return true; } catch (error) { console.error("Database connection test failed:", error); throw new Error(`Failed to connect to database: ${error.message}`); } finally { - // Make sure to release the client back to the pool - if (client) { - client.release(); - } + if (client) client.release(); } } @@ -73,9 +65,9 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { const { rows } = await client.query( - `SELECT id, "userId", "roomId", last_message_read - FROM participants - WHERE "userId" = $1`, + `SELECT id, "userId", "roomId", "last_message_read" + FROM participants + WHERE "userId" = $1`, [userId] ); return rows as Participant[]; @@ -91,7 +83,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { const { rows } = await client.query( - `SELECT userState FROM participants WHERE "roomId" = $1 AND userId = $2`, + `SELECT "userState" FROM participants WHERE "roomId" = $1 AND "userId" = $2`, [roomId, userId] ); return rows.length > 0 ? rows[0].userState : null; @@ -116,14 +108,14 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { let queryParams = [params.tableName, ...params.roomIds]; if (params.agentId) { - query += ` AND "userId" = $${params.roomIds.length + 2}`; + query += ` AND "agentId" = $${params.roomIds.length + 2}`; queryParams = [...queryParams, params.agentId]; } const { rows } = await client.query(query, queryParams); return rows.map((row) => ({ ...row, - content: JSON.parse(row.content), + content: typeof row.content === "string" ? JSON.parse(row.content) : row.content, })); } finally { client.release(); @@ -169,14 +161,12 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { if (rows.length === 0) return null; const account = rows[0]; - console.log("account", account); return { ...account, - details: - typeof account.details === "string" - ? JSON.parse(account.details) - : account.details, + details: typeof account.details === "string" + ? JSON.parse(account.details) + : account.details, }; } finally { client.release(); @@ -188,7 +178,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { try { await client.query( `INSERT INTO accounts (id, name, username, email, "avatarUrl", details) - VALUES ($1, $2, $3, $4, $5, $6)`, + VALUES ($1, $2, $3, $4, $5, $6)`, [ account.id ?? v4(), account.name, @@ -212,17 +202,16 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { try { const { rows } = await client.query( `SELECT a.id, a.name, a.username, a.details - FROM participants p - LEFT JOIN accounts a ON p."userId" = a.id - WHERE p."roomId" = $1`, + FROM participants p + LEFT JOIN accounts a ON p."userId" = a.id + WHERE p."roomId" = $1`, [params.roomId] ); return rows.map((row) => ({ ...row, - details: - typeof row.details === "string" - ? JSON.parse(row.details) - : row.details, + details: typeof row.details === "string" + ? JSON.parse(row.details) + : row.details, })); } finally { client.release(); @@ -240,10 +229,9 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { return { ...rows[0], - content: - typeof rows[0].content === "string" - ? JSON.parse(rows[0].content) - : rows[0].content, + content: typeof rows[0].content === "string" + ? JSON.parse(rows[0].content) + : rows[0].content, }; } finally { client.release(); @@ -269,13 +257,13 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { await client.query( `INSERT INTO memories ( - id, type, content, embedding, "userId", "roomId", "agentId", "unique", "createdAt" - ) VALUES ($1, $2, $3, $4::vector, $5::uuid, $6::uuid, $7::uuid, $8, to_timestamp($9/1000.0))`, + id, type, content, embedding, "userId", "roomId", "agentId", "unique", "createdAt" + ) VALUES ($1, $2, $3, $4, $5::uuid, $6::uuid, $7::uuid, $8, to_timestamp($9/1000.0))`, [ memory.id ?? v4(), tableName, JSON.stringify(memory.content), - `[${memory.embedding.join(",")}]`, + memory.embedding ? `[${memory.embedding.join(",")}]` : null, memory.userId, memory.roomId, memory.agentId, @@ -299,19 +287,19 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { let sql = ` - SELECT *, - 1 - (embedding <-> $3) as similarity - FROM memories - WHERE type = $1 AND "roomId" = $2 - `; + SELECT *, + 1 - (embedding <-> $3) as similarity + FROM memories + WHERE type = $1 AND "roomId" = $2 + `; if (params.unique) { sql += ` AND "unique" = true`; } sql += ` AND 1 - (embedding <-> $3) >= $4 - ORDER BY embedding <-> $3 - LIMIT $5`; + ORDER BY embedding <-> $3 + LIMIT $5`; const { rows } = await client.query(sql, [ params.tableName, @@ -323,7 +311,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { return rows.map((row) => ({ ...row, - content: JSON.parse(row.content), + content: typeof row.content === "string" ? JSON.parse(row.content) : row.content, similarity: row.similarity, })); } finally { @@ -366,7 +354,8 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { } if (params.agentId) { - sql += " AND agentId = $3"; + paramCount++; + sql += ` AND "agentId" = $${paramCount}`; values.push(params.agentId); } @@ -383,10 +372,9 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const { rows } = await client.query(sql, values); return rows.map((row) => ({ ...row, - content: - typeof row.content === "string" - ? JSON.parse(row.content) - : row.content, + content: typeof row.content === "string" + ? JSON.parse(row.content) + : row.content, })); } finally { client.release(); @@ -424,10 +412,9 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const { rows } = await client.query(sql, values); return rows.map((row) => ({ ...row, - objectives: - typeof row.objectives === "string" - ? JSON.parse(row.objectives) - : row.objectives, + objectives: typeof row.objectives === "string" + ? JSON.parse(row.objectives) + : row.objectives, })); } finally { client.release(); @@ -438,13 +425,8 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { await client.query( - "UPDATE goals SET name = $1, status = $2, objectives = $3 WHERE id = $4", - [ - goal.name, - goal.status, - JSON.stringify(goal.objectives), - goal.id, - ] + `UPDATE goals SET name = $1, status = $2, objectives = $3 WHERE id = $4`, + [goal.name, goal.status, JSON.stringify(goal.objectives), goal.id] ); } finally { client.release(); @@ -456,7 +438,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { try { await client.query( `INSERT INTO goals (id, "roomId", "userId", name, status, objectives) - VALUES ($1, $2, $3, $4, $5, $6)`, + VALUES ($1, $2, $3, $4, $5, $6)`, [ goal.id ?? v4(), goal.roomId, @@ -484,9 +466,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { const newRoomId = roomId || v4(); - await client.query("INSERT INTO rooms (id) VALUES ($1)", [ - newRoomId, - ]); + await client.query("INSERT INTO rooms (id) VALUES ($1)", [newRoomId]); return newRoomId as UUID; } finally { client.release(); @@ -514,7 +494,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { try { await client.query( `INSERT INTO relationships (id, "userA", "userB", "userId") - VALUES ($1, $2, $3, $4)`, + VALUES ($1, $2, $3, $4)`, [v4(), params.userA, params.userB, params.userA] ); return true; @@ -534,7 +514,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { try { const { rows } = await client.query( `SELECT * FROM relationships - WHERE ("userA" = $1 AND "userB" = $2) OR ("userA" = $2 AND "userB" = $1)`, + WHERE ("userA" = $1 AND "userB" = $2) OR ("userA" = $2 AND "userB" = $1)`, [params.userA, params.userB] ); return rows.length > 0 ? rows[0] : null; @@ -566,15 +546,30 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { }): Promise<{ embedding: number[]; levenshtein_score: number }[]> { const client = await this.pool.connect(); try { + // Get the JSON field content as text first const sql = ` - SELECT embedding, - levenshtein($1, content->$2->$3) as levenshtein_score - FROM memories - WHERE type = $4 - ORDER BY levenshtein_score - LIMIT $5 - `; - + WITH content_text AS ( + SELECT + embedding, + COALESCE( + content->$2->>$3, + '' + ) as content_text + FROM memories + WHERE type = $4 + AND content->$2->>$3 IS NOT NULL + ) + SELECT + embedding, + levenshtein( + $1, + content_text + ) as levenshtein_score + FROM content_text + ORDER BY levenshtein_score + LIMIT $5 + `; + const { rows } = await client.query(sql, [ opts.query_input, opts.query_field_name, @@ -582,11 +577,14 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { opts.query_table_name, opts.query_match_count, ]); - + return rows.map((row) => ({ embedding: row.embedding, levenshtein_score: row.levenshtein_score, })); + } catch (error) { + console.error('Error in getCachedEmbeddings:', error); + throw error; } finally { client.release(); } @@ -601,7 +599,8 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { await client.query( - 'INSERT INTO logs (body, "userId", "roomId", type) VALUES ($1, $2, $3, $4)', + `INSERT INTO logs (body, "userId", "roomId", type) + VALUES ($1, $2, $3, $4)`, [params.body, params.userId, params.roomId, params.type] ); } finally { @@ -622,15 +621,14 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { ): Promise { const client = await this.pool.connect(); try { - // Format the embedding array as a proper vector string const vectorStr = `[${embedding.join(",")}]`; let sql = ` - SELECT *, - 1 - (embedding <-> $1::vector) as similarity - FROM memories - WHERE type = $2 - `; + SELECT *, + 1 - (embedding <-> $1::vector) as similarity + FROM memories + WHERE type = $2 + `; const values: any[] = [vectorStr, params.tableName]; let paramCount = 2; @@ -640,7 +638,8 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { } if (params.agentId) { - sql += " AND agentId = $3"; + paramCount++; + sql += ` AND "agentId" = $${paramCount}`; values.push(params.agentId); } @@ -667,10 +666,9 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const { rows } = await client.query(sql, values); return rows.map((row) => ({ ...row, - content: - typeof row.content === "string" - ? JSON.parse(row.content) - : row.content, + content: typeof row.content === "string" + ? JSON.parse(row.content) + : row.content, similarity: row.similarity, })); } finally { @@ -682,7 +680,8 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { await client.query( - 'INSERT INTO participants (id, "userId", "roomId") VALUES ($1, $2, $3)', + `INSERT INTO participants (id, "userId", "roomId") + VALUES ($1, $2, $3)`, [v4(), userId, roomId] ); return true; @@ -698,7 +697,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { await client.query( - 'DELETE FROM participants WHERE "userId" = $1 AND "roomId" = $2', + `DELETE FROM participants WHERE "userId" = $1 AND "roomId" = $2`, [userId, roomId] ); return true; @@ -716,10 +715,10 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { }): Promise { const client = await this.pool.connect(); try { - await client.query("UPDATE goals SET status = $1 WHERE id = $2", [ - params.status, - params.goalId, - ]); + await client.query( + "UPDATE goals SET status = $1 WHERE id = $2", + [params.status, params.goalId] + ); } finally { client.release(); } @@ -741,7 +740,7 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { const client = await this.pool.connect(); try { await client.query( - "DELETE FROM memories WHERE type = $1 AND roomId = $2", + `DELETE FROM memories WHERE type = $1 AND "roomId" = $2`, [tableName, roomId] ); } finally { @@ -773,9 +772,10 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { async removeAllGoals(roomId: UUID): Promise { const client = await this.pool.connect(); try { - await client.query(`DELETE FROM goals WHERE "roomId" = $1`, [ - roomId, - ]); + await client.query( + `DELETE FROM goals WHERE "roomId" = $1`, + [roomId] + ); } finally { client.release(); } @@ -807,21 +807,21 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { client.release(); } } + async getActorDetails(params: { roomId: string }): Promise { const sql = ` - SELECT - a.id, - a.name, - a.username, - COALESCE(a.details::jsonb, '{}'::jsonb) as details - FROM participants p - LEFT JOIN accounts a ON p.userId = a.id - WHERE p.roomId = $1 - `; + SELECT + a.id, + a.name, + a.username, + COALESCE(a.details::jsonb, '{}'::jsonb) as details + FROM participants p + LEFT JOIN accounts a ON p."userId" = a.id + WHERE p."roomId" = $1 + `; try { const result = await this.pool.query(sql, [params.roomId]); - return result.rows.map((row) => ({ ...row, details: row.details, // PostgreSQL automatically handles JSON parsing @@ -832,4 +832,5 @@ export class PostgresDatabaseAdapter extends DatabaseAdapter { } } } -export default PostgresDatabaseAdapter; + +export default PostgresDatabaseAdapter; \ No newline at end of file From ed438d7cd294fb65b227a1ca7c2e25bb433a0e5b Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Sun, 10 Nov 2024 02:49:23 +0200 Subject: [PATCH 5/7] searchDexScreenerData, shouldTrade, getBuyAmounts, getTokenFromWallet Signed-off-by: MarcoMandar --- .../src/adapters/trustScoreDatabase.ts | 66 +++++ .../plugin-solana/src/evaluators/trust.ts | 171 +++++++----- packages/plugin-solana/src/providers/token.ts | 258 +++++++++++++++++- .../src/providers/trustScoreProvider.ts | 37 ++- .../plugin-solana/src/providers/wallet.ts | 2 +- packages/plugin-solana/src/types/token.ts | 13 + 6 files changed, 471 insertions(+), 76 deletions(-) diff --git a/packages/plugin-solana/src/adapters/trustScoreDatabase.ts b/packages/plugin-solana/src/adapters/trustScoreDatabase.ts index d88a9632b9..417bdf76f9 100644 --- a/packages/plugin-solana/src/adapters/trustScoreDatabase.ts +++ b/packages/plugin-solana/src/adapters/trustScoreDatabase.ts @@ -403,6 +403,72 @@ export class TrustScoreDatabase { } } + // get Or Create Recommender with discord id + + /** + * 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 discordId Discord ID of the recommender + * @returns Recommender object with all details, or null if failed + */ + + async getOrCreateRecommenderWithDiscordId( + discordId: string + ): Promise { + try { + // Begin a transaction + const transaction = this.db.transaction(() => { + // Attempt to retrieve the recommender + const existingRecommender = this.getRecommender(discordId); + if (existingRecommender) { + // Recommender exists, ensure metrics are initialized + this.initializeRecommenderMetrics(existingRecommender.id!); + return existingRecommender; + } + + // Recommender does not exist, create a new one + const newRecommender = { + id: uuidv4(), + address: discordId, + discordId: discordId, + }; + const newRecommenderId = this.addRecommender(newRecommender); + 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 recommender = this.getRecommender(newRecommenderId); + if (!recommender) { + throw new Error( + "Failed to retrieve the newly created recommender." + ); + } + + return recommender; + }); + + // Execute the transaction and return the recommender + const recommenderResult = transaction(); + return recommenderResult; + } catch (error) { + console.error( + "Error in getOrCreateRecommenderWithDiscordId:", + error + ); + return null; + } + } + /** * Initializes metrics for a recommender if not present. * @param recommenderId Recommender's UUID diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index 93e2ea0e3e..fcf68a3ef5 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -1,5 +1,8 @@ import { composeContext } from "@ai16z/eliza/src/context.ts"; -import { generateObjectArray, generateTrueOrFalse } from "@ai16z/eliza/src/generation.ts"; +import { + generateObjectArray, + generateTrueOrFalse, +} from "@ai16z/eliza/src/generation.ts"; import { MemoryManager } from "@ai16z/eliza/src/memory.ts"; import { booleanFooter } from "@ai16z/eliza/src/parsing.ts"; import { @@ -13,7 +16,9 @@ import { import { stringToUuid } from "@ai16z/eliza/src/uuid.ts"; import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; import { TokenProvider } from "../providers/token.ts"; +import { WalletProvider } from "../providers/wallet.ts"; import { TrustScoreDatabase } from "../adapters/trustScoreDatabase.ts"; +import { Connection, PublicKey } from "@solana/web3.js"; const shouldProcessTemplate = `# Task: Decide if the recent messages should be processed for token recommendations. @@ -37,8 +42,7 @@ export const formatRecommendations = (recommendations: Memory[]) => { return finalMessageStrings; }; -const recommendationTemplate = - `TASK: Extract recommendations to buy or sell memecoins from the conversation as an array of objects in JSON format. +const recommendationTemplate = `TASK: Extract recommendations to buy or sell memecoins from the conversation as an array of objects in JSON format. Memecoins usually have a ticker and a contract address. Additionally, recommenders may make recommendations with some amount of conviction. The amount of conviction in their recommendation can be none, low, medium, or high. Recommenders can make recommendations to buy, not buy, sell and not sell. @@ -77,7 +81,7 @@ Response should be a JSON object array inside a JSON markdown block. Correct res \`\`\``; async function handler(runtime: IAgentRuntime, message: Memory) { - console.log("Evaluating for trust") + console.log("Evaluating for trust"); const state = await runtime.composeState(message); const { agentId, roomId } = state; @@ -95,10 +99,10 @@ async function handler(runtime: IAgentRuntime, message: Memory) { }); if (!shouldProcess) { - console.log("Skipping process") + console.log("Skipping process"); return []; } - + // Get recent recommendations const recommendationsManager = new MemoryManager({ runtime, @@ -125,49 +129,83 @@ async function handler(runtime: IAgentRuntime, message: Memory) { modelClass: ModelClass.LARGE, }); - console.log("recommendations", recommendations) + console.log("recommendations", recommendations); if (!recommendations) { return []; } // If the recommendation is already known or corrupted, remove it - const filteredRecommendations = recommendations - .filter((rec) => { - return ( - !rec.alreadyKnown && - (rec.ticker || rec.contractAddress) && - rec.recommender && - rec.conviction && - rec.recommender.trim() !== "" - ); - }) + const filteredRecommendations = recommendations.filter((rec) => { + return ( + !rec.alreadyKnown && + (rec.ticker || rec.contractAddress) && + rec.recommender && + rec.conviction && + rec.recommender.trim() !== "" + ); + }); for (const rec of filteredRecommendations) { + // create the wallet provider and token provider + const walletProvider = new WalletProvider( + new Connection("https://api.mainnet-beta.solana.com"), + new PublicKey("Main Wallet") // TODO: get the wallet public key from the runtime + ); + const tokenProvider = new TokenProvider( + rec.contractAddress, + walletProvider + ); // TODO: Check to make sure the contract address is valid, it's the right one, etc - if(!rec.contractAddress) { - console.warn("Not implemented: try to resolve CA from ticker") - continue; + // + if (!rec.contractAddress) { + const tokenAddress = await tokenProvider.getTokenFromWallet( + runtime, + rec.ticker + ); + rec.contractAddress = tokenAddress; + if (!tokenAddress) { + // try to search for the symbol and return the contract address with they highest liquidity and market cap + const result = await tokenProvider.searchDexScreenerData( + rec.ticker + ); + const tokenAddress = result?.baseToken?.address; + rec.contractAddress = tokenAddress; + if (!tokenAddress) { + console.warn("Could not find contract address for token"); + // ask the user to provide the contract address + continue; + } + } } - // create the token provider and trust score manager - const tokenProvider = new TokenProvider(rec.contractAddress); + // create the trust score manager + const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db); - const trustScoreManager = new TrustScoreManager(tokenProvider, trustScoreDb); + const trustScoreManager = new TrustScoreManager( + tokenProvider, + trustScoreDb + ); // get actors from the database - const participants = await runtime.databaseAdapter.getParticipantsForRoom(message.roomId) + const participants = + await runtime.databaseAdapter.getParticipantsForRoom( + message.roomId + ); // find the first user Id from a user with the username that we extracted const user = participants.find(async (actor) => { const user = await runtime.databaseAdapter.getAccountById(actor); - return user.name.toLowerCase().trim() === rec.recommender.toLowerCase().trim(); + return ( + user.name.toLowerCase().trim() === + rec.recommender.toLowerCase().trim() + ); }); - if(!user) { - console.warn("Could not find user: ", rec.recommender) + if (!user) { + console.warn("Could not find user: ", rec.recommender); continue; } @@ -186,45 +224,44 @@ async function handler(runtime: IAgentRuntime, message: Memory) { // buy, dont buy, sell, dont sell - const buyAmounts = { - none: 0, - low: 10, - medium: 40, - high: 100 - } + const buyAmounts = await this.tokenProvider.getBuyAmounts(); - let buyAmount = buyAmounts[rec.conviction.toLowerCase().trim()] - if(!buyAmount) { + let buyAmount = buyAmounts[rec.conviction.toLowerCase().trim()]; + if (!buyAmount) { // handle annoying cases // for now just put in 10 sol buyAmount = 10; } - // TODO: scale this with market cap probably? - - // TODO: is this is a buy, sell, dont buy, or dont sell? + const shouldTrade = await this.tokenProvider.shouldTrade(); - switch(rec.type) { - case "buy": - // for now, lets just assume buy only, but we should implement - await trustScoreManager.createTradePerformance( - runtime, - rec.contractAddress, - userId, - { - buy_amount: rec.buyAmount, - is_simulation: true, - } + if (!shouldTrade) { + console.warn( + "There might be a problem with the token, not trading" ); - break; + continue; + } + + switch (rec.type) { + case "buy": + // for now, lets just assume buy only, but we should implement + await trustScoreManager.createTradePerformance( + runtime, + rec.contractAddress, + userId, + { + buy_amount: rec.buyAmount, + is_simulation: true, + } + ); + break; case "sell": case "dont_sell": case "dont_buy": - console.warn("Not implemented") - break; + console.warn("Not implemented"); + break; } - } return filteredRecommendations; @@ -242,7 +279,7 @@ export const trustEvaluator: Evaluator = { runtime: IAgentRuntime, message: Memory ): Promise => { - if(message.content.text.length < 5) { + if (message.content.text.length < 5) { return false; } @@ -262,15 +299,21 @@ None`, messages: [ { user: "{{user1}}", - content: { text: "Yo, have you checked out $SOLARUG? Dope new yield aggregator on Solana." }, + content: { + text: "Yo, have you checked out $SOLARUG? Dope new yield aggregator on Solana.", + }, }, { user: "{{user2}}", - content: { text: "Nah, I'm still trying to wrap my head around how yield farming even works haha. Is it risky?" }, + content: { + text: "Nah, I'm still trying to wrap my head around how yield farming even works haha. Is it risky?", + }, }, { user: "{{user1}}", - content: { text: "I mean, there's always risk in DeFi, but the $SOLARUG devs seem legit. Threw a few sol into the FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9 vault, farming's been smooth so far." }, + content: { + text: "I mean, there's always risk in DeFi, but the $SOLARUG devs seem legit. Threw a few sol into the FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9 vault, farming's been smooth so far.", + }, }, ] as ActionExample[], outcome: `\`\`\`json @@ -303,7 +346,9 @@ Recommendations about the actors: }, { user: "{{user2}}", - content: { text: "Idk man, feels like there's a new 'vault' or 'reserve' token every week on Sol. What happened to $COPETOKEN and $SOYLENT that you were shilling before?" }, + content: { + text: "Idk man, feels like there's a new 'vault' or 'reserve' token every week on Sol. What happened to $COPETOKEN and $SOYLENT that you were shilling before?", + }, }, { user: "{{user1}}", @@ -374,7 +419,7 @@ None`, "alreadyKnown": false } ] -\`\`\`` +\`\`\``, }, { @@ -428,7 +473,7 @@ None "alreadyKnown": false } ] -\`\`\`` +\`\`\``, }, { @@ -483,7 +528,7 @@ None`, "alreadyKnown": false } ] -\`\`\`` - } +\`\`\``, + }, ], -}; \ No newline at end of file +}; diff --git a/packages/plugin-solana/src/providers/token.ts b/packages/plugin-solana/src/providers/token.ts index ac1edd3775..01d84d9794 100644 --- a/packages/plugin-solana/src/providers/token.ts +++ b/packages/plugin-solana/src/providers/token.ts @@ -7,17 +7,20 @@ import { } from "@ai16z/eliza/src/types.ts"; import { DexScreenerData, - // DexScreenerPair, + DexScreenerPair, HolderData, ProcessedTokenData, TokenSecurityData, TokenTradeData, + CalculatedBuyAmounts, + Prices, } from "../types/token.ts"; -import { Connection } from "@solana/web3.js"; import * as fs from "fs"; import NodeCache from "node-cache"; import * as path from "path"; import { toBN } from "../bignumber.ts"; +import { WalletProvider, Item } from "./wallet.ts"; +import { Connection, PublicKey } from "@solana/web3.js"; const PROVIDER_CONFIG = { BIRDEYE_API: "https://public-api.birdeye.so", @@ -33,6 +36,7 @@ const PROVIDER_CONFIG = { TOKEN_SECURITY_ENDPOINT: "/defi/token_security?address=", TOKEN_TRADE_DATA_ENDPOINT: "/defi/v3/token/trade-data/single?address=", DEX_SCREENER_API: "https://api.dexscreener.com/latest/dex/tokens/", + MAIN_WALLET: "", }; export class TokenProvider { @@ -41,7 +45,8 @@ export class TokenProvider { constructor( // private connection: Connection, - private tokenAddress: string + private tokenAddress: string, + private walletProvider: WalletProvider ) { this.cache = new NodeCache({ stdTTL: 300 }); // 5 minutes cache const __dirname = path.resolve(); @@ -166,6 +171,128 @@ export class TokenProvider { throw lastError; } + async getTokensInWallet(runtime: IAgentRuntime): Promise { + const walletInfo = + await this.walletProvider.fetchPortfolioValue(runtime); + const items = walletInfo.items; + return items; + } + + // check if the token symbol is in the wallet + async getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) { + try { + const items = await this.getTokensInWallet(runtime); + const token = items.find((item) => item.symbol === tokenSymbol); + + if (token) { + return token.address; + } else { + return null; + } + } catch (error) { + console.error("Error checking token in wallet:", error); + return null; + } + } + + async fetchPrices(): Promise { + try { + const cacheKey = "prices"; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log("Returning cached prices."); + return cachedData; + } + const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; + const tokens = [SOL, BTC, ETH]; + const prices: Prices = { + solana: { usd: "0" }, + bitcoin: { usd: "0" }, + ethereum: { usd: "0" }, + }; + + for (const token of tokens) { + const response = await this.fetchWithRetry( + `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, + { + headers: { + "x-chain": "solana", + }, + } + ); + + if (response?.data?.value) { + const price = response.data.value.toString(); + prices[ + token === SOL + ? "solana" + : token === BTC + ? "bitcoin" + : "ethereum" + ].usd = price; + } else { + console.warn(`No price data available for token: ${token}`); + } + } + this.setCachedData(cacheKey, prices); + return prices; + } catch (error) { + console.error("Error fetching prices:", error); + throw error; + } + } + async calculateBuyAmounts(): Promise { + const dexScreenerData = await this.fetchDexScreenerData(); + const prices = await this.fetchPrices(); + const solPrice = toBN(prices.solana.usd); + + if (!dexScreenerData || dexScreenerData.pairs.length === 0) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + + // Get the first pair + const pair = dexScreenerData.pairs[0]; + const { liquidity, marketCap } = pair; + if (!liquidity || !marketCap) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + + if (liquidity.usd === 0) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + if (marketCap < 100000) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + + // impact percentages based on liquidity + const impactPercentages = { + LOW: 0.01, // 1% of liquidity + MEDIUM: 0.05, // 5% of liquidity + HIGH: 0.1, // 10% of liquidity + }; + + // Calculate buy amounts in USD + const lowBuyAmountUSD = liquidity.usd * impactPercentages.LOW; + const mediumBuyAmountUSD = liquidity.usd * impactPercentages.MEDIUM; + const highBuyAmountUSD = liquidity.usd * impactPercentages.HIGH; + + // Convert each buy amount to SOL + const lowBuyAmountSOL = toBN(lowBuyAmountUSD).div(solPrice).toNumber(); + const mediumBuyAmountSOL = toBN(mediumBuyAmountUSD) + .div(solPrice) + .toNumber(); + const highBuyAmountSOL = toBN(highBuyAmountUSD) + .div(solPrice) + .toNumber(); + + return { + none: 0, + low: lowBuyAmountSOL, + medium: mediumBuyAmountSOL, + high: highBuyAmountSOL, + }; + } + async fetchTokenSecurity(): Promise { const cacheKey = `tokenSecurity_${this.tokenAddress}`; const cachedData = this.getCachedData(cacheKey); @@ -472,6 +599,68 @@ export class TokenProvider { } } + async searchDexScreenerData( + symbol: string + ): Promise { + const cacheKey = `dexScreenerData_search_${symbol}`; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log("Returning cached search DexScreener data."); + return this.getHighestLiquidityPair(cachedData); + } + + const url = `https://api.dexscreener.com/latest/dex/search?q=${symbol}`; + try { + console.log(`Fetching DexScreener data for symbol: ${symbol}`); + const data = await fetch(url) + .then((res) => res.json()) + .catch((err) => { + console.error(err); + return null; + }); + + if (!data || !data.pairs || data.pairs.length === 0) { + throw new Error("No DexScreener data available"); + } + + const dexData: DexScreenerData = { + schemaVersion: data.schemaVersion, + pairs: data.pairs, + }; + + // Cache the result + this.setCachedData(cacheKey, dexData); + + // Return the pair with the highest liquidity and market cap + return this.getHighestLiquidityPair(dexData); + } catch (error) { + console.error(`Error fetching DexScreener data:`, error); + return null; + } + } + getHighestLiquidityPair(dexData: DexScreenerData): DexScreenerPair | null { + if (dexData.pairs.length === 0) { + return null; + } + + // Sort pairs by both liquidity and market cap to get the highest one + return dexData.pairs.reduce((highestPair, currentPair) => { + const currentLiquidity = currentPair.liquidity.usd; + const currentMarketCap = currentPair.marketCap; + const highestLiquidity = highestPair.liquidity.usd; + const highestMarketCap = highestPair.marketCap; + + if ( + currentLiquidity > highestLiquidity || + (currentLiquidity === highestLiquidity && + currentMarketCap > highestMarketCap) + ) { + return currentPair; + } + return highestPair; + }); + } + async analyzeHolderDistribution( tradeData: TokenTradeData ): Promise { @@ -733,6 +922,63 @@ export class TokenProvider { } } + async shouldTradeToken(): Promise { + try { + const tokenData = await this.getProcessedTokenData(); + const { tradeData, security, dexScreenerData } = tokenData; + const { ownerBalance, creatorBalance } = security; + const { liquidity, marketCap } = dexScreenerData.pairs[0]; + const liquidityUsd = toBN(liquidity.usd); + const marketCapUsd = toBN(marketCap); + const totalSupply = toBN(ownerBalance).plus(creatorBalance); + const ownerPercentage = toBN(ownerBalance).dividedBy(totalSupply); + const creatorPercentage = + toBN(creatorBalance).dividedBy(totalSupply); + const top10HolderPercent = toBN(tradeData.volume_24h_usd).dividedBy( + totalSupply + ); + const priceChange24hPercent = toBN( + tradeData.price_change_24h_percent + ); + const priceChange12hPercent = toBN( + tradeData.price_change_12h_percent + ); + const uniqueWallet24h = tradeData.unique_wallet_24h; + const volume24hUsd = toBN(tradeData.volume_24h_usd); + const volume24hUsdThreshold = 1000; + const priceChange24hPercentThreshold = 10; + const priceChange12hPercentThreshold = 5; + const top10HolderPercentThreshold = 0.05; + const uniqueWallet24hThreshold = 100; + const isTop10Holder = top10HolderPercent.gte( + top10HolderPercentThreshold + ); + const isVolume24h = volume24hUsd.gte(volume24hUsdThreshold); + const isPriceChange24h = priceChange24hPercent.gte( + priceChange24hPercentThreshold + ); + const isPriceChange12h = priceChange12hPercent.gte( + priceChange12hPercentThreshold + ); + const isUniqueWallet24h = + uniqueWallet24h >= uniqueWallet24hThreshold; + const isLiquidityTooLow = liquidityUsd.lt(1000); + const isMarketCapTooLow = marketCapUsd.lt(100000); + return ( + isTop10Holder || + isVolume24h || + isPriceChange24h || + isPriceChange12h || + isUniqueWallet24h || + isLiquidityTooLow || + isMarketCapTooLow + ); + } catch (error) { + console.error("Error processing token data:", error); + throw error; + } + } + formatTokenData(data: ProcessedTokenData): string { let output = `**Token Security and Trade Report**\n`; output += `Token Address: ${this.tokenAddress}\n\n`; @@ -820,7 +1066,11 @@ const tokenProvider: Provider = { _state?: State ): Promise => { try { - const provider = new TokenProvider(tokenAddress); + const walletProvider = new WalletProvider( + connection, + new PublicKey(PROVIDER_CONFIG.MAIN_WALLET) + ); + const provider = new TokenProvider(tokenAddress, walletProvider); return provider.getFormattedTokenReport(); } catch (error) { console.error("Error fetching token data:", error); diff --git a/packages/plugin-solana/src/providers/trustScoreProvider.ts b/packages/plugin-solana/src/providers/trustScoreProvider.ts index 7243200a3b..7589da66d3 100644 --- a/packages/plugin-solana/src/providers/trustScoreProvider.ts +++ b/packages/plugin-solana/src/providers/trustScoreProvider.ts @@ -17,7 +17,12 @@ import { TradePerformance, } from "../adapters/trustScoreDatabase.ts"; import settings from "@ai16z/eliza/src/settings.ts"; -import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza/src/types.ts"; +import { + IAgentRuntime, + Memory, + Provider, + State, +} from "@ai16z/eliza/src/types.ts"; const Wallet = settings.MAIN_WALLET_ADDRESS; interface TradeData { @@ -309,12 +314,17 @@ export class TrustScoreManager { recommenderId: string, data: TradeData ): Promise { + const recommender = + await this.trustScoreDb.getOrCreateRecommenderWithDiscordId( + recommenderId + ); 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(runtime); const solPrice = prices.solana.usd; const buySol = data.buy_amount / parseFloat(solPrice); @@ -322,7 +332,7 @@ export class TrustScoreManager { const creationData = { token_address: tokenAddress, - recommender_id: recommenderId, + recommender_id: recommender.id, buy_price: processedData.tradeData.price, sell_price: 0, buy_timeStamp: new Date().toISOString(), @@ -368,6 +378,10 @@ export class TrustScoreManager { sellDetails: sellDetails, isSimulation: boolean ) { + const recommender = + await this.trustScoreDb.getOrCreateRecommenderWithDiscordId( + recommenderId + ); const processedData: ProcessedTokenData = await this.tokenProvider.getProcessedTokenData(); const wallet = new WalletProvider( @@ -381,7 +395,7 @@ export class TrustScoreManager { sellDetails.sell_amount * processedData.tradeData.price; const trade = await this.trustScoreDb.getLatestTradePerformance( tokenAddress, - recommenderId, + recommender.id, isSimulation ); const buyTimeStamp = trade.buy_timeStamp; @@ -415,7 +429,7 @@ export class TrustScoreManager { }; this.trustScoreDb.updateTradePerformanceOnSell( tokenAddress, - recommenderId, + recommender.id, buyTimeStamp, sellDetailsData, isSimulation @@ -425,9 +439,15 @@ export class TrustScoreManager { } export const trustScoreProvider: Provider = { - async get(runtime: IAgentRuntime, message: Memory, state?: State): Promise { + async get( + runtime: IAgentRuntime, + message: Memory, + state?: State + ): Promise { try { - const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db); + const trustScoreDb = new TrustScoreDatabase( + runtime.databaseAdapter.db + ); // Get the user ID from the message const userId = message.userId; @@ -438,7 +458,8 @@ export const trustScoreProvider: Provider = { } // Get the recommender metrics for the user - const recommenderMetrics = await trustScoreDb.getRecommenderMetrics(userId); + const recommenderMetrics = + await trustScoreDb.getRecommenderMetrics(userId); if (!recommenderMetrics) { console.error("No recommender metrics found for user:", userId); @@ -459,4 +480,4 @@ export const trustScoreProvider: Provider = { return `Failed to fetch trust score: ${error instanceof Error ? error.message : "Unknown error"}`; } }, -}; \ No newline at end of file +}; diff --git a/packages/plugin-solana/src/providers/wallet.ts b/packages/plugin-solana/src/providers/wallet.ts index 5f38e3b836..8211795af8 100644 --- a/packages/plugin-solana/src/providers/wallet.ts +++ b/packages/plugin-solana/src/providers/wallet.ts @@ -20,7 +20,7 @@ const PROVIDER_CONFIG = { }, }; -interface Item { +export interface Item { name: string; address: string; symbol: string; diff --git a/packages/plugin-solana/src/types/token.ts b/packages/plugin-solana/src/types/token.ts index 6e12bde638..4f8d3e355e 100644 --- a/packages/plugin-solana/src/types/token.ts +++ b/packages/plugin-solana/src/types/token.ts @@ -272,3 +272,16 @@ export interface DexScreenerData { schemaVersion: string; pairs: DexScreenerPair[]; } + +export interface Prices { + solana: { usd: string }; + bitcoin: { usd: string }; + ethereum: { usd: string }; +} + +export interface CalculatedBuyAmounts { + none: 0; + low: number; + medium: number; + high: number; +} From 18af606d5afecee016ee7c9182300b87b351aa9b Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Sun, 10 Nov 2024 02:58:02 +0200 Subject: [PATCH 6/7] update Signed-off-by: MarcoMandar --- packages/plugin-solana/src/evaluators/trust.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index fcf68a3ef5..9fcb29fab7 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -150,7 +150,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { // create the wallet provider and token provider const walletProvider = new WalletProvider( new Connection("https://api.mainnet-beta.solana.com"), - new PublicKey("Main Wallet") // TODO: get the wallet public key from the runtime + new PublicKey(runtime.getSetting("WALLET_PUBLIC_KEY")) ); const tokenProvider = new TokenProvider( rec.contractAddress, From fc072eb17f3fb3da24bc3a3c47b8fdb551c66c97 Mon Sep 17 00:00:00 2001 From: MarcoMandar Date: Sun, 10 Nov 2024 02:58:40 +0200 Subject: [PATCH 7/7] continue if no contract is found Signed-off-by: MarcoMandar --- packages/plugin-solana/src/evaluators/trust.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index 9fcb29fab7..4bfb4a28c0 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -175,7 +175,6 @@ async function handler(runtime: IAgentRuntime, message: Memory) { rec.contractAddress = tokenAddress; if (!tokenAddress) { console.warn("Could not find contract address for token"); - // ask the user to provide the contract address continue; } }