Skip to content
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
6 changes: 5 additions & 1 deletion characters/podai.character.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
"knowledge": [
"knows many top/famous token/coin informcation",
"knows the price of the many tokens",
"knows the trend of the web3 tokens"
"knows the trend of the web3 tokens",
"The Arena is a next gen SocialFi app redefining how creators connect, engage, and monetize their content.",
"The Arena is an Avalanche-based social protocol, formerly known as Stars Arena, which was acquired by a new team in November 2023 and rebuilt after being on the verge of collapse.",
"Arena has more than 200,000 registered users, has paid more than $6 million to content creators, and has exceeded $100 million in transaction volume by Q4 2024.",
"Arena Social operates on the Avalanche blockchain, leveraging its high-performance consensus mechanism and low latency to ensure fast and secure operations."
],
"messageExamples": [
],
Expand Down
10 changes: 8 additions & 2 deletions packages/client-direct/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
],
"dependencies": {
"@elizaos/core": "workspace:*",
"@elizaos/plugin-avalanche": "workspace:*",
"@elizaos/plugin-binance": "workspace:*",
"@elizaos/plugin-data-enrich": "workspace:*",
"@elizaos/plugin-image-generation": "workspace:*",
"@elizaos/plugin-tee-verifiable-log": "workspace:*",
"@elizaos/plugin-tee-log": "workspace:*",
"@elizaos/plugin-data-enrich": "workspace:*",
"@elizaos/client-twitter": "workspace:*",
"agent-twitter-client": "0.0.18",
"@privy-io/server-auth":"1.18.1",
Expand All @@ -32,9 +34,13 @@
"@types/express": "5.0.0",
"@solana/spl-token": "^0.4.9",
"@solana/web3.js": "^1.95.8",
"@elizaos/plugin-binance": "workspace:*",
"@binance/connector": "^3.6.0",
"@elysiajs/swagger": "^1.2.0",
"@mysten/sui": "^1.21.1",
"solana-agent-kit": "^1.2.0",
"elysia": "^1.2.12",
"ethers": "^6.13.5",
"starknet": "6.18.0",
"body-parser": "1.20.3",
"cors": "2.8.5",
"discord.js": "14.16.3",
Expand Down
56 changes: 52 additions & 4 deletions packages/client-direct/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import { MemoController } from "./memo";
import { requireAuth } from "./auth";
import { CoinAnalysisObj, KEY_BNB_CACHE_STR } from "../../client-twitter/src/sighter";
import { ArenaAnalysisObj, KEY_ARENA_CACHE_STR } from "../../client-twitter/src/arena";
//import { ethers } from 'ethers';
//import { requireAuth } from "./auth";

Expand Down Expand Up @@ -199,7 +200,7 @@
this.handleTwitterOauthInit.bind(this)
);
app.get(
"/:agentId/twitter_oauth_callback",

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
this.handleTwitterOauthCallback.bind(this)
);
app.get(
Expand All @@ -210,6 +211,10 @@
"/:agentId/bnb_query",
this.handleBnbQuery.bind(this)
);
app.get(
"/:agentId/arena_query",
this.handleArenaQuery.bind(this)
);
app.post(
"/:agentId/twitter_profile_search",
this.handleTwitterProfileSearch.bind(this)
Expand Down Expand Up @@ -240,7 +245,7 @@
requireAuth,
memoController.handleAddMemo.bind(memoController)
);
app.delete(

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
"/:agentId/memo",
requireAuth,
memoController.handleDeleteMomo.bind(memoController)
Expand Down Expand Up @@ -455,80 +460,80 @@
accessToken,
refreshToken,
expiresIn,
};
await this.handleGrowthExperience(20, userProfile, "twitter auth");
await userManager.saveUserData(userProfile);

