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 avalanche plugin #842

Merged
merged 20 commits into from
Dec 28, 2024
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ USE_GAIANET_EMBEDDING= # Set to TRUE for GAIANET/768, leave blank for l
EVM_PRIVATE_KEY=
EVM_PROVIDER_URL=

# Avalanche
AVALANCHE_PRIVATE_KEY=
AVALANCHE_PUBLIC_KEY=

# Solana
SOLANA_PRIVATE_KEY=
SOLANA_PUBLIC_KEY=
Expand Down
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@elizaos/plugin-cronoszkevm": "workspace:*",
"@elizaos/plugin-3d-generation": "workspace:*",
"@elizaos/plugin-fuel": "workspace:*",
"@elizaos/plugin-avalanche": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
14 changes: 10 additions & 4 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { tonPlugin } from "@elizaos/plugin-ton";
import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import { cronosZkEVMPlugin } from "@elizaos/plugin-cronoszkevm";
import { abstractPlugin } from "@elizaos/plugin-abstract";
import { avalanchePlugin } from "@elizaos/plugin-avalanche";
import Database from "better-sqlite3";
import fs from "fs";
import path from "path";
Expand Down Expand Up @@ -441,14 +442,14 @@ export async function initializeClients(

function determineClientType(client: Client): string {
// Check if client has a direct type identifier
if ('type' in client) {
if ("type" in client) {
return (client as any).type;
}

// Check constructor name
const constructorName = client.constructor?.name;
if (constructorName && !constructorName.includes('Object')) {
return constructorName.toLowerCase().replace('client', '');
if (constructorName && !constructorName.includes("Object")) {
return constructorName.toLowerCase().replace("client", "");
}

// Fallback: Generate a unique identifier
Expand All @@ -461,7 +462,9 @@ export async function initializeClients(
for (const client of plugin.clients) {
const startedClient = await client.start(runtime);
const clientType = determineClientType(client);
elizaLogger.debug(`Initializing client of type: ${clientType}`);
elizaLogger.debug(
`Initializing client of type: ${clientType}`
);
clients[clientType] = startedClient;
}
}
Expand Down Expand Up @@ -593,6 +596,9 @@ export async function createAgent(
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
getSecret(character, "STORY_PRIVATE_KEY") ? storyPlugin : null,
getSecret(character, "FUEL_PRIVATE_KEY") ? fuelPlugin : null,
getSecret(character, "AVALANCHE_PRIVATE_KEY")
? avalanchePlugin
: null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
20 changes: 20 additions & 0 deletions packages/plugin-avalanche/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@elizaos/plugin-avalanche",
"version": "0.0.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"viem": "2.21.49"
},
"devDependencies": {
"tsup": "8.3.5"
},
"scripts": {
"build": "tsup src/index.ts --format esm --no-dts"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
169 changes: 169 additions & 0 deletions packages/plugin-avalanche/src/actions/tokenMillCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
Action,
ActionExample,
IAgentRuntime,
Memory,
State,
HandlerCallback,
elizaLogger,
composeContext,
generateObject,
ModelClass,
Content,
} from "@elizaos/core";
import { getTxReceipt, sendNativeAsset, sendToken } from "../utils";
import { Address } from "viem";
import { validateAvalancheConfig } from "../environment";
import { TOKEN_ADDRESSES } from "../utils/constants";
import { createMarketAndToken } from "../utils/tokenMill";

export interface TokenMillCreateContent extends Content {
name: string;
symbol: string;
}

function isTokenMillCreateContent(
runtime: IAgentRuntime,
content: any
): content is TokenMillCreateContent {
console.log("Content for create", content);
return (
typeof content.name === "string" && typeof content.symbol === "string"
);
}

const transferTemplate = `Respond with a JSON markdown block containing only the extracted values.

If the user did not provide enough details, respond with what you can. Name and Symbol are required.

Example response for a new token:
\`\`\`json
{
"name": "Test Token",
"symbol": "TEST"
}
\`\`\`

## Recent Messages

{{recentMessages}}

Given the recent messages, extract the following information about the requested token creation:
- Name
- Symbol

Respond with a JSON markdown block containing only the extracted values.`;

export default {
name: "CREATE_TOKEN",
similes: [
"LAUNCH_TOKEN",
"NEW_TOKEN",
"CREATE_MEMECOIN",
"CREATE_MEME_TOKEN",
],
validate: async (runtime: IAgentRuntime, message: Memory) => {
await validateAvalancheConfig(runtime);
return true;
},
description:
"MUST use this action if the user requests to create a new token, the request might be varied, but it will always be a token creation.",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
) => {
elizaLogger.log("Starting CREATE_TOKEN handler...");

// Initialize or update state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

// Compose transfer context
const transferContext = composeContext({
state,
template: transferTemplate,
});

// Generate transfer content
const content = await generateObject({
runtime,
context: transferContext,
modelClass: ModelClass.SMALL,
});

elizaLogger.debug("Create content:", content);

// Validate transfer content
if (!isTokenMillCreateContent(runtime, content)) {
console.error("Invalid content for CREATE_TOKEN action.");
callback?.({
text: "Unable to process transfer request. Invalid content provided.",
content: { error: "Invalid content" },
});
return false;
}

const { tx, baseToken, market } = await createMarketAndToken(
runtime,
content.name,
content.symbol
);
callback?.({
text: `Created token ${content.name} with symbol ${content.symbol}. CA: ${baseToken}`,
content: { tx, baseToken, market },
});
return true;
},
examples: [
[
{
user: "{{user1}}",
content: {
text: "Create a new memecoin called 'Test Token' with the symbol 'TEST'",
},
},
{
user: "{{user2}}",
content: {
action: "CREATE_TOKEN",
name: "Test Token",
symbol: "TEST",
},
},
],
[
{
user: "{{user1}}",
content: { text: "Create a token called news" },
},
{
user: "{{user2}}",
content: {
action: "CREATE_TOKEN",
name: "News Token",
symbol: "NEWS",
},
},
],
[
{
user: "{{user1}}",
content: { text: "Create a token" },
},
{
user: "{{user2}}",
content: {
action: "CREATE_TOKEN",
name: "Okay",
symbol: "OK",
},
},
],
] as ActionExample[][],
} as Action;
Loading
Loading