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: birdeye provider to support all possible evm symbols #1366

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fb9c477
feat: add birdeye provider as base api for further integration
simpletrontdip Dec 20, 2024
1b03327
feat: init wallet porfolio if needed
simpletrontdip Dec 20, 2024
d756b09
feat: update README, provide some basic useage and integration guide
simpletrontdip Dec 20, 2024
6b55ba4
feat: add tests and format code
simpletrontdip Dec 20, 2024
bbea87e
feat: utilize cache in fetch functions and tests
simpletrontdip Dec 20, 2024
f28d149
fix: remove unused deps
simpletrontdip Dec 20, 2024
d568051
chore: update lock file
simpletrontdip Dec 20, 2024
ea3c313
feat: add new package to agent
simpletrontdip Dec 20, 2024
a4f5d8b
feat: init birdeye provider from character config
simpletrontdip Dec 22, 2024
999ecc0
chore: update lock file
simpletrontdip Dec 22, 2024
003d410
feat: update root package prefix
simpletrontdip Dec 22, 2024
dd37091
fix: update core package after rebasing
simpletrontdip Dec 22, 2024
b39aeb3
feat: add reviewToken action
simpletrontdip Dec 23, 2024
5f9d7ed
feat: enhance token report
simpletrontdip Dec 23, 2024
0adbb9c
feat: refine prompt template
simpletrontdip Dec 23, 2024
e2726a5
Merge branch 'develop' into plugin-birdeye
simpletrontdip Dec 23, 2024
efdb226
Merge branch 'develop' into plugin-birdeye
simpletrontdip Dec 25, 2024
daed0e0
feat: refine report token detail format, prompt
simpletrontdip Dec 25, 2024
2502944
Merge branch 'develop' into plugin-birdeye
simpletrontdip Dec 25, 2024
bf99d6a
feat: refine provider, enhance static content injected
simpletrontdip Dec 25, 2024
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 @@ -52,6 +52,7 @@
"@elizaos/plugin-multiversx": "workspace:*",
"@elizaos/plugin-near": "workspace:*",
"@elizaos/plugin-zksync-era": "workspace:*",
"@elizaos/plugin-birdeye": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
2 changes: 2 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { suiPlugin } from "@elizaos/plugin-sui";
import { TEEMode, teePlugin } from "@elizaos/plugin-tee";
import { tonPlugin } from "@elizaos/plugin-ton";
import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import { birdeyePlugin } from "@elizaos/plugin-birdeye";
import { abstractPlugin } from "@elizaos/plugin-abstract";
import Database from "better-sqlite3";
import fs from "fs";
Expand Down Expand Up @@ -547,6 +548,7 @@ export async function createAgent(
getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null,
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
getSecret(character, "STORY_PRIVATE_KEY") ? storyPlugin : null,
getSecret(character, "BIRDEYE_API_KEY") ? birdeyePlugin : null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-birdeye/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*

!dist/**
!package.json
!readme.md
!tsup.config.ts
25 changes: 25 additions & 0 deletions packages/plugin-birdeye/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Birdeye plugin for Eliza

Basic API wrapper for <https://birdeye.so> inside our AI agent, almost readonly API only.
For more information please refer to their [docs](https://docs.birdeye.so/reference/get_defi-tokenlist)

## What you need?

```
BIRDEYE_API_KEY=zzz-some-secret
BIRDEYE_WALLET_ADDR=your porfolio profile address
```

## Integrate with other plugin

```js
// init your provider with custom support map
const provider = new BirdeyeProvider(runtime.cacheManager, {
'WETH': '0xs0000000001231231',
...
})

const price = await provider.fetchTokenPriceBySymbol('WETH')

// further action based on this
```
3 changes: 3 additions & 0 deletions packages/plugin-birdeye/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-birdeye/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@elizaos/plugin-birdeye",
"version": "0.1.7-alpha.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"node-cache": "5.1.2",
"tsup": "8.3.5",
"vitest": "2.1.4"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache .",
"test": "vitest run"
}
}
184 changes: 184 additions & 0 deletions packages/plugin-birdeye/src/actions/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import {
Action,
ActionExample,
composeContext,
elizaLogger,
generateText,
ModelClass,
type IAgentRuntime,
type Memory,
type State
} from "@elizaos/core";
import { BirdeyeProvider } from "../providers/birdeye";

const extractTokenSymbolTemplate = `Given the recent message below:

{{recentMessages}}

Extract the 1 latest information about the requested token report:
- Input token symbol
- Extra about this symbol

When the symbol is specified in all lowered case, such as btc, eth, sol..., we should convert it into wellknown symbol.
E.g. btc instead of BTC, sol instead of SOL.

But when we see them in mixed form, such as SOl, DOl, eTH, except the case they're quoted (e.g. 'wEth', 'SOl',...)
When in doubt, specify the concern in the message field, include your suggested value with it.

Respond exactly a JSON object containing only the extracted values, no extra description or message needed.
Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema:
{
"symbol": string | null,
"message": string | null,
}

Examples:
Message: 'Tell me about BTC'
Response: '{ "symbol": "BTC", "message": null}'

Message: 'Do you know about SOl.'
Response: '{ "symbol": "SOl", "message": "We've found SOL seems match, is that what you want?"}'
`;


const formatTokenReport = (data) => {
let output = `*Token Security and Trade Report*\n`;
output += `Token symbol: ${data.symbol}\n`
output += `Token Address: ${data.tokenAddress}\n\n`;

output += `*Ownership Distribution:*\n`;
output += `- Owner Balance: ${data.security.ownerBalance}\n`;
output += `- Creator Balance: ${data.security.creatorBalance}\n`;
output += `- Owner Percentage: ${data.security.ownerPercentage}%\n`;
output += `- Creator Percentage: ${data.security.creatorPercentage}%\n`;
output += `- Top 10 Holders Balance: ${data.security.top10HolderBalance}\n`;
output += `- Top 10 Holders Percentage: ${data.security.top10HolderPercent}%\n\n`;

// Trade Data
output += `*Trade Data:*\n`;
output += `- Holders: ${data.volume.holder}\n`;
output += `- Unique Wallets (24h): ${data.volume.unique_wallet_24h}\n`;
output += `- Price Change (24h): ${data.volume.price_change_24h_percent}%\n`;
output += `- Price Change (12h): ${data.volume.price_change_12h_percent}%\n`;
output += `- Volume (24h USD): $${data.volume.volume_24h_usd}\n`;
output += `- Current Price: $${data.volume.price}\n\n`;

return output
}

const extractTokenSymbol = async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: any) => {
const context = composeContext({
state,
template: extractTokenSymbolTemplate
})

const response = await generateText({
runtime,
context,
modelClass: ModelClass.LARGE,
})

elizaLogger.log('Response', response)

try {
const regex = new RegExp(/\{(.+)\}/gms);
const normalized = response && regex.exec(response)?.[0]
elizaLogger.debug('Normalized data', normalized)
return normalized && JSON.parse(normalized)
} catch {
callback?.({text: response})
return true
}
}

export const reportToken = {
name: "REPORT_TOKEN",
similes: ["CHECK_TOKEN", "REVIEW_TOKEN", "TOKEN_DETAILS"],
description: "Check background data for a given token",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: any
) => {
try {
const params = await extractTokenSymbol(
runtime,
message,
state,
options,
callback
)

elizaLogger.debug('Params', params)

if(!params?.symbol) {
callback?.({text: "I need a token symbol to begin"})
return true
}

if(params?.message) {
// show concern message
callback?.({text: `*Warning*: ${params.message}`})
}

const symbol = params?.symbol
elizaLogger.log('Fetching birdeye data', symbol)
const provider = new BirdeyeProvider(runtime.cacheManager)

const [tokenAddress, security, volume] = await Promise.all([
provider.getTokenAddress(symbol),
provider.fetchTokenSecurityBySymbol(symbol),
provider.fetchTokenTradeDataBySymbol(symbol),
]);

elizaLogger.log('Fetching birdeye done')
const msg = formatTokenReport({
symbol,
tokenAddress,
security: security.data,
volume: volume.data
})
callback?.({text: msg})
return true
} catch (error) {
console.error("Error in reportToken handler:", error.message);
callback?.({ text: `Error: ${error.message}` });
return false;
}
},
validate: async (runtime: IAgentRuntime, message: Memory) => {
return true;
},
examples: [
[
{
user: "user",
content: {
text: "Tell me what you know about SOL",
action: "CHECK_TOKEN",
},
},
{
user: "user",
content: {
text: "Do you know about SOL",
action: "TOKEN_DETAILS",
},
},
{
user: "user",
content: {
text: "Tell me about WETH",
action: "REVIEW_TOKEN",
},
},
],
] as ActionExample[][],
} as Action;
35 changes: 35 additions & 0 deletions packages/plugin-birdeye/src/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { IAgentRuntime } from "@elizaos/core";
import { z } from "zod";

export const birdeyeEnvSchema = z.object({
BIRDEYE_API_KEY: z.string().min(1, "Birdeye API key is required"),
});

export type BirdeyeConfig = z.infer<typeof birdeyeEnvSchema>;

export async function validateBirdeyeConfig(
runtime: IAgentRuntime
): Promise<BirdeyeConfig> {
try {
const config = {
BIRDEYE_API_KEY:
runtime.getSetting("BIRDEYE_API_KEY") ||
process.env.BIRDEYE_API_KEY,
BIRDEYE_WALLET_ADDR:
runtime.getSetting("BIRDEYE_WALLET_ADDR") ||
process.env.BIRDEYE_WALLET_ADDR,
};

return birdeyeEnvSchema.parse(config);
} catch (error) {
if (error instanceof z.ZodError) {
const errorMessages = error.errors
.map((err) => `${err.path.join(".")}: ${err.message}`)
.join("\n");
throw new Error(
`Birdeye configuration validation failed:\n${errorMessages}`
);
}
throw error;
}
}
15 changes: 15 additions & 0 deletions packages/plugin-birdeye/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Plugin } from "@elizaos/core";

import { birdeyeProvider, BirdeyeProvider } from "./providers/birdeye";
import { reportToken } from "./actions/report";

export { BirdeyeProvider };

export const birdeyePlugin: Plugin = {
name: "birdeye",
description: "Birdeye Plugin for Eliza",
providers: [birdeyeProvider],
actions: [reportToken]
};

export default birdeyePlugin;
Loading
Loading