Skip to content

Commit 99106da

Browse files
authoredDec 26, 2024
Merge pull request elizaOS#1464 from elizaOS/thomas/cronos-zkevm
feat: Adding plugin for Cronos ZKEVM
2 parents 5b3385c + e720c67 commit 99106da

File tree

10 files changed

+366
-5
lines changed

10 files changed

+366
-5
lines changed
 

‎.env.example

+4
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,7 @@ STORY_PRIVATE_KEY= # Story private key
340340
STORY_API_BASE_URL= # Story API base URL
341341
STORY_API_KEY= # Story API key
342342
PINATA_JWT= # Pinata JWT for uploading files to IPFS
343+
344+
# Cronos zkEVM
345+
CRONOSZKEVM_ADDRESS=
346+
CRONOSZKEVM_PRIVATE_KEY=

‎agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@elizaos/plugin-multiversx": "workspace:*",
5353
"@elizaos/plugin-near": "workspace:*",
5454
"@elizaos/plugin-zksync-era": "workspace:*",
55+
"@elizaos/plugin-cronoszkevm": "workspace:*",
5556
"@elizaos/plugin-3d-generation": "workspace:*",
5657
"readline": "1.3.0",
5758
"ws": "8.18.0",

‎agent/src/index.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { suiPlugin } from "@elizaos/plugin-sui";
5656
import { TEEMode, teePlugin } from "@elizaos/plugin-tee";
5757
import { tonPlugin } from "@elizaos/plugin-ton";
5858
import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
59+
import { cronosZkEVMPlugin } from "@elizaos/plugin-cronoszkEVM";
5960
import { abstractPlugin } from "@elizaos/plugin-abstract";
6061
import Database from "better-sqlite3";
6162
import fs from "fs";
@@ -181,7 +182,7 @@ export async function loadCharacters(
181182

182183
// .id isn't really valid
183184
const characterId = character.id || character.name;
184-
const characterPrefix = `CHARACTER.${characterId.toUpperCase().replace(/ /g, '_')}.`;
185+
const characterPrefix = `CHARACTER.${characterId.toUpperCase().replace(/ /g, "_")}.`;
185186

186187
const characterSettings = Object.entries(process.env)
187188
.filter(([key]) => key.startsWith(characterPrefix))
@@ -194,7 +195,7 @@ export async function loadCharacters(
194195
character.settings = character.settings || {};
195196
character.settings.secrets = {
196197
...characterSettings,
197-
...character.settings.secrets
198+
...character.settings.secrets,
198199
};
199200
}
200201

@@ -536,9 +537,7 @@ export async function createAgent(
536537
getSecret(character, "HEURIST_API_KEY")
537538
? imageGenerationPlugin
538539
: null,
539-
getSecret(character, "FAL_API_KEY")
540-
? ThreeDGenerationPlugin
541-
: null,
540+
getSecret(character, "FAL_API_KEY") ? ThreeDGenerationPlugin : null,
542541
...(getSecret(character, "COINBASE_API_KEY") &&
543542
getSecret(character, "COINBASE_PRIVATE_KEY")
544543
? [
@@ -567,6 +566,9 @@ export async function createAgent(
567566
getSecret(character, "APTOS_PRIVATE_KEY") ? aptosPlugin : null,
568567
getSecret(character, "MVX_PRIVATE_KEY") ? multiversxPlugin : null,
569568
getSecret(character, "ZKSYNC_PRIVATE_KEY") ? zksyncEraPlugin : null,
569+
getSecret(character, "CRONOSZKEVM_PRIVATE_KEY")
570+
? cronosZkEVMPlugin
571+
: null,
570572
getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null,
571573
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
572574
getSecret(character, "STORY_PRIVATE_KEY") ? storyPlugin : null,
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "@elizaos/plugin-cronoszkevm",
3+
"version": "0.1.4-alpha.3",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@elizaos/core": "workspace:*",
9+
"@elizaos/plugin-trustdb": "workspace:*",
10+
"tsup": "^8.3.5",
11+
"web3": "^4.15.0",
12+
"web3-plugin-zksync": "^1.0.8"
13+
},
14+
"scripts": {
15+
"build": "tsup --format esm --dts"
16+
},
17+
"peerDependencies": {
18+
"whatwg-url": "7.1.0"
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import {
2+
ActionExample,
3+
Content,
4+
HandlerCallback,
5+
IAgentRuntime,
6+
Memory,
7+
ModelClass,
8+
State,
9+
type Action,
10+
elizaLogger,
11+
composeContext,
12+
generateObject,
13+
} from "@elizaos/core";
14+
import { validateCronosZkevmConfig } from "../enviroment";
15+
16+
import { Web3 } from "web3";
17+
import {
18+
ZKsyncPlugin,
19+
ZKsyncWallet,
20+
types,
21+
Web3ZKsyncL2,
22+
} from "web3-plugin-zksync";
23+
24+
export interface TransferContent extends Content {
25+
tokenAddress: string;
26+
recipient: string;
27+
amount: string | number;
28+
}
29+
30+
export function isTransferContent(
31+
content: TransferContent
32+
): content is TransferContent {
33+
// Validate types
34+
const validTypes =
35+
typeof content.tokenAddress === "string" &&
36+
typeof content.recipient === "string" &&
37+
(typeof content.amount === "string" ||
38+
typeof content.amount === "number");
39+
if (!validTypes) {
40+
return false;
41+
}
42+
43+
// Validate addresses
44+
const validAddresses =
45+
content.tokenAddress.startsWith("0x") &&
46+
content.tokenAddress.length === 42 &&
47+
content.recipient.startsWith("0x") &&
48+
content.recipient.length === 42;
49+
50+
return validAddresses;
51+
}
52+
53+
const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
54+
55+
Here are several frequently used addresses. Use these for the corresponding tokens:
56+
- ZKCRO/zkCRO: 0x000000000000000000000000000000000000800A
57+
- USDC/usdc: 0xaa5b845f8c9c047779bedf64829601d8b264076c
58+
- ETH/eth: 0x898b3560affd6d955b1574d87ee09e46669c60ea
59+
60+
Example response:
61+
\`\`\`json
62+
{
63+
"tokenAddress": "0xaa5b845f8c9c047779bedf64829601d8b264076c",
64+
"recipient": "0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
65+
"amount": "1000"
66+
}
67+
\`\`\`
68+
69+
{{recentMessages}}
70+
71+
Given the recent messages, extract the following information about the requested token transfer:
72+
- Token contract address
73+
- Recipient wallet address
74+
- Amount to transfer
75+
76+
Respond with a JSON markdown block containing only the extracted values.`;
77+
78+
export default {
79+
name: "SEND_TOKEN",
80+
similes: [
81+
"TRANSFER_TOKEN_ON_CRONOSZKEVM",
82+
"TRANSFER_TOKENS_ON_CRONOSZK",
83+
"SEND_TOKENS_ON_CRONOSZKEVM",
84+
"SEND_TOKENS_ON_CRONOSZK",
85+
"SEND_ETH_ON_CRONOSZKEVM",
86+
"SEND_ETH_ON_CRONOSZK",
87+
"PAY_ON_CRONOSZKEVM",
88+
"PAY_ON_CRONOSZK",
89+
],
90+
validate: async (runtime: IAgentRuntime, message: Memory) => {
91+
await validateCronosZkevmConfig(runtime);
92+
return true;
93+
},
94+
description: "Transfer tokens from the agent's wallet to another address",
95+
handler: async (
96+
runtime: IAgentRuntime,
97+
message: Memory,
98+
state: State,
99+
_options: { [key: string]: unknown },
100+
callback?: HandlerCallback
101+
): Promise<boolean> => {
102+
elizaLogger.log("Starting SEND_TOKEN handler...");
103+
104+
// Initialize or update state
105+
if (!state) {
106+
state = (await runtime.composeState(message)) as State;
107+
} else {
108+
state = await runtime.updateRecentMessageState(state);
109+
}
110+
111+
// Compose transfer context
112+
const transferContext = composeContext({
113+
state,
114+
template: transferTemplate,
115+
});
116+
117+
// Generate transfer content
118+
const content = await generateObject({
119+
runtime,
120+
context: transferContext,
121+
modelClass: ModelClass.SMALL,
122+
});
123+
124+
// Validate transfer content
125+
if (!isTransferContent(content)) {
126+
console.error("Invalid content for TRANSFER_TOKEN action.");
127+
if (callback) {
128+
callback({
129+
text: "Unable to process transfer request. Invalid content provided.",
130+
content: { error: "Invalid transfer content" },
131+
});
132+
}
133+
return false;
134+
}
135+
136+
try {
137+
const PRIVATE_KEY = runtime.getSetting("CRONOSZKEVM_PRIVATE_KEY")!;
138+
const PUBLIC_KEY = runtime.getSetting("CRONOSZKEVM_ADDRESS")!;
139+
140+
const web3: Web3 = new Web3(/* optional L1 provider */);
141+
142+
web3.registerPlugin(
143+
new ZKsyncPlugin(
144+
new Web3ZKsyncL2("https://mainnet.zkevm.cronos.org")
145+
)
146+
);
147+
148+
const smartAccount = new web3.ZKsync.SmartAccount({
149+
address: PUBLIC_KEY,
150+
secret: "0x" + PRIVATE_KEY,
151+
});
152+
153+
const transferTx = await smartAccount.transfer({
154+
to: content.recipient,
155+
token: content.tokenAddress,
156+
amount: web3.utils.toWei(content.amount, "ether"),
157+
});
158+
159+
const receipt = await transferTx.wait();
160+
161+
elizaLogger.success(
162+
"Transfer completed successfully! tx: " +
163+
receipt.transactionHash
164+
);
165+
if (callback) {
166+
callback({
167+
text:
168+
"Transfer completed successfully! tx: " +
169+
receipt.transactionHash,
170+
content: {},
171+
});
172+
}
173+
174+
return true;
175+
} catch (error) {
176+
elizaLogger.error("Error during token transfer:", error);
177+
if (callback) {
178+
callback({
179+
text: `Error transferring tokens: ${error.message}`,
180+
content: { error: error.message },
181+
});
182+
}
183+
return false;
184+
}
185+
},
186+
187+
examples: [
188+
[
189+
{
190+
user: "{{user1}}",
191+
content: {
192+
text: "Send 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
193+
},
194+
},
195+
{
196+
user: "{{agent}}",
197+
content: {
198+
text: "Sure, I'll send 100 USDC to that address now.",
199+
action: "SEND_TOKEN",
200+
},
201+
},
202+
{
203+
user: "{{agent}}",
204+
content: {
205+
text: "Successfully sent 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec",
206+
},
207+
},
208+
],
209+
[
210+
{
211+
user: "{{user1}}",
212+
content: {
213+
text: "Please send 100 ZKCRO tokens to 0xbD8679cf79137042214fA4239b02F4022208EE82",
214+
},
215+
},
216+
{
217+
user: "{{agent}}",
218+
content: {
219+
text: "Of course. Sending 100 ZKCRO to that address now.",
220+
action: "SEND_TOKEN",
221+
},
222+
},
223+
{
224+
user: "{{agent}}",
225+
content: {
226+
text: "Successfully sent 100 ZKCRO to 0xbD8679cf79137042214fA4239b02F4022208EE82\nTransaction: 0x0b9f23e69ea91ba98926744472717960cc7018d35bc3165bdba6ae41670da0f0",
227+
},
228+
},
229+
],
230+
] as ActionExample[][],
231+
} as Action;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { IAgentRuntime } from "@elizaos/eliza";
2+
import { z } from "zod";
3+
4+
export const CronosZkEVMEnvSchema = z.object({
5+
CRONOSZKEVM_ADDRESS: z.string().min(1, "Cronos zkEVM address is required"),
6+
CRONOSZKEVM_PRIVATE_KEY: z
7+
.string()
8+
.min(1, "Cronos zkEVM private key is required"),
9+
});
10+
11+
export type CronoszkEVMConfig = z.infer<typeof CronosZkEVMEnvSchema>;
12+
13+
export async function validateCronosZkevmConfig(
14+
runtime: IAgentRuntime
15+
): Promise<CronoszkEVMConfig> {
16+
try {
17+
const config = {
18+
CRONOSZKEVM_ADDRESS:
19+
runtime.getSetting("CRONOSZKEVM_ADDRESS") ||
20+
process.env.CRONOSZKEVM_ADDRESS,
21+
CRONOSZKEVM_PRIVATE_KEY:
22+
runtime.getSetting("CRONOSZKEVM_PRIVATE_KEY") ||
23+
process.env.CRONOSZKEVM_PRIVATE_KEY,
24+
};
25+
26+
return CronosZkEVMEnvSchema.parse(config);
27+
} catch (error) {
28+
if (error instanceof z.ZodError) {
29+
const errorMessages = error.errors
30+
.map((err) => `${err.path.join(".")}: ${err.message}`)
31+
.join("\n");
32+
throw new Error(
33+
`CronosZkEVM configuration validation failed:\n${errorMessages}`
34+
);
35+
}
36+
throw error;
37+
}
38+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Plugin } from "@elizaos/core";
2+
3+
import transfer from "./actions/transfer";
4+
5+
export const cronosZkEVMPlugin: Plugin = {
6+
name: "cronoszkevm",
7+
description: "Cronos zkEVM plugin for Eliza",
8+
actions: [transfer],
9+
evaluators: [],
10+
providers: [],
11+
};
12+
13+
export default cronosZkEVMPlugin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../core/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "src"
6+
},
7+
"include": ["src/**/*.ts"]
8+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig({
4+
entry: ["src/index.ts"],
5+
outDir: "dist",
6+
sourcemap: true,
7+
clean: true,
8+
format: ["esm"], // Ensure you're targeting CommonJS
9+
external: [
10+
"dotenv", // Externalize dotenv to prevent bundling
11+
"fs", // Externalize fs to use Node.js built-in module
12+
"path", // Externalize other built-ins if necessary
13+
"@reflink/reflink",
14+
"@node-llama-cpp",
15+
"https",
16+
"http",
17+
"agentkeepalive"
18+
// Add other modules you want to externalize
19+
],
20+
});

0 commit comments

Comments
 (0)
Please sign in to comment.