Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Dex Screener plugin with token price action, evaluators, an… #1865

Merged
merged 16 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@elizaos/plugin-avalanche": "workspace:*",
"@elizaos/plugin-video-generation": "workspace:*",
"@elizaos/plugin-web-search": "workspace:*",
"@elizaos/plugin-dexscreener": "workspace:*",
"@elizaos/plugin-letzai": "workspace:*",
"@elizaos/plugin-thirdweb": "workspace:*",
"@elizaos/plugin-genlayer": "workspace:*",
Expand Down
7 changes: 6 additions & 1 deletion agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ import { coinmarketcapPlugin } from "@elizaos/plugin-coinmarketcap";
import { confluxPlugin } from "@elizaos/plugin-conflux";
import { createCosmosPlugin } from "@elizaos/plugin-cosmos";
import { cronosZkEVMPlugin } from "@elizaos/plugin-cronoszkevm";
import { echoChambersPlugin } from "@elizaos/plugin-echochambers";
import { evmPlugin } from "@elizaos/plugin-evm";
import { flowPlugin } from "@elizaos/plugin-flow";
import { fuelPlugin } from "@elizaos/plugin-fuel";
Expand Down Expand Up @@ -99,6 +98,9 @@ import { thirdwebPlugin } from "@elizaos/plugin-thirdweb";
import { tonPlugin } from "@elizaos/plugin-ton";
import { squidRouterPlugin } from "@elizaos/plugin-squid-router";
import { webSearchPlugin } from "@elizaos/plugin-web-search";
import { echoChambersPlugin } from "@elizaos/plugin-echochambers";
import { dexScreenerPlugin } from "@elizaos/plugin-dexscreener";

