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: Adding plugin for Cronos ZKEVM #1464

Merged
merged 10 commits into from
Dec 26, 2024
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,7 @@ STORY_PRIVATE_KEY= # Story private key
STORY_API_BASE_URL= # Story API base URL
STORY_API_KEY= # Story API key
PINATA_JWT= # Pinata JWT for uploading files to IPFS

# Cronos zkEVM
CRONOSZKEVM_ADDRESS=
CRONOSZKEVM_PRIVATE_KEY=
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@elizaos/plugin-multiversx": "workspace:*",
"@elizaos/plugin-near": "workspace:*",
"@elizaos/plugin-zksync-era": "workspace:*",
"@elizaos/plugin-cronoszkevm": "workspace:*",
"@elizaos/plugin-3d-generation": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
Expand Down
12 changes: 7 additions & 5 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { suiPlugin } from "@elizaos/plugin-sui";
import { TEEMode, teePlugin } from "@elizaos/plugin-tee";
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 Database from "better-sqlite3";
import fs from "fs";
Expand Down Expand Up @@ -181,7 +182,7 @@ export async function loadCharacters(

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

const characterSettings = Object.entries(process.env)
.filter(([key]) => key.startsWith(characterPrefix))
Expand All @@ -194,7 +195,7 @@ export async function loadCharacters(
character.settings = character.settings || {};
character.settings.secrets = {
...characterSettings,
...character.settings.secrets
...character.settings.secrets,
};
}

Expand Down Expand Up @@ -536,9 +537,7 @@ export async function createAgent(
getSecret(character, "HEURIST_API_KEY")
? imageGenerationPlugin
: null,
getSecret(character, "FAL_API_KEY")
? ThreeDGenerationPlugin
: null,
getSecret(character, "FAL_API_KEY") ? ThreeDGenerationPlugin : null,
...(getSecret(character, "COINBASE_API_KEY") &&
getSecret(character, "COINBASE_PRIVATE_KEY")
? [
Expand Down Expand Up @@ -567,6 +566,9 @@ export async function createAgent(
getSecret(character, "APTOS_PRIVATE_KEY") ? aptosPlugin : null,
getSecret(character, "MVX_PRIVATE_KEY") ? multiversxPlugin : null,
getSecret(character, "ZKSYNC_PRIVATE_KEY") ? zksyncEraPlugin : null,
getSecret(character, "CRONOSZKEVM_PRIVATE_KEY")
? cronosZkEVMPlugin
: null,
getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null,
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
getSecret(character, "STORY_PRIVATE_KEY") ? storyPlugin : null,
Expand Down
20 changes: 20 additions & 0 deletions packages/plugin-cronoszkevm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@elizaos/plugin-cronoszkevm",
"version": "0.1.4-alpha.3",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"@elizaos/plugin-trustdb": "workspace:*",
"tsup": "^8.3.5",
"web3": "^4.15.0",
"web3-plugin-zksync": "^1.0.8"
},
"scripts": {
"build": "tsup --format esm --dts"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
231 changes: 231 additions & 0 deletions packages/plugin-cronoszkevm/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import {
ActionExample,
Content,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
type Action,
elizaLogger,
composeContext,
generateObject,
} from "@elizaos/core";
import { validateCronosZkevmConfig } from "../enviroment";

import { Web3 } from "web3";
import {
ZKsyncPlugin,
ZKsyncWallet,
types,
Web3ZKsyncL2,
} from "web3-plugin-zksync";

export interface TransferContent extends Content {
tokenAddress: string;
recipient: string;
amount: string | number;
}

export function isTransferContent(
content: TransferContent
): content is TransferContent {
// Validate types
const validTypes =
typeof content.tokenAddress === "string" &&
typeof content.recipient === "string" &&
(typeof content.amount === "string" ||
typeof content.amount === "number");
if (!validTypes) {
return false;
}

// Validate addresses
const validAddresses =
content.tokenAddress.startsWith("0x") &&
content.tokenAddress.length === 42 &&
content.recipient.startsWith("0x") &&
content.recipient.length === 42;

return validAddresses;
}

const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
Here are several frequently used addresses. Use these for the corresponding tokens:
- ZKCRO/zkCRO: 0x000000000000000000000000000000000000800A
- USDC/usdc: 0xaa5b845f8c9c047779bedf64829601d8b264076c
- ETH/eth: 0x898b3560affd6d955b1574d87ee09e46669c60ea
Example response:
\`\`\`json
{
"tokenAddress": "0xaa5b845f8c9c047779bedf64829601d8b264076c",
"recipient": "0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
"amount": "1000"
}
\`\`\`
{{recentMessages}}
Given the recent messages, extract the following information about the requested token transfer:
- Token contract address
- Recipient wallet address
- Amount to transfer
Respond with a JSON markdown block containing only the extracted values.`;

export default {
name: "SEND_TOKEN",
similes: [
"TRANSFER_TOKEN_ON_CRONOSZKEVM",
"TRANSFER_TOKENS_ON_CRONOSZK",
"SEND_TOKENS_ON_CRONOSZKEVM",
"SEND_TOKENS_ON_CRONOSZK",
"SEND_ETH_ON_CRONOSZKEVM",
"SEND_ETH_ON_CRONOSZK",
"PAY_ON_CRONOSZKEVM",
"PAY_ON_CRONOSZK",
],
validate: async (runtime: IAgentRuntime, message: Memory) => {
await validateCronosZkevmConfig(runtime);
return true;
},
description: "Transfer tokens from the agent's wallet to another address",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting SEND_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,
});

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

try {
const PRIVATE_KEY = runtime.getSetting("CRONOSZKEVM_PRIVATE_KEY")!;
const PUBLIC_KEY = runtime.getSetting("CRONOSZKEVM_ADDRESS")!;

const web3: Web3 = new Web3(/* optional L1 provider */);

web3.registerPlugin(
new ZKsyncPlugin(
new Web3ZKsyncL2("https://mainnet.zkevm.cronos.org")
)
);

const smartAccount = new web3.ZKsync.SmartAccount({
address: PUBLIC_KEY,
secret: "0x" + PRIVATE_KEY,
});

const transferTx = await smartAccount.transfer({
to: content.recipient,
token: content.tokenAddress,
amount: web3.utils.toWei(content.amount, "ether"),
});

const receipt = await transferTx.wait();

elizaLogger.success(
"Transfer completed successfully! tx: " +
receipt.transactionHash
);
if (callback) {
callback({
text:
"Transfer completed successfully! tx: " +
receipt.transactionHash,
content: {},
});
}

return true;
} catch (error) {
elizaLogger.error("Error during token transfer:", error);
if (callback) {
callback({
text: `Error transferring tokens: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},

examples: [
[
{
user: "{{user1}}",
content: {
text: "Send 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62",
},
},
{
user: "{{agent}}",
content: {
text: "Sure, I'll send 100 USDC to that address now.",
action: "SEND_TOKEN",
},
},
{
user: "{{agent}}",
content: {
text: "Successfully sent 100 USDC to 0xCCa8009f5e09F8C5dB63cb0031052F9CB635Af62\nTransaction: 0x4fed598033f0added272c3ddefd4d83a521634a738474400b27378db462a76ec",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Please send 100 ZKCRO tokens to 0xbD8679cf79137042214fA4239b02F4022208EE82",
},
},
{
user: "{{agent}}",
content: {
text: "Of course. Sending 100 ZKCRO to that address now.",
action: "SEND_TOKEN",
},
},
{
user: "{{agent}}",
content: {
text: "Successfully sent 100 ZKCRO to 0xbD8679cf79137042214fA4239b02F4022208EE82\nTransaction: 0x0b9f23e69ea91ba98926744472717960cc7018d35bc3165bdba6ae41670da0f0",
},
},
],
] as ActionExample[][],
} as Action;
38 changes: 38 additions & 0 deletions packages/plugin-cronoszkevm/src/enviroment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { IAgentRuntime } from "@elizaos/eliza";
import { z } from "zod";

export const CronosZkEVMEnvSchema = z.object({
CRONOSZKEVM_ADDRESS: z.string().min(1, "Cronos zkEVM address is required"),
CRONOSZKEVM_PRIVATE_KEY: z
.string()
.min(1, "Cronos zkEVM private key is required"),
});

export type CronoszkEVMConfig = z.infer<typeof CronosZkEVMEnvSchema>;

export async function validateCronosZkevmConfig(
runtime: IAgentRuntime
): Promise<CronoszkEVMConfig> {
try {
const config = {
CRONOSZKEVM_ADDRESS:
runtime.getSetting("CRONOSZKEVM_ADDRESS") ||
process.env.CRONOSZKEVM_ADDRESS,
CRONOSZKEVM_PRIVATE_KEY:
runtime.getSetting("CRONOSZKEVM_PRIVATE_KEY") ||
process.env.CRONOSZKEVM_PRIVATE_KEY,
};

return CronosZkEVMEnvSchema.parse(config);
} catch (error) {
if (error instanceof z.ZodError) {
const errorMessages = error.errors
.map((err) => `${err.path.join(".")}: ${err.message}`)
.join("\n");
throw new Error(
`CronosZkEVM configuration validation failed:\n${errorMessages}`
);
}
throw error;
}
}
13 changes: 13 additions & 0 deletions packages/plugin-cronoszkevm/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Plugin } from "@elizaos/core";

import transfer from "./actions/transfer";

export const cronosZkEVMPlugin: Plugin = {
name: "cronoszkevm",
description: "Cronos zkEVM plugin for Eliza",
actions: [transfer],
evaluators: [],
providers: [],
};

export default cronosZkEVMPlugin;
8 changes: 8 additions & 0 deletions packages/plugin-cronoszkevm/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../core/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}
20 changes: 20 additions & 0 deletions packages/plugin-cronoszkevm/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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"
// Add other modules you want to externalize
],
});
Loading
Loading