Skip to content
New issue

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

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

Already on GitHub? # to your account

feat: Add edwin plugin to eliza #3045

Merged
merged 1 commit into from
Feb 3, 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 @@ -57,6 +57,7 @@
"@elizaos/plugin-cosmos": "workspace:*",
"@elizaos/plugin-echochambers": "workspace:*",
"@elizaos/plugin-evm": "workspace:*",
"@elizaos/plugin-edwin": "workspace:*",
"@elizaos/plugin-flow": "workspace:*",
"@elizaos/plugin-gelato": "workspace:*",
"@elizaos/plugin-giphy": "workspace:*",
Expand Down
5 changes: 5 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { confluxPlugin } from "@elizaos/plugin-conflux";
import { createCosmosPlugin } from "@elizaos/plugin-cosmos";
import { cronosZkEVMPlugin } from "@elizaos/plugin-cronoszkevm";
import { evmPlugin } from "@elizaos/plugin-evm";
import { edwinPlugin } from "@elizaos/plugin-edwin";
import { flowPlugin } from "@elizaos/plugin-flow";
import { fuelPlugin } from "@elizaos/plugin-fuel";
import { genLayerPlugin } from "@elizaos/plugin-genlayer";
Expand Down Expand Up @@ -1068,6 +1069,10 @@ export async function createAgent(
getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))
? evmPlugin
: null,
(getSecret(character, "EVM_PRIVATE_KEY") ||
getSecret(character, "SOLANA_PRIVATE_KEY"))
? edwinPlugin
: null,
(getSecret(character, "EVM_PUBLIC_KEY") ||
getSecret(character, "INJECTIVE_PUBLIC_KEY")) &&
getSecret(character, "INJECTIVE_PRIVATE_KEY")
Expand Down
67 changes: 67 additions & 0 deletions packages/plugin-edwin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# @elizaos/plugin-edwin

Edwin plugin for Eliza that enables interaction with Edwin tools for DeFi operations.

## About

See full info and docs at [Edwin docs](https://docs.edwin.finance).
## Setup

1. Install dependencies:

```bash
pnpm install
```

2. Configure environment variables for chains you want to support:

```env
EVM_PRIVATE_KEY=<YOUR_EVM_PRIVATE_KEY>
SOLANA_PRIVATE_KEY=<YOUR_SOLANA_PRIVATE_KEY>
```

## Available Tools

The plugin provides access to the following Edwin tools:

- supply
- withdraw
- stake
- addLiquidity
- removeLiquidity

## Usage Examples

1. Supply on AAVE:

```
Supply 100 USDC to AAVE
```

2. Add liquidity on Meteora:

```
Find a meteora pool with high liquidity and add to td 10 USDC and 0.01 SOL.
```

## Development

1. Build the plugin:

```bash
pnpm build
```

2. Run in development mode:

```bash
pnpm dev
```

## Dependencies

- edwin-sdk

## License

MIT
17 changes: 17 additions & 0 deletions packages/plugin-edwin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@elizaos/plugin-edwin",
"version": "0.1.0",
"description": "Edwin plugin for elizaos agent",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"edwin-sdk": "0.3.4",
"tsup": "8.3.5"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch"
}
}
138 changes: 138 additions & 0 deletions packages/plugin-edwin/src/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {
type Action,
generateText,
type HandlerCallback,
type IAgentRuntime,
type Memory,
ModelClass,
type State,
composeContext,
generateObjectDeprecated,
} from "@elizaos/core";

import { Edwin, EdwinAction } from "edwin-sdk";

type GetEdwinActionsParams = {
getClient: () => Promise<Edwin>;
};

/**
* Get all edwin actions
*/
export async function getEdwinActions({
getClient,
}: GetEdwinActionsParams): Promise<Action[]> {
const edwin = await getClient();
const edwinActions = await edwin.getActions();
const actions = edwinActions.map((action: EdwinAction) => ({
name: action.name.toUpperCase(),
description: action.description,
similes: [],
validate: async () => true,
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State | undefined,
options?: Record<string, unknown>,
callback?: HandlerCallback
): Promise<boolean> => {
try {
const client = await getClient();
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}
const parameterContext = composeContext({
state,
template: action.template,
});
const parameters = await generateObjectDeprecated({
runtime,
context: parameterContext,
modelClass: ModelClass.LARGE,
});
const result = await executeAction(action, parameters, client);
const responseContext = composeResponseContext(
action,
result,
state
);
const response = await generateResponse(
runtime,
responseContext
);
callback?.({ text: response, content: result });
return true;
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
callback?.({
text: `Error executing action ${action.name}: ${errorMessage}`,
content: { error: errorMessage },
});
return false;
}
},
examples: [],
}));
return actions;
}

