Skip to content

Commit d834f0b

Browse files
authored
Merge pull request #11 from BitPodAI/dev
Dev Merge to Main
2 parents 7a2381d + 561553a commit d834f0b

12 files changed

Lines changed: 374 additions & 20 deletions

File tree

characters/podai.character.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
"knowledge": [
1717
"knows many top/famous token/coin informcation",
1818
"knows the price of the many tokens",
19-
"knows the trend of the web3 tokens"
19+
"knows the trend of the web3 tokens",
20+
"The Arena is a next gen SocialFi app redefining how creators connect, engage, and monetize their content.",
21+
"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.",
22+
"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.",
23+
"Arena Social operates on the Avalanche blockchain, leveraging its high-performance consensus mechanism and low latency to ensure fast and secure operations."
2024
],
2125
"messageExamples": [
2226
],

packages/client-direct/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
],
2121
"dependencies": {
2222
"@elizaos/core": "workspace:*",
23+
"@elizaos/plugin-avalanche": "workspace:*",
24+
"@elizaos/plugin-binance": "workspace:*",
25+
"@elizaos/plugin-data-enrich": "workspace:*",
2326
"@elizaos/plugin-image-generation": "workspace:*",
2427
"@elizaos/plugin-tee-verifiable-log": "workspace:*",
2528
"@elizaos/plugin-tee-log": "workspace:*",
26-
"@elizaos/plugin-data-enrich": "workspace:*",
2729
"@elizaos/client-twitter": "workspace:*",
2830
"agent-twitter-client": "0.0.18",
2931
"@privy-io/server-auth":"1.18.1",
@@ -32,9 +34,13 @@
3234
"@types/express": "5.0.0",
3335
"@solana/spl-token": "^0.4.9",
3436
"@solana/web3.js": "^1.95.8",
35-
"@elizaos/plugin-binance": "workspace:*",
3637
"@binance/connector": "^3.6.0",
38+
"@elysiajs/swagger": "^1.2.0",
39+
"@mysten/sui": "^1.21.1",
3740
"solana-agent-kit": "^1.2.0",
41+
"elysia": "^1.2.12",
42+
"ethers": "^6.13.5",
43+
"starknet": "6.18.0",
3844
"body-parser": "1.20.3",
3945
"cors": "2.8.5",
4046
"discord.js": "14.16.3",

packages/client-direct/src/routes.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { transferStarknetToken } from "../../plugin-data-enrich/src/starknet";
3434
import { MemoController } from "./memo";
3535
import { requireAuth } from "./auth";
3636
import { CoinAnalysisObj, KEY_BNB_CACHE_STR } from "../../client-twitter/src/sighter";
37+
import { ArenaAnalysisObj, KEY_ARENA_CACHE_STR } from "../../client-twitter/src/arena";
3738
//import { ethers } from 'ethers';
3839
//import { requireAuth } from "./auth";
3940

@@ -210,6 +211,10 @@ export class Routes {
210211
"/:agentId/bnb_query",
211212
this.handleBnbQuery.bind(this)
212213
);
214+
app.get(
215+
"/:agentId/arena_query",
216+
this.handleArenaQuery.bind(this)
217+
);
213218
app.post(
214219
"/:agentId/twitter_profile_search",
215220
this.handleTwitterProfileSearch.bind(this)
@@ -630,6 +635,52 @@ export class Routes {
630635
});
631636
}
632637
}
638+
639+
async handleArenaQuery(req: express.Request, res: express.Response) {
640+
const kol = typeof req.query.username === 'string' ? req.query.username : '';
641+
const kolname = kol.trim();
642+
if (!kolname) {
643+
throw new ApiError(533, "kolname is blank");
644+
}
645+
console.log("handleArenaQuery, kolname: " + kolname);
646+
const runtime = await this.authUtils.getRuntime(req.params.agentId);
647+
let userId = "blank";
648+
twEventCenter.emit("MSG_ARENA_QUERY", kolname, userId);
649+
let anaObj: ArenaAnalysisObj = null;
650+
651+
for (let i = 0; i < 10; i++) {
652+
await this.sleep(1000);
653+
const cached = await runtime.cacheManager.get(KEY_ARENA_CACHE_STR + kolname);
654+
// console.log("handleArenaQuery, cached: " + cached);
655+
if (cached && typeof cached === 'string') {
656+
try {
657+
anaObj = JSON.parse(cached);
658+
if (anaObj) {
659+
if (Date.now() - anaObj.timestamp > 3000) {
660+
continue;
661+
} else {
662+
break;
663+
}
664+
}
665+
} catch (error) {
666+
console.error('JSON parse failed: ', error);
667+
}
668+
}
669+
}
670+
671+
if (anaObj && anaObj.coin_analysis && anaObj.coin_prediction) {
672+
res.json({
673+
coin_analysis: anaObj.coin_analysis,
674+
coin_prediction: anaObj.coin_prediction,
675+
});
676+
}
677+
else {
678+
res.json({
679+
res: false,
680+
reason: "try again",
681+
});
682+
}
683+
}
633684

