Skip to content

Commit

Permalink
Merge pull request #1464 from elizaOS/thomas/cronos-zkevm
Browse files Browse the repository at this point in the history
feat: Adding plugin for Cronos ZKEVM
  • Loading branch information
shakkernerd authored Dec 26, 2024
2 parents 5b3385c + e720c67 commit 99106da
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 5 deletions.
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

0 comments on commit 99106da

Please sign in to comment.