//return { accessToken };
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FungIPle</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTczIiBoZWlnaHQ9IjE2MyIgdmlld0JveD0iMCAwIDE3MyAxNjMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0wIDEwNi44OUgxOS4yMDM1TDE5LjIzMzYgNzcuNDYwOUgwLjAzMDA3NkwwIDEwNi44OVoiIGZpbGw9IiNGRjk5MDAiLz4KPHBhdGggZD0iTTE3Mi45NTIgMjkuNDI5NUwxNzIuOTY3IDE5LjcxNDlDMTcyLjk2NyA5LjUxOTA5IDE2My4yNjggMCAxNTIuODYyIDBIODMuNzkyMkg4MS43OTIxSDc0LjQzODZINzMuOTQyM1YwLjAxNTAzODFDNjAuMDQ3MiAwLjI0MDYwOSA1MC4wOTIxIDEwLjEzNTcgNTAuMDc3IDIzLjg1MDRMNTAuMDE2OSA3Ny40NjExSDI5LjU2NTJMMjkuNTM1MiAxMDYuODkxSDQ5Ljk4NjhMNDkuOTI2NyAxNjIuNjIySDgzLjY4NjlMODMuNzQ3MSAxMDYuODkxSDgzLjc3NzJIMTQ4LjUzMUMxNjIuNjgxIDEwNi44OTEgMTcyLjg3NyA5Ni45MDUzIDE3Mi44OTIgODMuMDQwMkwxNzIuOTM3IDQxLjQ2NzJIMTM5LjE3N0wxMzkuMTMyIDc3LjQ2MTFIODMuNzkyMlYyOS40Mjk1SDEzOS4xOTJIMTcyLjk1MloiIGZpbGw9IiNGRjk5MDAiLz4KPC9zdmc+Cg==">
<style>
body {
margin: 0;
}
.container {
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
}
.ad_img {
max-width: 1000px;
width: 100%;
height: auto;
}
@media only screen and (max-width: 670px) {
.ad_img {
max-width: 660px;
width: 100%;
height: auto;
}
}
</style>
</head>
<body>
<div style="text-align: center; font-size: 20px; font-weight: bold;">
<h1>FungIPle Agent</h1>
<br>Login Success!<br>
<script type="text/javascript">
console.log('window.opener');
console.log(window.opener);
function closeWindow() {
console.log('closeWindow');
try {
window.opener.postMessage({
type: 'TWITTER_AUTH_SUCCESS',
code: '${code}',
username: '${username}',
accessToken: '${accessToken}',
refreshToken: '${refreshToken}',
expiresIn: '${expiresIn}',
state: '${state}'
},
'*'
);
window.close();
} catch(e) {
console.log(e);
}
}
</script>
<button style="text-align: center; width: 40%; height: 40px; font-size: 20px; background-color: #9F91ED; color: #ffffff; margin: 20px; border: none; border-radius: 10px;"
onclick="closeWindow()">
Click to Close</button>
<br>
</div>
<div class="container">
<img style="max-width: 40%; width: 40%; height: auto;" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTczIiBoZWlnaHQ9IjE2MyIgdmlld0JveD0iMCAwIDE3MyAxNjMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0wIDEwNi44OUgxOS4yMDM1TDE5LjIzMzYgNzcuNDYwOUgwLjAzMDA3NkwwIDEwNi44OVoiIGZpbGw9IiNGRjk5MDAiLz4KPHBhdGggZD0iTTE3Mi45NTIgMjkuNDI5NUwxNzIuOTY3IDE5LjcxNDlDMTcyLjk2NyA5LjUxOTA5IDE2My4yNjggMCAxNTIuODYyIDBIODMuNzkyMkg4MS43OTIxSDc0LjQzODZINzMuOTQyM1YwLjAxNTAzODFDNjAuMDQ3MiAwLjI0MDYwOSA1MC4wOTIxIDEwLjEzNTcgNTAuMDc3IDIzLjg1MDRMNTAuMDE2OSA3Ny40NjExSDI5LjU2NTJMMjkuNTM1MiAxMDYuODkxSDQ5Ljk4NjhMNDkuOTI2NyAxNjIuNjIySDgzLjY4NjlMODMuNzQ3MSAxMDYuODkxSDgzLjc3NzJIMTQ4LjUzMUMxNjIuNjgxIDEwNi44OTEgMTcyLjg3NyA5Ni45MDUzIDE3Mi44OTIgODMuMDQwMkwxNzIuOTM3IDQxLjQ2NzJIMTM5LjE3N0wxMzkuMTMyIDc3LjQ2MTFIODMuNzkyMlYyOS40Mjk1SDEzOS4xOTJIMTcyLjk1MloiIGZpbGw9IiNGRjk5MDAiLz4KPC9zdmc+Cg==">
</div>