634685
async handleTwitterProfileSearch(
635686
req: express.Request,
@@ -670,9 +721,6 @@ export class Routes {
670721
username: item?.username,
671722
name: item?.name,
672723
avatar: item?.avatar,
673-
//avatar: "https://pbs.twimg.com/profile_images/898967039301349377/bLmMDwtf.jpg",
674-
//avatar: "https://abs.twimg.com/sticky/default_profile_images/default_profile.png",
675-
//avatar: "https://pbs.twimg.com/profile_images/1809130917350494209/Q_WjcqLz.jpg";
676724
};
677725
678726
if (item?.username) {
@@ -1198,7 +1246,7 @@ export class Routes {
11981246
return res.json({
11991247
success: true,
12001248
signature,
1201-
data: "ETH reward processed",
1249+
data: "ETH eco reward processed",
12021250
});
12031251
case "sui":
12041252
// Handle SUI transfer

packages/client-twitter/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@elizaos/core": "workspace:*",
2323
"@elizaos/plugin-data-enrich": "workspace:*",
2424
"@elizaos/plugin-binance": "workspace:*",
25+
"@elizaos/plugin-avalanche": "workspace:*",
2526
"@binance/connector": "^3.6.0",
2627
"agent-twitter-client": "0.0.18",
2728
"discord.js": "14.16.3",
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { UserManager, ConsensusProvider } from "@elizaos/plugin-data-enrich";
2+
import {
3+
avalanchePlugin
4+
} from "@elizaos/plugin-avalanche";
5+
import {
6+
elizaLogger,
7+
generateText,
8+
IAgentRuntime,
9+
ModelClass,
10+
} from "@elizaos/core";
11+
import { ClientBase } from "./base";
12+
import { SearchMode } from "agent-twitter-client";
13+
import { UserResponce } from "../../plugin-avalanche/src/types/index";
14+
15+
export const KEY_ARENA_CACHE_STR = "key_arena_res_cache_";
16+
17+
export class ArenaAnalysisObj {
18+
public coin_analysis: string;
19+
public coin_prediction: string;
20+
public timestamp: number;
21+
public token: string;
22+
constructor(token: string, analysis: string, prediction: string) {
23+
this.token = token;
24+
this.coin_analysis = analysis;
25+
this.coin_prediction = prediction;
26+
this.timestamp = Date.now();
27+
}
28+
}
29+
30+
export class ArenaClient {
31+
client: ClientBase;
32+
runtime: IAgentRuntime;
33+
userManager: UserManager;
34+
35+
constructor(client: ClientBase, runtime: IAgentRuntime) {
36+
this.client = client;
37+
this.runtime = runtime;
38+
this.userManager = new UserManager(this.runtime.cacheManager);
39+
this.sendingTwitterDebug = false;
40+
}
41+
42+
intervalId: NodeJS.Timeout;
43+
sendingTwitterDebug: boolean;
44+
45+
async start() {
46+
console.log("Arena Query start");
47+
if (!this.client.profile) {
48+
await this.client.init();
49+
}
50+
}
51+
async sleep(ms) {
52+
return new Promise(resolve => setTimeout(resolve, ms));
53+
}
54+
async extractBraceContent(input: string): Promise<string> {
55+
const regex = /\{.*\}/;
56+
const match = input.match(regex);
57+
return match ? match[0] : '';
58+
}
59+
60+
async arenaQuery(username: string, userId: any) {
61+
// 1. get param. 2 get prompt. 3. get tweet info. 4. get arena info. 5. get ai answer.
62+
63+
console.log("arenaQuery, in arana. username: " + username);
64+
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";
65+
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.";
66+
67+
const tweetsres = await this.client.fetchSearchTweets(
68+
username,
69+
20, SearchMode.Latest
70+
);
71+
const promptTweet =
72+
`
73+
Here are some tweets/replied:
74+
${[...tweetsres?.tweets]
75+
.filter((tweet) => {
76+
// ignore tweets where any of the thread tweets contain a tweet by the bot
77+
const thread = tweet.thread;
78+
const botTweet = thread.find(
79+
(t) =>
80+
t.username ===
81+
this.runtime.getSetting("TWITTER_USERNAME")
82+
);
83+
return !botTweet;
84+
})
85+
.map(
86+
(tweet) => `
87+
From: ${tweet.name} (@${tweet.username})
88+
Text: ${tweet.text}\n
89+
Likes: ${tweet.likes}, Replies: ${tweet.replies}, Retweets: ${tweet.retweets},
90+
`
91+
)
92+
.join("\n")}
93+
`;
94+
95+
96+
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`;
97+
const { actions } = avalanchePlugin;
98+
let aranaQueryAction = null;
99+
actions.forEach(action => {
100+
// console.log( "arenaQuery, action.name: " + action.name );
101+
if(action.name === 'PROFILE_CHECK') {
102+
aranaQueryAction = action;
103+
}
104+
});
105+
106+
const arenaOptions: Record<string, unknown> = {
107+
kolname: username,
108+
};
109+
const data = await aranaQueryAction.handler(this.runtime, null, null, arenaOptions, null);
110+
111+
if(data) {
112+
promptArena += JSON.stringify(data)
113+
}
114+
console.log("arenaQuery, in arenaQuery. promt:\npromptHeader"
115+
+ promptHeader + "\n promptTweet "
116+
+ promptTweet + "\n promptArena: " + promptArena + "\n promptFooter " + promptFooter);
117+
118+
let responseStr = await generateText({
119+
runtime: this.runtime,
120+
context: promptHeader + promptTweet + promptArena + promptFooter,
121+
modelClass: ModelClass.LARGE,
122+
});
123+
console.log("arenaQuery, in arenaQuery. responseStr: ", responseStr);
124+
125+
let responseObj = null;
126+
try {
127+
responseObj = JSON.parse(responseStr);
128+
} catch (error) {
129+
responseObj = null;
130+
console.error('JSON parse error: ', error.message);
131+
}
132+
if (responseObj) {
133+
const anaobj = new ArenaAnalysisObj(username, responseObj?.coin_analysis, "empty");
134+
await this.runtime.cacheManager.set(KEY_ARENA_CACHE_STR + username, JSON.stringify(anaobj));
135+
}
136+
}
137+
}

packages/client-twitter/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { TwitterSearchClient } from "./search.ts";
77
import { TwitterSpaceClient } from "./spaces.ts";
88
import { TwitterWatchClient } from "./watcher.ts";
99
import { SighterClient, KEY_BNB_CACHE_STR } from "./sighter.ts";
10+
import { ArenaClient } from "./arena.ts";
1011
import { TwitterFinderClient } from "./finder.ts";
1112
import { EventEmitter } from 'events';
1213

@@ -26,6 +27,7 @@ class TwitterManager {
2627
space?: TwitterSpaceClient;
2728
watcher: TwitterWatchClient;
2829
sighter: SighterClient;
30+
arena: ArenaClient;
2931
finder: TwitterFinderClient;
3032

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

5153
this.sighter = new SighterClient(this.client, runtime);
54+
this.arena = new ArenaClient(this.client, runtime);
5255

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

9699
await manager.finder.start();
97100
await manager.watcher.start();
101+
await manager.arena.start();
98102
twEventCenter.on('MSG_RE_TWITTER', (text, userId) => {
99103
console.log('MSG_RE_TWITTER userId: ' + userId + " text: " + text);
100104
manager.watcher.sendReTweet(text, userId);
@@ -103,7 +107,10 @@ export const TwitterClientInterface: Client = {
103107
// console.log('MSG_RE_TWITTER userId: ' + userId + " text: " + text);
104108
manager.sighter.bnbQuery(coinsymbol, userId);
105109
});
106-
110+
twEventCenter.on("MSG_ARENA_QUERY", (username, userId) => {
111+
// console.log('MSG_RE_TWITTER userId: ' + userId + " text: " + text);
112+
manager.arena.arenaQuery(username, userId);
113+
});
107114
return manager;
108115
},
109116

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {
2+
type Action,
3+
type ActionExample,
4+
type IAgentRuntime,
5+
type Memory,
6+
type State,
7+
type HandlerCallback,
8+
elizaLogger,
9+
type Content,
10+
} from "@elizaos/core";
11+
import { UserResponse } from "../types";
12+
13+
export interface ProfileContent extends Content {
14+
depositTokenAddress: string;
15+
strategyAddress: string;
16+
amount: string | number;
17+
}
18+
19+
export default {
20+
name: "PROFILE_CHECK",
21+
similes: ["DEPOSIT_FOR_AVALANCHE", "DEPOSIT_PROFILE"],
22+
validate: async (runtime: IAgentRuntime, _message: Memory) => {
23+
return true;
24+
},
25+
description:
26+
"get arena socila info.",
27+
handler: async (
28+
runtime: IAgentRuntime,
29+
message: Memory,
30+
state: State,
31+
_options: { [key: string]: unknown },
32+
callback?: HandlerCallback
33+
): Promise<UserResponse> => {
34+
35+
elizaLogger.log("Starting PROFILE_CHECK handler...");
36+
const username = _options['kolname'] as string;
37+
try {
38+
const baseUrl = 'https://api.starsarena.com/user/handle';
39+
const response = await fetch(`${baseUrl}?handle=${username}`, {
40+
method: 'GET',
41+
headers: {
42+
'Accept': 'application/json',
43+
'Content-Type': 'application/json'
44+
}
45+
});
46+
if (!response.ok) {
47+
return null;
48+
}
49+
const data = await response.json();
50+
const userinfo = data?.user;
51+
if (userinfo) {
52+
return userinfo as UserResponse;
53+
}
54+
} catch (error) {
55+
console.error('Error fetching user info:', error);
56+
}
57+
return null;
58+
},
59+
examples: [
60+
[
61+
{
62+
user: "{{user1}}",
63+
content: { text: "Deposit 1 USDC into the strategy" },
64+
},
65+
],
66+
[
67+
{
68+
user: "{{user1}}",
69+
content: { text: "Deposit 10 gmYAK to earn yield" },
70+
},
71+
],
72+
] as ActionExample[][],
73+
} as Action;

0 commit comments

Comments
 (0)