import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import Database from "better-sqlite3";
import fs from "fs";
Expand Down Expand Up @@ -761,6 +763,9 @@ export async function createAgent(
// character.plugins are handled when clients are added
plugins: [
bootstrapPlugin,
getSecret(character, "DEXSCREENER_API_KEY")
? dexScreenerPlugin
: null,
getSecret(character, "CONFLUX_CORE_PRIVATE_KEY")
? confluxPlugin
: null,
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-dexscreener/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*

!dist/**
!package.json
!readme.md
!tsup.config.ts
3 changes: 3 additions & 0 deletions packages/plugin-dexscreener/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import eslintGlobalConfig from "../../eslint.config.mjs";

export default [...eslintGlobalConfig];
19 changes: 19 additions & 0 deletions packages/plugin-dexscreener/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@elizaos/plugin-dexscreener",
"version": "0.1.7-alpha.2",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"tsup": "8.3.5"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
1 change: 1 addition & 0 deletions packages/plugin-dexscreener/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./tokenAction.ts";
189 changes: 189 additions & 0 deletions packages/plugin-dexscreener/src/actions/tokenAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { Action, IAgentRuntime, Memory, State, HandlerCallback } from "@elizaos/core";
import { TokenPriceProvider } from "../providers/tokenProvider";

export const priceTemplate = `Determine if this is a token price request. If it is one of the specified situations, perform the corresponding action:

Situation 1: "Get token price"
- Message contains: words like "price", "value", "cost", "worth" AND a token symbol/address
- Example: "What's the price of ETH?" or "How much is BTC worth?"
- Action: Get the current price of the token

Previous conversation for context:
{{conversation}}

You are replying to: {{message}}
`;

export class TokenPriceAction implements Action {
name = "GET_TOKEN_PRICE";
similes = ["FETCH_TOKEN_PRICE", "CHECK_TOKEN_PRICE", "TOKEN_PRICE"];
description = "Fetches and returns token price information";
suppressInitialMessage = true;
template = priceTemplate;

async validate(runtime: IAgentRuntime, message: Memory): Promise<boolean> {
const content = typeof message.content === 'string'
? message.content
: message.content?.text;

if (!content) return false;

const hasPriceKeyword = /\b(price|value|worth|cost)\b/i.test(content);
const hasToken = (
/0x[a-fA-F0-9]{40}/.test(content) ||
/[\$#]?[a-zA-Z0-9]+/i.test(content)
);

return hasPriceKeyword && hasToken;
}

async handler(
runtime: IAgentRuntime,
message: Memory,
state?: State,
_options: { [key: string]: unknown } = {},
callback?: HandlerCallback
): Promise<boolean> {
try {
// Get the provider
const provider = runtime.providers.find(p => p instanceof TokenPriceProvider);
if (!provider) {
throw new Error("Token price provider not found");
}

// Get price data
console.log("Fetching price data...");
const priceData = await provider.get(runtime, message, state);
console.log("Received price data:", priceData);

if (priceData.includes("Error")) {
throw new Error(priceData);
}

// If we have a callback, use it to send the response
if (callback) {
await callback({
text: priceData,
action: this.name
});
}

// Set the response in state to prevent further processing
if (state) {
state.responseData = {
text: priceData,
action: this.name
};
}

return true;

} catch (error) {
console.error("Error in price action handler:", error);

if (callback) {
await callback({
text: `Sorry, I couldn't fetch the token price: ${error.message}`,
action: this.name
});
}

return false;
}
}

examples = [
[
{
user: "{{user}}",
content: {
text: "check price of eth"
}
},
{
user: "{{system}}",
content: {
text: "The current price of Ethereum (ETH) is $2,345.67 with a 24h trading volume of $9.87B. The liquidity pool holds $1.23B, and the price has changed +5.43% in the last 24 hours.",
action: "GET_TOKEN_PRICE"
}
}
],
[
{
user: "{{user}}",
content: {
text: "How much is BTC worth right now?"
}
},
{
user: "{{system}}",
content: {
text: "Bitcoin (BTC) is currently trading at $42,567.89. There's $15.23B in liquidity, with a 24h volume of $25.87B. The price is up 2.15% in the last 24 hours.",
action: "GET_TOKEN_PRICE"
}
}
],
[
{
user: "{{user}}",
content: {
text: "what's the current value of $bnb"
}
},
{
user: "{{system}}",
content: {
text: "Binance Coin (BNB) is priced at $345.67 with $5.23B in liquidity. The trading volume over the last 24h is $1.87B, and the price has decreased by 1.23% during this period.",
action: "GET_TOKEN_PRICE"
}
}
],
[
{
user: "{{user}}",
content: {
text: "can you tell me the price for USDT?"
}
},
{
user: "{{system}}",
content: {
text: "Tether (USDT) is maintaining its peg at $1.00 with minimal change (+0.01%). The token has $25.23B in liquidity and has seen $45.87B in trading volume over the past 24 hours.",
action: "GET_TOKEN_PRICE"
}
}
],
[
{
user: "{{user}}",
content: {
text: "show me the cost of #SOL"
}
},
{
user: "{{system}}",
content: {
text: "Solana (SOL) is trading at $123.45, showing strong momentum with an 8.75% increase in the last 24 hours. The liquidity stands at $3.23B with a 24h trading volume of $987.54M.",
action: "GET_TOKEN_PRICE"
}
}
],
[
{
user: "{{user}}",
content: {
text: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0 price please"
}
},
{
user: "{{system}}",
content: {
text: "The price of Polygon (MATIC) is currently $1.23, up 3.45% in the past 24 hours. The token has $2.23B in liquidity and has seen $567.54M in trading volume today.",
action: "GET_TOKEN_PRICE"
}
}
]
];
}

export const tokenPriceAction = new TokenPriceAction();
1 change: 1 addition & 0 deletions packages/plugin-dexscreener/src/evaluators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./tokenEvaluator.ts";
78 changes: 78 additions & 0 deletions packages/plugin-dexscreener/src/evaluators/tokenEvaluator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Evaluator, IAgentRuntime, Memory, State } from "@elizaos/core";

export class TokenPriceEvaluator implements Evaluator {
name = "TOKEN_PRICE_EVALUATOR";
similes = ["price", "token price", "check price"];
description = "Evaluates messages for token price requests";

async validate(runtime: IAgentRuntime, message: Memory): Promise<boolean> {
const content = typeof message.content === 'string'
? message.content
: message.content?.text;

if (!content) return false;

// Check for price-related keywords
const hasPriceKeyword = /\b(price|value|worth|cost)\b/i.test(content);

// Look for either:
// 1. Ethereum address
// 2. Token symbol starting with $ or #
// 3. Token symbol after "of" or "for" (case insensitive)
const hasToken = (
/0x[a-fA-F0-9]{40}/.test(content) || // Ethereum address
/[\$#][a-zA-Z]+/.test(content) || // $TOKEN or #TOKEN format
/\b(of|for)\s+[a-zA-Z0-9]+\b/i.test(content) // "price of TOKEN" format
);

return hasPriceKeyword && hasToken;
}

async handler(runtime: IAgentRuntime, message: Memory, state?: State): Promise<string> {
return "GET_TOKEN_PRICE";
}

examples = [
{
context: "User asking for token price with address",
messages: [
{
user: "{{user}}",
content: {
text: "What's the price of 0x1234567890123456789012345678901234567890?",
action: "GET_TOKEN_PRICE"
}
}
],
outcome: "GET_TOKEN_PRICE"
},
{
context: "User checking token price with $ symbol",
messages: [
{
user: "{{user}}",
content: {
text: "Check price of $eth",
action: "GET_TOKEN_PRICE"
}
}
],
outcome: "GET_TOKEN_PRICE"
},
{
context: "User checking token price with plain symbol",
messages: [
{
user: "{{user}}",
content: {
text: "What's the value for btc",
action: "GET_TOKEN_PRICE"
}
}
],
outcome: "GET_TOKEN_PRICE"
}
];
}

export const tokenPriceEvaluator = new TokenPriceEvaluator();
18 changes: 18 additions & 0 deletions packages/plugin-dexscreener/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Plugin } from "@elizaos/core";
import { TokenPriceAction } from "./actions/tokenAction.ts";
import { TokenPriceEvaluator } from "./evaluators/tokenEvaluator.ts";
import { TokenPriceProvider } from "./providers/tokenProvider.ts";

export * as actions from "./actions";
export * as evaluators from "./evaluators";
export * as providers from "./providers";

export const dexScreenerPlugin: Plugin = {
name: "dexscreener",
description: "Dex Screener Plugin with Token Price Action, Evaluators and Providers",
actions: [
new TokenPriceAction()
],
evaluators: [ new TokenPriceEvaluator() ],
providers: [ new TokenPriceProvider() ]
};
1 change: 1 addition & 0 deletions packages/plugin-dexscreener/src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./tokenProvider.ts";
Loading
Loading