<div>

Check failure

Code scanning / CodeQL

Reflected cross-site scripting High

Cross-site scripting vulnerability due to a
user-provided value
.
Cross-site scripting vulnerability due to a
user-provided value
.
<br>
</div>

Expand Down Expand Up @@ -630,6 +635,52 @@
});
}
}

async handleArenaQuery(req: express.Request, res: express.Response) {
const kol = typeof req.query.username === 'string' ? req.query.username : '';
const kolname = kol.trim();
if (!kolname) {
throw new ApiError(533, "kolname is blank");
}
console.log("handleArenaQuery, kolname: " + kolname);
const runtime = await this.authUtils.getRuntime(req.params.agentId);
let userId = "blank";
twEventCenter.emit("MSG_ARENA_QUERY", kolname, userId);
let anaObj: ArenaAnalysisObj = null;

for (let i = 0; i < 10; i++) {
await this.sleep(1000);
const cached = await runtime.cacheManager.get(KEY_ARENA_CACHE_STR + kolname);
// console.log("handleArenaQuery, cached: " + cached);
if (cached && typeof cached === 'string') {
try {
anaObj = JSON.parse(cached);
if (anaObj) {
if (Date.now() - anaObj.timestamp > 3000) {
continue;
} else {
break;
}
}
} catch (error) {
console.error('JSON parse failed: ', error);
}
}
}

if (anaObj && anaObj.coin_analysis && anaObj.coin_prediction) {
res.json({
coin_analysis: anaObj.coin_analysis,
coin_prediction: anaObj.coin_prediction,
});
}
else {
res.json({
res: false,
reason: "try again",
});
}
}

async handleTwitterProfileSearch(
req: express.Request,
Expand Down Expand Up @@ -670,9 +721,6 @@
username: item?.username,
name: item?.name,
avatar: item?.avatar,
//avatar: "https://pbs.twimg.com/profile_images/898967039301349377/bLmMDwtf.jpg",
//avatar: "https://abs.twimg.com/sticky/default_profile_images/default_profile.png",
//avatar: "https://pbs.twimg.com/profile_images/1809130917350494209/Q_WjcqLz.jpg";
};

