Skip to content

Commit 719b0a7

Browse files
authored
Merge pull request #773 from ai16z/shaw/add-goat
Integrate goat plugin
2 parents f1abcbb + 9abeb1c commit 719b0a7

12 files changed

+418
-197
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ HEURIST_IMAGE_MODEL=
8686

8787
# EVM
8888
EVM_PRIVATE_KEY=
89+
EVM_PROVIDER_URL=
8990

9091
# Solana
9192
SOLANA_PRIVATE_KEY=

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@ai16z/plugin-node": "workspace:*",
3333
"@ai16z/plugin-solana": "workspace:*",
3434
"@ai16z/plugin-0g": "workspace:*",
35+
"@ai16z/plugin-goat": "workspace:*",
3536
"@ai16z/plugin-starknet": "workspace:*",
3637
"@ai16z/plugin-icp": "workspace:*",
3738
"@ai16z/plugin-tee": "workspace:*",

agent/src/index.ts

+2-18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
validateCharacterConfig,
2424
} from "@ai16z/eliza";
2525
import { zgPlugin } from "@ai16z/plugin-0g";
26+
import { goatPlugin } from "@ai16z/plugin-goat";
2627
import { bootstrapPlugin } from "@ai16z/plugin-bootstrap";
2728
// import { buttplugPlugin } from "@ai16z/plugin-buttplug";
2829
import {
@@ -90,24 +91,6 @@ export async function loadCharacters(
9091
.map((filePath) => filePath.trim());
9192
const loadedCharacters = [];
9293

93-
// Add logging here
94-
elizaLogger.info("Character loading details:", {
95-
characterPaths,
96-
cwd: process.cwd(),
97-
dirname: __dirname,
98-
fullPath: path.resolve(
99-
process.cwd(),
100-
"characters/8bitoracle.laozi.character.json"
101-
),
102-
exists: fs.existsSync(
103-
path.resolve(
104-
process.cwd(),
105-
"characters/8bitoracle.laozi.character.json"
106-
)
107-
),
108-
dirContents: fs.readdirSync(process.cwd()),
109-
});
110-
11194
if (characterPaths?.length > 0) {
11295
for (const characterPath of characterPaths) {
11396
let content = null;
@@ -393,6 +376,7 @@ export function createAgent(
393376
? [coinbaseMassPaymentsPlugin, tradePlugin]
394377
: []),
395378
getSecret(character, "WALLET_SECRET_SALT") ? teePlugin : null,
379+
getSecret(character, "ALCHEMY_API_KEY") ? goatPlugin : null,
396380
].filter(Boolean),
397381
providers: [],
398382
actions: [],

packages/plugin-goat/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Goat Plugin
2+
Example plugin setup of how you can integrate [Goat](https://ohmygoat.dev/) tools and plugins with Eliza.
3+
4+
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!
5+
6+
## Setup
7+
1. Configure your wallet (key pair, smart wallet, etc. see all available wallets at [https://ohmygoat.dev/wallets](https://ohmygoat.dev/wallets))
8+
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))
9+
3. Select a chain (see all available chains at [https://ohmygoat.dev/chains](https://ohmygoat.dev/chains))
10+
4. Import and add the plugin to your Eliza agent
11+
5. Build the project
12+
6. Add the necessary environment variables to set up your wallet and plugins

packages/plugin-goat/package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "@ai16z/plugin-goat",
3+
"version": "0.0.1",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@ai16z/eliza": "workspace:*",
9+
"@goat-sdk/core": "0.3.8",
10+
"@goat-sdk/plugin-erc20": "0.1.6",
11+
"@goat-sdk/wallet-viem": "0.1.3",
12+
"tsup": "^8.3.5",
13+
"viem": "^2.21.45"
14+
},
15+
"scripts": {
16+
"build": "tsup --format esm --dts"
17+
},
18+
"peerDependencies": {
19+
"whatwg-url": "7.1.0"
20+
}
21+
}

packages/plugin-goat/src/actions.ts

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import {
2+
type WalletClient,
3+
type Plugin,
4+
getDeferredTools,
5+
addParametersToDescription,
6+
type ChainForWalletClient,
7+
type DeferredTool,
8+
} from "@goat-sdk/core";
9+
import {
10+
type Action,
11+
generateText,
12+
type HandlerCallback,
13+
type IAgentRuntime,
14+
type Memory,
15+
ModelClass,
16+
type State,
17+
composeContext,
18+
generateObjectV2,
19+
} from "@ai16z/eliza";
20+
21+
type GetOnChainActionsParams<TWalletClient extends WalletClient> = {
22+
chain: ChainForWalletClient<TWalletClient>;
23+
getWalletClient: (runtime: IAgentRuntime) => Promise<TWalletClient>;
24+
plugins: Plugin<TWalletClient>[];
25+
supportsSmartWallets?: boolean;
26+
};
27+
28+
/**
29+
* Get all the on chain actions for the given wallet client and plugins
30+
*
31+
* @param params
32+
* @returns
33+
*/
34+
export async function getOnChainActions<TWalletClient extends WalletClient>({
35+
getWalletClient,
36+
plugins,
37+
chain,
38+
supportsSmartWallets,
39+
}: GetOnChainActionsParams<TWalletClient>): Promise<Action[]> {
40+
const tools = await getDeferredTools<TWalletClient>({
41+
plugins,
42+
wordForTool: "action",
43+
chain,
44+
supportsSmartWallets,
45+
});
46+
47+
return tools
48+
.map((action) => ({
49+
...action,
50+
name: action.name.toUpperCase(),
51+
}))
52+
.map((tool) => createAction(tool, getWalletClient));
53+
}
54+
55+
function createAction<TWalletClient extends WalletClient>(
56+
tool: DeferredTool<TWalletClient>,
57+
getWalletClient: (runtime: IAgentRuntime) => Promise<TWalletClient>
58+
): Action {
59+
return {
60+
name: tool.name,
61+
similes: [],
62+
description: tool.description,
63+
validate: async () => true,
64+
handler: async (
65+
runtime: IAgentRuntime,
66+
message: Memory,
67+
state: State | undefined,
68+
options?: Record<string, unknown>,
69+
callback?: HandlerCallback
70+
): Promise<boolean> => {
71+
try {
72+
const walletClient = await getWalletClient(runtime);
73+
let currentState =
74+
state ?? (await runtime.composeState(message));
75+
currentState =
76+
await runtime.updateRecentMessageState(currentState);
77+
78+
const parameterContext = composeParameterContext(
79+
tool,
80+
currentState
81+
);
82+
const parameters = await generateParameters(
83+
runtime,
84+
parameterContext,
85+
tool
86+
);
87+
88+
const parsedParameters = tool.parameters.safeParse(parameters);
89+
if (!parsedParameters.success) {
90+
callback?.({
91+
text: `Invalid parameters for action ${tool.name}: ${parsedParameters.error.message}`,
92+
content: { error: parsedParameters.error.message },
93+
});
94+
return false;
95+
}
96+
97+
const result = await tool.method(
98+
walletClient,
99+
parsedParameters.data
100+
);
101+
const responseContext = composeResponseContext(
102+
tool,
103+
result,
104+
currentState
105+
);
106+
const response = await generateResponse(
107+
runtime,
108+
responseContext
109+
);
110+
111+
callback?.({ text: response, content: result });
112+
return true;
113+
} catch (error) {
114+
const errorMessage =
115+
error instanceof Error ? error.message : String(error);
116+
callback?.({
117+
text: `Error executing action ${tool.name}: ${errorMessage}`,
118+
content: { error: errorMessage },
119+
});
120+
return false;
121+
}
122+
},
123+
examples: [],
124+
};
125+
}
126+
127+
function composeParameterContext<TWalletClient extends WalletClient>(
128+
tool: DeferredTool<TWalletClient>,
129+
state: State
130+
): string {
131+
const contextTemplate = `{{recentMessages}}
132+
133+
Given the recent messages, extract the following information for the action "${tool.name}":
134+
${addParametersToDescription("", tool.parameters)}
135+
`;
136+
return composeContext({ state, template: contextTemplate });
137+
}
138+
139+
async function generateParameters<TWalletClient extends WalletClient>(
140+
runtime: IAgentRuntime,
141+
context: string,
142+
tool: DeferredTool<TWalletClient>
143+
): Promise<unknown> {
144+
const { object } = await generateObjectV2({
145+
runtime,
146+
context,
147+
modelClass: ModelClass.SMALL,
148+
schema: tool.parameters,
149+
});
150+
151+
return object;
152+
}
153+
154+
function composeResponseContext<TWalletClient extends WalletClient>(
155+
tool: DeferredTool<TWalletClient>,
156+
result: unknown,
157+
state: State
158+
): string {
159+
const responseTemplate = `
160+
# Action Examples
161+
{{actionExamples}}
162+
(Action examples are for reference only. Do not use the information from them in your response.)
163+
164+
# Knowledge
165+
{{knowledge}}
166+
167+
# Task: Generate dialog and actions for the character {{agentName}}.
168+
About {{agentName}}:
169+
{{bio}}
170+
{{lore}}
171+
172+
{{providers}}
173+
174+
{{attachments}}
175+
176+
# Capabilities
177+
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.
178+
179+
The action "${tool.name}" was executed successfully.
180+
Here is the result:
181+
${JSON.stringify(result)}
182+
183+
{{actions}}
184+
185+
Respond to the message knowing that the action was successful and these were the previous messages:
186+
{{recentMessages}}
187+
`;
188+
return composeContext({ state, template: responseTemplate });
189+
}
190+
191+
async function generateResponse(
192+
runtime: IAgentRuntime,
193+
context: string
194+
): Promise<string> {
195+
return generateText({
196+
runtime,
197+
context,
198+
modelClass: ModelClass.SMALL,
199+
});
200+
}

packages/plugin-goat/src/index.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { Plugin } from '@ai16z/eliza'
2+
import { getOnChainActions } from './actions';
3+
import { erc20, USDC } from '@goat-sdk/plugin-erc20';
4+
import { chain, getWalletClient, walletProvider } from './provider';
5+
import { sendETH } from '@goat-sdk/core';
6+
7+
export const goatPlugin: Plugin = {
8+
name: "[GOAT] Onchain Actions",
9+
description: "Base integration plugin",
10+
providers: [walletProvider],
11+
evaluators: [],
12+
services: [],
13+
actions: [
14+
...(await getOnChainActions({
15+
getWalletClient,
16+
// Add plugins here based on what actions you want to use
17+
// See all available plugins at https://ohmygoat.dev/chains-wallets-plugins#plugins
18+
plugins: [sendETH(), erc20({ tokens: [USDC] })],
19+
chain: {
20+
type: "evm",
21+
id: chain.id,
22+
},
23+
})),
24+
],
25+
};
26+
27+
export default goatPlugin

packages/plugin-goat/src/provider.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { createWalletClient, http } from "viem";
2+
import { privateKeyToAccount } from "viem/accounts";
3+
import { base } from "viem/chains";
4+
5+
import { Memory, Provider, State, type IAgentRuntime } from "@ai16z/eliza";
6+
import { viem } from "@goat-sdk/wallet-viem";
7+
8+
9+
// Add the chain you want to use, remember to update also
10+
// the EVM_PROVIDER_URL to the correct one for the chain
11+
export const chain = base;
12+
13+
/**
14+
* Create a wallet client for the given runtime.
15+
*
16+
* You can change it to use a different wallet client such as Crossmint smart wallets or others.
17+
*
18+
* See all available wallet clients at https://ohmygoat.dev/wallets
19+
*
20+
* @param runtime
21+
* @returns Wallet client
22+
*/
23+
export async function getWalletClient(runtime: IAgentRuntime) {
24+
const privateKey = runtime.getSetting("EVM_PRIVATE_KEY");
25+
if (!privateKey) throw new Error("EVM_PRIVATE_KEY not configured");
26+
27+
const provider = runtime.getSetting("EVM_PROVIDER_URL");
28+
if (!provider) throw new Error("EVM_PROVIDER_URL not configured");
29+
30+
const walletClient = createWalletClient({
31+
account: privateKeyToAccount(privateKey as `0x${string}`),
32+
chain: chain,
33+
transport: http(provider),
34+
});
35+
return viem(walletClient);
36+
}
37+
38+
export const walletProvider: Provider = {
39+
async get(
40+
runtime: IAgentRuntime,
41+
message: Memory,
42+
state?: State
43+
): Promise<string | null> {
44+
try {
45+
const walletClient = await getWalletClient(runtime);
46+
const address = walletClient.getAddress();
47+
const balance = await walletClient.balanceOf(address);
48+
return `EVM Wallet Address: ${address}\nBalance: ${balance} ETH`;
49+
} catch (error) {
50+
console.error("Error in EVM wallet provider:", error);
51+
return null;
52+
}
53+
},
54+
};

packages/plugin-goat/tsconfig.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "../core/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "./src",
6+
"declaration": true
7+
},
8+
"include": [
9+
"src"
10+
]
11+
}

0 commit comments

Comments
 (0)