async function executeAction(
action: EdwinAction,
parameters: any,
edwin: Edwin
): Promise<unknown> {
const result = await action.execute(parameters);
return result;
}

function composeResponseContext(
action: EdwinAction,
result: unknown,
state: State
): string {
const responseTemplate = `
# Action Examples
{{actionExamples}}

# Knowledge
{{knowledge}}

# Task: Generate dialog and actions for the character {{agentName}}.
About {{agentName}}:
{{bio}}
{{lore}}

{{providers}}

{{attachments}}

# Capabilities
Note that {{agentName}} is capable of reading/seeing/hearing various forms of media, including images, videos, audio, plaintext and PDFs. Recent attachments have been included above under the "Attachments" section.

The action "${action.name}" was executed successfully.
Here is the result:
${JSON.stringify(result)}

{{actions}}

Respond to the message knowing that the action was successful and these were the previous messages:
{{recentMessages}}
`;
const context = composeContext({ state, template: responseTemplate });
return context;
}

async function generateResponse(
runtime: IAgentRuntime,
context: string
): Promise<string> {
const response = await generateText({
runtime,
context,
modelClass: ModelClass.LARGE,
});
return response;
}
28 changes: 28 additions & 0 deletions packages/plugin-edwin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Plugin } from "@elizaos/core";
import { edwinProvider, getEdwinClient } from "./provider";
import { getEdwinActions } from "./actions";

// Initial banner
console.log("\n┌═════════════════════════════════════┐");
console.log("│ EDWIN PLUGIN │");
console.log("│ ,_, │");
console.log("│ (o,o) │");
console.log("│ {`\"'} │");
console.log("│ -\"-\"- │");
console.log("├─────────────────────────────────────┤");
console.log("│ Initializing Edwin Plugin... │");
console.log("│ Version: 0.0.1 │");
console.log("└═════════════════════════════════════┘");

export const edwinPlugin: Plugin = {
name: "[Edwin] Integration",
description: "Edwin integration plugin",
providers: [edwinProvider],
evaluators: [],
services: [],
actions: await getEdwinActions({
getClient: getEdwinClient,
}),
};

export default edwinPlugin;
34 changes: 34 additions & 0 deletions packages/plugin-edwin/src/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Provider, IAgentRuntime } from "@elizaos/core";
import { Edwin } from "edwin-sdk";
import { EdwinConfig } from "edwin-sdk";

// Static variable to hold the singleton instance
let edwinRunningInstance: Edwin | null = null;

export async function getEdwinClient(): Promise<Edwin> {
// If instance exists, return it
if (edwinRunningInstance) {
return edwinRunningInstance;
}
// Otherwise create new instance
const edwinConfig: EdwinConfig = {
evmPrivateKey: process.env.EVM_PRIVATE_KEY as `0x${string}`,
solanaPrivateKey: process.env.SOLANA_PRIVATE_KEY as string,
actions: ["supply", "withdraw", "stake", "getPools", "addLiquidity"],
};

edwinRunningInstance = new Edwin(edwinConfig);
return edwinRunningInstance;
}

export const edwinProvider: Provider = {
async get(runtime: IAgentRuntime): Promise<string | null> {
try {
const edwin = await getEdwinClient();
return edwin.getPortfolio();
} catch (error) {
console.error("Error in Edwin provider:", error);
return null;
}
},
};
9 changes: 9 additions & 0 deletions packages/plugin-edwin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../core/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "./src",
"declaration": true
},
"include": ["src"]
}
21 changes: 21 additions & 0 deletions packages/plugin-edwin/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
outDir: "dist",
sourcemap: true,
clean: true,
format: ["esm"], // Ensure you're targeting CommonJS
external: [
"dotenv", // Externalize dotenv to prevent bundling
"fs", // Externalize fs to use Node.js built-in module
"path", // Externalize other built-ins if necessary
"@reflink/reflink",
"@node-llama-cpp",
"https",
"http",
"agentkeepalive",
"viem",
"@lifi/sdk",
],
});
Loading