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

Integrate goat plugin #773

Merged
merged 8 commits into from
Dec 1, 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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ HEURIST_IMAGE_MODEL=

# EVM
EVM_PRIVATE_KEY=
EVM_PROVIDER_URL=

# Solana
SOLANA_PRIVATE_KEY=
Expand Down
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@ai16z/plugin-node": "workspace:*",
"@ai16z/plugin-solana": "workspace:*",
"@ai16z/plugin-0g": "workspace:*",
"@ai16z/plugin-goat": "workspace:*",
"@ai16z/plugin-starknet": "workspace:*",
"@ai16z/plugin-icp": "workspace:*",
"@ai16z/plugin-tee": "workspace:*",
Expand Down
20 changes: 2 additions & 18 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
validateCharacterConfig,
} from "@ai16z/eliza";
import { zgPlugin } from "@ai16z/plugin-0g";
import { goatPlugin } from "@ai16z/plugin-goat";
import { bootstrapPlugin } from "@ai16z/plugin-bootstrap";
// import { buttplugPlugin } from "@ai16z/plugin-buttplug";
import {
Expand Down Expand Up @@ -90,24 +91,6 @@ export async function loadCharacters(
.map((filePath) => filePath.trim());
const loadedCharacters = [];

// Add logging here
elizaLogger.info("Character loading details:", {
characterPaths,
cwd: process.cwd(),
dirname: __dirname,
fullPath: path.resolve(
process.cwd(),
"characters/8bitoracle.laozi.character.json"
),
exists: fs.existsSync(
path.resolve(
process.cwd(),
"characters/8bitoracle.laozi.character.json"
)
),
dirContents: fs.readdirSync(process.cwd()),
});

if (characterPaths?.length > 0) {
for (const characterPath of characterPaths) {
let content = null;
Expand Down Expand Up @@ -393,6 +376,7 @@ export function createAgent(
? [coinbaseMassPaymentsPlugin, tradePlugin]
: []),
getSecret(character, "WALLET_SECRET_SALT") ? teePlugin : null,
getSecret(character, "ALCHEMY_API_KEY") ? goatPlugin : null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
12 changes: 12 additions & 0 deletions packages/plugin-goat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Goat Plugin
Example plugin setup of how you can integrate [Goat](https://ohmygoat.dev/) tools and plugins with Eliza.

Adds onchain capabilities to your agent to send and check balances of ETH and USDC. Add all the capabilities you need by adding more plugins!

## Setup
1. Configure your wallet (key pair, smart wallet, etc. see all available wallets at [https://ohmygoat.dev/wallets](https://ohmygoat.dev/wallets))
2. Add the plugins you need (uniswap, zora, polymarket, etc. see all available plugins at [https://ohmygoat.dev/chains-wallets-plugins](https://ohmygoat.dev/chains-wallets-plugins))
3. Select a chain (see all available chains at [https://ohmygoat.dev/chains](https://ohmygoat.dev/chains))
4. Import and add the plugin to your Eliza agent
5. Build the project
6. Add the necessary environment variables to set up your wallet and plugins
21 changes: 21 additions & 0 deletions packages/plugin-goat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@ai16z/plugin-goat",
"version": "0.0.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@ai16z/eliza": "workspace:*",
"@goat-sdk/core": "0.3.8",
"@goat-sdk/plugin-erc20": "0.1.6",
"@goat-sdk/wallet-viem": "0.1.3",
"tsup": "^8.3.5",
"viem": "^2.21.45"
},
"scripts": {
"build": "tsup --format esm --dts"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
200 changes: 200 additions & 0 deletions packages/plugin-goat/src/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import {
type WalletClient,
type Plugin,
getDeferredTools,
addParametersToDescription,
type ChainForWalletClient,
type DeferredTool,
} from "@goat-sdk/core";
import {
type Action,
generateText,
type HandlerCallback,
type IAgentRuntime,
type Memory,
ModelClass,
type State,
composeContext,
generateObjectV2,
} from "@ai16z/eliza";

type GetOnChainActionsParams<TWalletClient extends WalletClient> = {
chain: ChainForWalletClient<TWalletClient>;
getWalletClient: (runtime: IAgentRuntime) => Promise<TWalletClient>;
plugins: Plugin<TWalletClient>[];
supportsSmartWallets?: boolean;
};

/**
* Get all the on chain actions for the given wallet client and plugins
*
* @param params
* @returns
*/
export async function getOnChainActions<TWalletClient extends WalletClient>({
getWalletClient,
plugins,
chain,
supportsSmartWallets,
}: GetOnChainActionsParams<TWalletClient>): Promise<Action[]> {
const tools = await getDeferredTools<TWalletClient>({
plugins,
wordForTool: "action",
chain,
supportsSmartWallets,
});

return tools
.map((action) => ({
...action,
name: action.name.toUpperCase(),
}))
.map((tool) => createAction(tool, getWalletClient));
}

function createAction<TWalletClient extends WalletClient>(
tool: DeferredTool<TWalletClient>,
getWalletClient: (runtime: IAgentRuntime) => Promise<TWalletClient>
): Action {
return {
name: tool.name,
similes: [],
description: tool.description,
validate: async () => true,
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State | undefined,
options?: Record<string, unknown>,
callback?: HandlerCallback
): Promise<boolean> => {
try {
const walletClient = await getWalletClient(runtime);
let currentState =
state ?? (await runtime.composeState(message));
currentState =
await runtime.updateRecentMessageState(currentState);

const parameterContext = composeParameterContext(
tool,
currentState
);
const parameters = await generateParameters(
runtime,
parameterContext,
tool
);

const parsedParameters = tool.parameters.safeParse(parameters);
if (!parsedParameters.success) {
callback?.({
text: `Invalid parameters for action ${tool.name}: ${parsedParameters.error.message}`,
content: { error: parsedParameters.error.message },
});
return false;
}

const result = await tool.method(
walletClient,
parsedParameters.data
);
const responseContext = composeResponseContext(
tool,
result,
currentState
);
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 ${tool.name}: ${errorMessage}`,
content: { error: errorMessage },
});
return false;
}
},
examples: [],
};
}

function composeParameterContext<TWalletClient extends WalletClient>(
tool: DeferredTool<TWalletClient>,
state: State
): string {
const contextTemplate = `{{recentMessages}}

Given the recent messages, extract the following information for the action "${tool.name}":
${addParametersToDescription("", tool.parameters)}
`;
return composeContext({ state, template: contextTemplate });
}

async function generateParameters<TWalletClient extends WalletClient>(
runtime: IAgentRuntime,
context: string,
tool: DeferredTool<TWalletClient>
): Promise<unknown> {
const { object } = await generateObjectV2({
runtime,
context,
modelClass: ModelClass.SMALL,
schema: tool.parameters,
});

return object;
}

function composeResponseContext<TWalletClient extends WalletClient>(
tool: DeferredTool<TWalletClient>,
result: unknown,
state: State
): string {
const responseTemplate = `
# Action Examples
{{actionExamples}}
(Action examples are for reference only. Do not use the information from them in your response.)

# 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 "${tool.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}}
`;
return composeContext({ state, template: responseTemplate });
}

async function generateResponse(
runtime: IAgentRuntime,
context: string
): Promise<string> {
return generateText({
runtime,
context,
modelClass: ModelClass.SMALL,
});
}
27 changes: 27 additions & 0 deletions packages/plugin-goat/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Plugin } from '@ai16z/eliza'
import { getOnChainActions } from './actions';
import { erc20, USDC } from '@goat-sdk/plugin-erc20';
import { chain, getWalletClient, walletProvider } from './provider';
import { sendETH } from '@goat-sdk/core';

export const goatPlugin: Plugin = {
name: "[GOAT] Onchain Actions",
description: "Base integration plugin",
providers: [walletProvider],
evaluators: [],
services: [],
actions: [
...(await getOnChainActions({
getWalletClient,
// Add plugins here based on what actions you want to use
// See all available plugins at https://ohmygoat.dev/chains-wallets-plugins#plugins
plugins: [sendETH(), erc20({ tokens: [USDC] })],
chain: {
type: "evm",
id: chain.id,
},
})),
],
};

export default goatPlugin
54 changes: 54 additions & 0 deletions packages/plugin-goat/src/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

import { Memory, Provider, State, type IAgentRuntime } from "@ai16z/eliza";
import { viem } from "@goat-sdk/wallet-viem";


// Add the chain you want to use, remember to update also
// the EVM_PROVIDER_URL to the correct one for the chain
export const chain = base;

/**
* Create a wallet client for the given runtime.
*
* You can change it to use a different wallet client such as Crossmint smart wallets or others.
*
* See all available wallet clients at https://ohmygoat.dev/wallets
*
* @param runtime
* @returns Wallet client
*/
export async function getWalletClient(runtime: IAgentRuntime) {
const privateKey = runtime.getSetting("EVM_PRIVATE_KEY");
if (!privateKey) throw new Error("EVM_PRIVATE_KEY not configured");

const provider = runtime.getSetting("EVM_PROVIDER_URL");
if (!provider) throw new Error("EVM_PROVIDER_URL not configured");

const walletClient = createWalletClient({
account: privateKeyToAccount(privateKey as `0x${string}`),
chain: chain,
transport: http(provider),
});
return viem(walletClient);
}

export const walletProvider: Provider = {
async get(
runtime: IAgentRuntime,
message: Memory,
state?: State
): Promise<string | null> {
try {
const walletClient = await getWalletClient(runtime);
const address = walletClient.getAddress();
const balance = await walletClient.balanceOf(address);
return `EVM Wallet Address: ${address}\nBalance: ${balance} ETH`;
} catch (error) {
console.error("Error in EVM wallet provider:", error);
return null;
}
},
};
11 changes: 11 additions & 0 deletions packages/plugin-goat/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../core/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "./src",
"declaration": true
},
"include": [
"src"
]
}
Loading
Loading