if (item?.username) {
Expand Down Expand Up @@ -1198,7 +1246,7 @@
return res.json({
success: true,
signature,
data: "ETH reward processed",
data: "ETH eco reward processed",
});
case "sui":
// Handle SUI transfer
Expand Down
1 change: 1 addition & 0 deletions packages/client-twitter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@elizaos/core": "workspace:*",
"@elizaos/plugin-data-enrich": "workspace:*",
"@elizaos/plugin-binance": "workspace:*",
"@elizaos/plugin-avalanche": "workspace:*",
"@binance/connector": "^3.6.0",
"agent-twitter-client": "0.0.18",
"discord.js": "14.16.3",
Expand Down
137 changes: 137 additions & 0 deletions packages/client-twitter/src/arena.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { UserManager, ConsensusProvider } from "@elizaos/plugin-data-enrich";
import {
avalanchePlugin
} from "@elizaos/plugin-avalanche";
import {
elizaLogger,
generateText,
IAgentRuntime,
ModelClass,
} from "@elizaos/core";
import { ClientBase } from "./base";
import { SearchMode } from "agent-twitter-client";
import { UserResponce } from "../../plugin-avalanche/src/types/index";

export const KEY_ARENA_CACHE_STR = "key_arena_res_cache_";

export class ArenaAnalysisObj {
public coin_analysis: string;
public coin_prediction: string;
public timestamp: number;
public token: string;
constructor(token: string, analysis: string, prediction: string) {
this.token = token;
this.coin_analysis = analysis;
this.coin_prediction = prediction;
this.timestamp = Date.now();
}
}

export class ArenaClient {
client: ClientBase;
runtime: IAgentRuntime;
userManager: UserManager;

constructor(client: ClientBase, runtime: IAgentRuntime) {
this.client = client;
this.runtime = runtime;
this.userManager = new UserManager(this.runtime.cacheManager);
this.sendingTwitterDebug = false;
}

intervalId: NodeJS.Timeout;
sendingTwitterDebug: boolean;

async start() {
console.log("Arena Query start");
if (!this.client.profile) {
await this.client.init();
}
}
async sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async extractBraceContent(input: string): Promise<string> {
const regex = /\{.*\}/;
const match = input.match(regex);
return match ? match[0] : '';
}

async arenaQuery(username: string, userId: any) {
// 1. get param. 2 get prompt. 3. get tweet info. 4. get arena info. 5. get ai answer.

console.log("arenaQuery, in arana. username: " + username);
const promptHeader = "You are a cryptocurrency expert with extensive experience in cryptocurrency trading and frequently active in various cryptocurrency communities. You are now providing an analysis based on the following two social media platforms. The first one is some dynamics of X account, and the second one is the first web3 social media associated with X. I will first provide the updates of X account";
const promptFooter = " please use 100 - word English texts respectively to analyze the reasons for the current price trend. The response format should be formatted as a JSON block as follows: {\"coin_analysis\": \"{coin_analysis}\"}. No other text should be provided, No need to use markdown syntax, just return JSON directly.";

const tweetsres = await this.client.fetchSearchTweets(
username,
20, SearchMode.Latest
);
const promptTweet =
`
Here are some tweets/replied:
${[...tweetsres?.tweets]
.filter((tweet) => {
// ignore tweets where any of the thread tweets contain a tweet by the bot
const thread = tweet.thread;
const botTweet = thread.find(
(t) =>
t.username ===
this.runtime.getSetting("TWITTER_USERNAME")
);
return !botTweet;
})
.map(
(tweet) => `
From: ${tweet.name} (@${tweet.username})
Text: ${tweet.text}\n
Likes: ${tweet.likes}, Replies: ${tweet.replies}, Retweets: ${tweet.retweets},
`
)
.join("\n")}
`;


let promptArena = `There is another web3 social networking site based on X website below, with the same account. It can send content and also capture X's dynamics on the same account. The user associated with this account has corresponding cryptocurrency prices and user information, as shown below`;
const { actions } = avalanchePlugin;
let aranaQueryAction = null;
actions.forEach(action => {
// console.log( "arenaQuery, action.name: " + action.name );
if(action.name === 'PROFILE_CHECK') {
aranaQueryAction = action;
}
});

const arenaOptions: Record<string, unknown> = {
kolname: username,
};
const data = await aranaQueryAction.handler(this.runtime, null, null, arenaOptions, null);

if(data) {
promptArena += JSON.stringify(data)
}
console.log("arenaQuery, in arenaQuery. promt:\npromptHeader"
+ promptHeader + "\n promptTweet "
+ promptTweet + "\n promptArena: " + promptArena + "\n promptFooter " + promptFooter);

let responseStr = await generateText({
runtime: this.runtime,
context: promptHeader + promptTweet + promptArena + promptFooter,
modelClass: ModelClass.LARGE,
});
console.log("arenaQuery, in arenaQuery. responseStr: ", responseStr);

let responseObj = null;
try {
responseObj = JSON.parse(responseStr);
} catch (error) {
responseObj = null;
console.error('JSON parse error: ', error.message);
}
if (responseObj) {
const anaobj = new ArenaAnalysisObj(username, responseObj?.coin_analysis, "empty");
await this.runtime.cacheManager.set(KEY_ARENA_CACHE_STR + username, JSON.stringify(anaobj));
}
}
}
9 changes: 8 additions & 1 deletion packages/client-twitter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TwitterSearchClient } from "./search.ts";
import { TwitterSpaceClient } from "./spaces.ts";
import { TwitterWatchClient } from "./watcher.ts";
import { SighterClient, KEY_BNB_CACHE_STR } from "./sighter.ts";
import { ArenaClient } from "./arena.ts";
import { TwitterFinderClient } from "./finder.ts";
import { EventEmitter } from 'events';

Expand All @@ -26,6 +27,7 @@ class TwitterManager {
space?: TwitterSpaceClient;
watcher: TwitterWatchClient;
sighter: SighterClient;
arena: ArenaClient;
finder: TwitterFinderClient;

constructor(runtime: IAgentRuntime, twitterConfig: TwitterConfig) {
Expand All @@ -49,6 +51,7 @@ class TwitterManager {
this.interaction = new TwitterInteractionClient(this.client, runtime);

this.sighter = new SighterClient(this.client, runtime);
this.arena = new ArenaClient(this.client, runtime);

// Optional Spaces logic (enabled if TWITTER_SPACES_ENABLE is true)
if (twitterConfig.TWITTER_SPACES_ENABLE) {
Expand Down Expand Up @@ -95,6 +98,7 @@ export const TwitterClientInterface: Client = {

await manager.finder.start();
await manager.watcher.start();
await manager.arena.start();
twEventCenter.on('MSG_RE_TWITTER', (text, userId) => {
console.log('MSG_RE_TWITTER userId: ' + userId + " text: " + text);
manager.watcher.sendReTweet(text, userId);
Expand All @@ -103,7 +107,10 @@ export const TwitterClientInterface: Client = {
// console.log('MSG_RE_TWITTER userId: ' + userId + " text: " + text);
manager.sighter.bnbQuery(coinsymbol, userId);
});

twEventCenter.on("MSG_ARENA_QUERY", (username, userId) => {
// console.log('MSG_RE_TWITTER userId: ' + userId + " text: " + text);
manager.arena.arenaQuery(username, userId);
});
return manager;
},

Expand Down
73 changes: 73 additions & 0 deletions packages/plugin-avalanche/src/actions/arena.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
type Action,
type ActionExample,
type IAgentRuntime,
type Memory,
type State,
type HandlerCallback,
elizaLogger,
type Content,
} from "@elizaos/core";
import { UserResponse } from "../types";

export interface ProfileContent extends Content {
depositTokenAddress: string;
strategyAddress: string;
amount: string | number;
}

export default {
name: "PROFILE_CHECK",
similes: ["DEPOSIT_FOR_AVALANCHE", "DEPOSIT_PROFILE"],
validate: async (runtime: IAgentRuntime, _message: Memory) => {
return true;
},
description:
"get arena socila info.",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<UserResponse> => {

elizaLogger.log("Starting PROFILE_CHECK handler...");
const username = _options['kolname'] as string;
try {
const baseUrl = 'https://api.starsarena.com/user/handle';
const response = await fetch(`${baseUrl}?handle=${username}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return null;
}
const data = await response.json();
const userinfo = data?.user;
if (userinfo) {
return userinfo as UserResponse;
}
} catch (error) {
console.error('Error fetching user info:', error);
}
return null;
},
examples: [
[
{
user: "{{user1}}",
content: { text: "Deposit 1 USDC into the strategy" },
},
],
[
{
user: "{{user1}}",
content: { text: "Deposit 10 gmYAK to earn yield" },
},
],
] as ActionExample[][],
} as Action;
Loading
Loading