From 2895f7b8346aa2ccaa31c58f4a2bef48048aea29 Mon Sep 17 00:00:00 2001
From: jonghwan lee <jonghwan@crypto.com>
Date: Mon, 13 Jan 2025 13:07:33 +0900
Subject: [PATCH 1/2] feat: Add Cronos Evm

- Created `@elizaos/plugin-cronos` with:
  - Support for Cronos Mainnet and Testnet
  - Token transfer functionality
  - Balance checking capability
  - Wallet provider implementation

- `README.md`: Documentation and setup guide
- Action handlers for transfers and balance checks
- Chain configurations and wallet provider
- TypeScript configurations and types

- CRO/TCRO token support
- Environment variable setup for private keys
- Security guidelines for key management
- Comprehensive API documentation

feat: Enhance balance and transfer actions with validation and schema

- Updated `BalanceAction` and `TransferAction` to include Zod validation schemas for parameters.
- Replaced deprecated `generateObjectDeprecated` with `generateObject` for better type safety.
- Improved error handling and logging for balance and transfer operations.
- Added address validation to ensure proper Ethereum address format.
- Updated templates to reflect new parameter requirements for balance checks.
- Refactored wallet provider methods to support fetching balance by address.
---
 packages/plugin-cronos/README.md              | 255 ++++++++++++++++++
 packages/plugin-cronos/package.json           |  33 +++
 packages/plugin-cronos/src/actions/balance.ts | 137 ++++++++++
 .../plugin-cronos/src/actions/transfer.ts     | 173 ++++++++++++
 .../plugin-cronos/src/constants/chains.ts     |  51 ++++
 packages/plugin-cronos/src/index.ts           |  20 ++
 .../plugin-cronos/src/providers/wallet.ts     | 197 ++++++++++++++
 packages/plugin-cronos/src/templates/index.ts |  62 +++++
 packages/plugin-cronos/src/types/index.ts     |  39 +++
 packages/plugin-cronos/tsconfig.json          |  15 ++
 packages/plugin-cronos/tsup.config.ts         |  21 ++
 11 files changed, 1003 insertions(+)
 create mode 100644 packages/plugin-cronos/README.md
 create mode 100644 packages/plugin-cronos/package.json
 create mode 100644 packages/plugin-cronos/src/actions/balance.ts
 create mode 100644 packages/plugin-cronos/src/actions/transfer.ts
 create mode 100644 packages/plugin-cronos/src/constants/chains.ts
 create mode 100644 packages/plugin-cronos/src/index.ts
 create mode 100644 packages/plugin-cronos/src/providers/wallet.ts
 create mode 100644 packages/plugin-cronos/src/templates/index.ts
 create mode 100644 packages/plugin-cronos/src/types/index.ts
 create mode 100644 packages/plugin-cronos/tsconfig.json
 create mode 100644 packages/plugin-cronos/tsup.config.ts

diff --git a/packages/plugin-cronos/README.md b/packages/plugin-cronos/README.md
new file mode 100644
index 00000000000..ff3021f5f30
--- /dev/null
+++ b/packages/plugin-cronos/README.md
@@ -0,0 +1,255 @@
+# @elizaos/plugin-cronos
+
+Cronos plugin for Eliza, extending the EVM plugin functionality.
+
+## Supported Networks
+
+### Mainnet
+- Cronos Mainnet (Chain ID: 25)
+  - RPC Endpoint: https://evm.cronos.org/
+  - Explorer: https://explorer.cronos.org/
+  - Native Token: CRO
+
+### Testnet
+- Cronos Testnet 3 (Chain ID: 338)
+  - RPC Endpoint: https://evm-t3.cronos.org/
+  - Explorer: https://cronos.org/explorer/testnet3
+  - Native Token: TCRO
+
+## Installation
+
+```bash
+pnpm add @elizaos/plugin-cronos
+```
+
+## Usage
+
+### Basic Setup
+```typescript
+import { cronosPlugin } from "@elizaos/plugin-cronos";
+
+// Use the plugin in your Eliza configuration
+const config = {
+    plugins: [cronosPlugin],
+    // ... rest of your config
+};
+```
+
+### Character Configuration Guide
+
+Create a `your-character.character.json` file with the following structure:
+
+```json
+{
+    "name": "YourCharacterName",
+    "plugins": ["@elizaos/plugin-cronos"],
+    "clients": ["telegram"],
+    "modelProvider": "openai",
+    "settings": {
+        "secrets": {},
+        "chains": {
+            "evm": ["cronos", "cronosTestnet"]
+        }
+    },
+    "system": "Primary function is to execute token transfers and check balances on Cronos chain.",
+    "actions": {
+        "SEND_TOKEN": {
+            "enabled": true,
+            "priority": 1,
+            "force": true,
+            "schema": {
+                "type": "object",
+                "properties": {
+                    "fromChain": {
+                        "type": "string",
+                        "description": "The chain to execute the transfer on",
+                        "enum": ["cronos", "cronosTestnet"]
+                    },
+                    "toAddress": {
+                        "type": "string",
+                        "description": "The recipient's wallet address"
+                    },
+                    "amount": {
+                        "type": "string",
+                        "description": "The amount of tokens to transfer"
+                    }
+                },
+                "required": ["fromChain", "toAddress", "amount"]
+            },
+            "triggers": [
+                "send * CRO to *",
+                "transfer * CRO to *"
+            ],
+            "examples": [
+                {
+                    "input": "Send 0.1 CRO to 0x...",
+                    "output": {
+                        "fromChain": "cronos",
+                        "toAddress": "0x...",
+                        "amount": "0.1"
+                    }
+                }
+            ]
+        },
+        "CHECK_BALANCE": {
+            "enabled": true,
+            "priority": 1,
+            "force": true,
+            "schema": {
+                "type": "object",
+                "properties": {
+                    "chain": {
+                        "type": "string",
+                        "description": "The chain to check balance on",
+                        "enum": ["cronos", "cronosTestnet"]
+                    }
+                },
+                "required": ["chain"]
+            },
+            "triggers": [
+                "check balance",
+                "show balance",
+                "what's my balance",
+                "how much CRO do I have",
+                "check balance on *",
+                "show balance on *"
+            ],
+            "examples": [
+                {
+                    "input": "check balance",
+                    "output": {
+                        "chain": "cronos"
+                    }
+                },
+                {
+                    "input": "what's my balance on testnet",
+                    "output": {
+                        "chain": "cronosTestnet"
+                    }
+                }
+            ]
+        }
+    },
+    "messageExamples": [
+        [
+            {
+                "user": "{{user1}}",
+                "content": {
+                    "text": "Send 100 CRO to 0x..."
+                }
+            },
+            {
+                "user": "YourCharacterName",
+                "content": {
+                    "text": "Processing token transfer...",
+                    "action": "SEND_TOKEN"
+                }
+            }
+        ],
+        [
+            {
+                "user": "{{user1}}",
+                "content": {
+                    "text": "What's my balance?"
+                }
+            },
+            {
+                "user": "YourCharacterName",
+                "content": {
+                    "text": "Checking your balance...",
+                    "action": "CHECK_BALANCE"
+                }
+            }
+        ]
+    ]
+}
+```
+
+#### Key Configuration Fields:
+
+1. **Basic Setup**
+   - `name`: Your character's name
+   - `plugins`: Include `@elizaos/plugin-cronos`
+   - `clients`: Supported client platforms
+
+2. **Chain Settings**
+   - Configure both mainnet and testnet in `settings.chains.evm`
+   - Available options: `"cronos"` (mainnet) and `"cronosTestnet"`
+
+3. **Action Configuration**
+   - `SEND_TOKEN`: Action for token transfers
+   - `CHECK_BALANCE`: Action for checking wallet balance
+   - `schema`: Defines the required parameters for each action
+   - `triggers`: Phrases that activate the actions
+   - `examples`: Sample inputs and outputs
+
+4. **Message Examples**
+   - Provide example interactions
+   - Show how actions are triggered
+   - Demonstrate expected responses
+
+### Action Examples
+```
+// Send tokens on mainnet
+"Send 0.1 CRO to 0x..." use mainnet
+
+// Send tokens on testnet
+"Send 0.1 TCRO to 0x..." use testnet
+
+// Check balance on mainnet
+"check balance"
+"what's my balance"
+"how much CRO do I have"
+
+// Check balance on testnet
+"check balance on testnet"
+"what's my balance on testnet"
+```
+
+## Features
+
+- All standard EVM functionality inherited from @elizaos/plugin-evm
+- Preconfigured for both Cronos Mainnet and Testnet
+- Native CRO/TCRO token support
+- Automated token transfer actions
+- Balance checking functionality
+- Built-in chain configuration
+
+## Environment Variables
+
+Required environment variable for transactions:
+
+```env
+# Wallet private key (Required, must start with 0x)
+CRONOS_PRIVATE_KEY=0x...
+```
+
+### Security Warnings ⚠️
+
+- **NEVER** commit private keys to version control
+- **NEVER** share private keys with anyone
+- **ALWAYS** use environment variables or secure key management
+- Use separate keys for mainnet and testnet
+- Monitor your wallet for unauthorized transactions
+
+### Setup
+
+1. Create `.env` file:
+```env
+CRONOS_PRIVATE_KEY=0x...  # Mainnet
+```
+
+2. For testnet development, use `.env.local`:
+```env
+CRONOS_PRIVATE_KEY=0x...  # Testnet only
+```
+
+3. Add to `.gitignore`:
+```
+.env
+.env.*
+```
+
+## License
+
+MIT
\ No newline at end of file
diff --git a/packages/plugin-cronos/package.json b/packages/plugin-cronos/package.json
new file mode 100644
index 00000000000..855c087c191
--- /dev/null
+++ b/packages/plugin-cronos/package.json
@@ -0,0 +1,33 @@
+{
+    "name": "@elizaos/plugin-cronos",
+    "version": "0.0.1",
+    "type": "module",
+    "main": "dist/index.js",
+    "module": "dist/index.js",
+    "types": "dist/index.d.ts",
+    "exports": {
+        "./package.json": "./package.json",
+        ".": {
+            "import": {
+                "@elizaos/source": "./src/index.ts",
+                "types": "./dist/index.d.ts",
+                "default": "./dist/index.js"
+            }
+        }
+    },
+    "files": [
+        "dist"
+    ],
+    "scripts": {
+        "build": "tsup --format esm --dts"
+    },
+    "dependencies": {
+        "@elizaos/core": "workspace:*",
+        "node-cache": "^5.1.2",
+        "viem": "^2.0.0"
+    },
+    "devDependencies": {
+        "tsup": "^8.0.1",
+        "typescript": "^5.3.3"
+    }
+}
\ No newline at end of file
diff --git a/packages/plugin-cronos/src/actions/balance.ts b/packages/plugin-cronos/src/actions/balance.ts
new file mode 100644
index 00000000000..fb09c732e91
--- /dev/null
+++ b/packages/plugin-cronos/src/actions/balance.ts
@@ -0,0 +1,137 @@
+import {
+    Action,
+    composeContext,
+    generateObject,
+    HandlerCallback,
+    ModelClass,
+    type IAgentRuntime,
+    type Memory,
+    type State,
+} from "@elizaos/core";
+import { z } from "zod";
+import { isAddress } from "viem";
+
+import { CronosWalletProvider, initCronosWalletProvider } from "../providers/wallet";
+import type { BalanceParams } from "../types";
+import { balanceTemplate } from "../templates";
+
+const BalanceSchema = z.object({
+    chain: z.enum(["cronos", "cronosTestnet"], {
+        required_error: "Chain must be either cronos or cronosTestnet",
+        invalid_type_error: "Chain must be either cronos or cronosTestnet",
+    }),
+    address: z.string().refine((val) => isAddress(val), {
+        message: "Invalid Ethereum address format",
+    }),
+});
+
+export class BalanceAction {
+    constructor(private walletProvider: CronosWalletProvider) {}
+
+    async getBalance(params: BalanceParams): Promise<string> {
+        this.walletProvider.switchChain(params.chain);
+        const balance = await this.walletProvider.getAddressBalance(params.address);
+
+        if (!balance) {
+            throw new Error("Failed to fetch balance");
+        }
+
+        return balance;
+    }
+}
+
+const buildBalanceDetails = async (
+    state: State,
+    runtime: IAgentRuntime,
+    wp: CronosWalletProvider
+): Promise<BalanceParams> => {
+    state.supportedChains = '"cronos"|"cronosTestnet"';
+
+    const context = composeContext({
+        state,
+        template: balanceTemplate,
+    });
+
+    const balanceDetails = (await generateObject({
+        runtime,
+        context,
+        modelClass: ModelClass.SMALL,
+        schema: BalanceSchema,
+    })).object as BalanceParams;
+
+    return balanceDetails;
+};
+
+export const balanceAction: Action = {
+    name: "CHECK_BALANCE",
+    description: "Check CRO token balance on Cronos chain",
+    handler: async (
+        runtime: IAgentRuntime,
+        message: Memory,
+        state: State | undefined,
+        _options: any,
+        callback?: HandlerCallback
+    ) => {
+        if (!state) {
+            state = (await runtime.composeState(message)) as State;
+        } else {
+            state = await runtime.updateRecentMessageState(state);
+        }
+
+        const walletProvider = await initCronosWalletProvider(runtime);
+        const action = new BalanceAction(walletProvider);
+
+        const paramOptions = await buildBalanceDetails(
+            state,
+            runtime,
+            walletProvider
+        );
+
+        try {
+            const balance = await action.getBalance(paramOptions);
+            if (callback) {
+                callback({
+                    text: `Balance for ${paramOptions.address} on ${paramOptions.chain} is ${balance} CRO`,
+                    content: {
+                        success: true,
+                        balance,
+                        chain: paramOptions.chain,
+                        address: paramOptions.address,
+                    },
+                });
+            }
+            return true;
+        } catch (error) {
+            if (callback) {
+                callback({
+                    text: `Error checking balance: ${error.message}`,
+                    content: { error: error.message },
+                });
+            }
+            return false;
+        }
+    },
+    validate: async (runtime: IAgentRuntime) => {
+        const privateKey = runtime.getSetting("CRONOS_PRIVATE_KEY");
+        return typeof privateKey === "string" && privateKey.startsWith("0x");
+    },
+    examples: [
+        [
+            {
+                user: "assistant",
+                content: {
+                    text: "I'll check your balance on Cronos mainnet",
+                    action: "CHECK_BALANCE",
+                },
+            },
+            {
+                user: "user",
+                content: {
+                    text: "What's my balance?",
+                    action: "CHECK_BALANCE",
+                },
+            },
+        ],
+    ],
+    similes: ["balance", "CHECK_BALANCE", "GET_BALANCE", "SHOW_BALANCE"],
+};
\ No newline at end of file
diff --git a/packages/plugin-cronos/src/actions/transfer.ts b/packages/plugin-cronos/src/actions/transfer.ts
new file mode 100644
index 00000000000..f9a11150d76
--- /dev/null
+++ b/packages/plugin-cronos/src/actions/transfer.ts
@@ -0,0 +1,173 @@
+import { ByteArray, formatEther, parseEther, type Hex, isAddress } from "viem";
+import {
+    Action,
+    composeContext,
+    generateObject,
+    HandlerCallback,
+    ModelClass,
+    type IAgentRuntime,
+    type Memory,
+    type State,
+} from "@elizaos/core";
+import { z } from "zod";
+
+import { CronosWalletProvider, initCronosWalletProvider } from "../providers/wallet";
+import type { Transaction, TransferParams } from "../types";
+import { transferTemplate } from "../templates";
+import { cronos, cronosTestnet } from "../constants/chains";
+
+const TransferSchema = z.object({
+    fromChain: z.enum(["cronos", "cronosTestnet"]),
+    toAddress: z.string().refine((val) => isAddress(val), {
+        message: "Invalid Ethereum address",
+    }),
+    amount: z.string().refine((val) => {
+        try {
+            parseEther(val);
+            return true;
+        } catch {
+            return false;
+        }
+    }, {
+        message: "Invalid amount format",
+    }),
+    data: z.string().optional(),
+});
+
+export class TransferAction {
+    constructor(private walletProvider: CronosWalletProvider) {}
+
+    async transfer(params: TransferParams): Promise<Transaction> {
+        if (!params.data) {
+            params.data = "0x";
+        }
+
+        this.walletProvider.switchChain(params.fromChain);
+        const walletClient = this.walletProvider.getWalletClient(params.fromChain);
+        const chainConfig = params.fromChain === "cronos" ? cronos : cronosTestnet;
+
+        try {
+            const hash = await walletClient.sendTransaction({
+                account: walletClient.account,
+                to: params.toAddress as Hex,
+                value: parseEther(params.amount),
+                data: params.data as Hex,
+                chain: chainConfig,
+                gasPrice: undefined,
+                maxFeePerGas: undefined,
+                maxPriorityFeePerGas: undefined,
+                maxFeePerBlobGas: undefined,
+                blobs: undefined,
+                kzg: undefined,
+            });
+
+            return {
+                hash,
+                from: walletClient.account.address,
+                to: params.toAddress,
+                value: parseEther(params.amount),
+                data: params.data as Hex,
+                chainId: chainConfig.id,
+            };
+        } catch (error) {
+            throw new Error(`Transfer failed: ${error.message}`);
+        }
+    }
+}
+
+const buildTransferDetails = async (
+    state: State,
+    runtime: IAgentRuntime,
+    wp: CronosWalletProvider
+): Promise<TransferParams> => {
+    state.supportedChains = '"cronos"|"cronosTestnet"';
+
+    const context = composeContext({
+        state,
+        template: transferTemplate,
+    });
+
+    const transferDetails = (await generateObject({
+        runtime,
+        context,
+        modelClass: ModelClass.SMALL,
+        schema: TransferSchema,
+    })).object as TransferParams;
+
+    return transferDetails;
+};
+
+export const transferAction: Action = {
+    name: "SEND_TOKENS",
+    description: "Transfer CRO tokens on Cronos chain",
+    handler: async (
+        runtime: IAgentRuntime,
+        message: Memory,
+        state: State | undefined,
+        _options: any,
+        callback?: HandlerCallback
+    ) => {
+        if (!state) {
+            state = (await runtime.composeState(message)) as State;
+        } else {
+            state = await runtime.updateRecentMessageState(state);
+        }
+
+        const walletProvider = await initCronosWalletProvider(runtime);
+        const action = new TransferAction(walletProvider);
+
+        const paramOptions = await buildTransferDetails(
+            state,
+            runtime,
+            walletProvider
+        );
+
+        try {
+            const transferResp = await action.transfer(paramOptions);
+            if (callback) {
+                callback({
+                    text: `Successfully transferred ${paramOptions.amount} CRO to ${paramOptions.toAddress}\nTransaction Hash: ${transferResp.hash}`,
+                    content: {
+                        success: true,
+                        hash: transferResp.hash,
+                        amount: formatEther(transferResp.value),
+                        recipient: transferResp.to,
+                        chain: paramOptions.fromChain,
+                    },
+                });
+            }
+            return true;
+        } catch (error) {
+            if (callback) {
+                callback({
+                    text: `Error transferring tokens: ${error.message}`,
+                    content: { error: error.message },
+                });
+            }
+            return false;
+        }
+    },
+    validate: async (runtime: IAgentRuntime) => {
+        const privateKey = runtime.getSetting("CRONOS_PRIVATE_KEY");
+        return typeof privateKey === "string" && privateKey.startsWith("0x");
+    },
+    examples: [
+        [
+            {
+                user: "assistant",
+                content: {
+                    text: "I'll help you transfer 1 CRO to 0x000000000000000000000000000000000000800A on Cronos Testnet",
+                    action: "SEND_TOKENS",
+                },
+            },
+            {
+                user: "user",
+                content: {
+                    text: "Transfer 1 CRO to 0x000000000000000000000000000000000000800A on Cronos Testnet",
+                    action: "SEND_TOKENS",
+                },
+            },
+        ],
+    ],
+    similes: ["transfer", "SEND_TOKENS", "TOKEN_TRANSFER", "MOVE_TOKENS"],
+};
\ No newline at end of file
diff --git a/packages/plugin-cronos/src/constants/chains.ts b/packages/plugin-cronos/src/constants/chains.ts
new file mode 100644
index 00000000000..152e6c2e3ec
--- /dev/null
+++ b/packages/plugin-cronos/src/constants/chains.ts
@@ -0,0 +1,51 @@
+import { defineChain } from "viem";
+
+export const cronos = defineChain({
+    id: 25,
+    name: "Cronos Mainnet",
+    nativeCurrency: {
+        decimals: 18,
+        name: "cronos",
+        symbol: "CRO",
+    },
+    rpcUrls: {
+        default: {
+            http: ["https://evm.cronos.org/"],
+        },
+        public: {
+            http: ["https://evm.cronos.org/"],
+        },
+    },
+    blockExplorers: {
+        default: {
+            name: "Cronos Explorer",
+            url: "https://explorer.cronos.org/",
+        },
+    },
+    testnet: false,
+});
+
+export const cronosTestnet = defineChain({
+    id: 338,
+    name: "cronos-testnet",
+    nativeCurrency: {
+        decimals: 18,
+        name: "Cronos",
+        symbol: "TCRO",
+    },
+    rpcUrls: {
+        default: {
+            http: ["https://evm-t3.cronos.org/"],
+        },
+        public: {
+            http: ["https://evm-t3.cronos.org/"],
+        },
+    },
+    blockExplorers: {
+        default: {
+            name: "Cronos Explorer",
+            url: "https://cronos.org/explorer/testnet3",
+        },
+    },
+    testnet: true,
+});
\ No newline at end of file
diff --git a/packages/plugin-cronos/src/index.ts b/packages/plugin-cronos/src/index.ts
new file mode 100644
index 00000000000..d2e2cdc6c43
--- /dev/null
+++ b/packages/plugin-cronos/src/index.ts
@@ -0,0 +1,20 @@
+export * from "./actions/transfer";
+export * from "./actions/balance";
+export * from "./providers/wallet";
+export * from "./types";
+
+import type { Plugin } from "@elizaos/core";
+import { transferAction } from "./actions/transfer";
+import { balanceAction } from "./actions/balance";
+import { cronosWalletProvider } from "./providers/wallet";
+
+export const cronosPlugin: Plugin = {
+    name: "cronos",
+    description: "Cronos chain integration plugin",
+    providers: [cronosWalletProvider],
+    evaluators: [],
+    services: [],
+    actions: [transferAction, balanceAction],
+};
+
+export default cronosPlugin;
\ No newline at end of file
diff --git a/packages/plugin-cronos/src/providers/wallet.ts b/packages/plugin-cronos/src/providers/wallet.ts
new file mode 100644
index 00000000000..a67a3f73f34
--- /dev/null
+++ b/packages/plugin-cronos/src/providers/wallet.ts
@@ -0,0 +1,197 @@
+import {
+    createPublicClient,
+    createWalletClient,
+    formatUnits,
+    http,
+    type Address,
+    type WalletClient,
+    type PublicClient,
+    type Chain,
+    type HttpTransport,
+    type Account,
+    type PrivateKeyAccount,
+} from "viem";
+import { privateKeyToAccount } from "viem/accounts";
+import {
+    type IAgentRuntime,
+    type Memory,
+    type State,
+    type ICacheManager,
+    elizaLogger,
+} from "@elizaos/core";
+import NodeCache from "node-cache";
+import * as path from "path";
+
+import { cronos, cronosTestnet } from "../constants/chains";
+import type { CronosChain, CronosProvider } from "../types";
+
+export class CronosWalletProvider {
+    private cache: NodeCache;
+    private cacheKey: string = "cronos/wallet";
+    private currentChain: CronosChain = "cronos";
+    private CACHE_EXPIRY_SEC = 5;
+    chains: Record<CronosChain, Chain> = {
+        cronos,
+        cronosTestnet,
+    };
+    account: PrivateKeyAccount;
+
+    constructor(
+        accountOrPrivateKey: PrivateKeyAccount | `0x${string}`,
+        private cacheManager: ICacheManager
+    ) {
+        this.setAccount(accountOrPrivateKey);
+        this.cache = new NodeCache({ stdTTL: this.CACHE_EXPIRY_SEC });
+    }
+
+    getAddress(): Address {
+        return this.account.address;
+    }
+
+    getCurrentChain(): Chain {
+        return this.chains[this.currentChain];
+    }
+
+    getPublicClient(
+        chainName: CronosChain
+    ): PublicClient<HttpTransport, Chain, Account | undefined> {
+        const transport = this.createHttpTransport(chainName);
+
+        const publicClient = createPublicClient({
+            chain: this.chains[chainName],
+            transport,
+        });
+        return publicClient;
+    }
+
+    getWalletClient(chainName: CronosChain): WalletClient {
+        const transport = this.createHttpTransport(chainName);
+
+        const walletClient = createWalletClient({
+            chain: this.chains[chainName],
+            transport,
+            account: this.account,
+        });
+
+        return walletClient;
+    }
+
+    async getWalletBalance(): Promise<string | null> {
+        return this.getAddressBalance(this.account.address);
+    }
+
+    async getAddressBalance(address: Address): Promise<string | null> {
+        const cacheKey = `balance_${address}_${this.currentChain}`;
+        const cachedData = await this.getCachedData<string>(cacheKey);
+        if (cachedData) {
+            elizaLogger.log(
+                `Returning cached balance for address ${address} on chain: ${this.currentChain}`
+            );
+            return cachedData;
+        }
+
+        try {
+            const client = this.getPublicClient(this.currentChain);
+            const balance = await client.getBalance({
+                address,
+            });
+            const balanceFormatted = formatUnits(balance, 18);
+            this.setCachedData<string>(cacheKey, balanceFormatted);
+            elizaLogger.log(
+                `Balance cached for address ${address} on chain: ${this.currentChain}`
+            );
+            return balanceFormatted;
+        } catch (error) {
+            console.error(`Error getting balance for address ${address}:`, error);
+            return null;
+        }
+    }
+
+    switchChain(chainName: CronosChain) {
+        if (!this.chains[chainName]) {
+            throw new Error(`Invalid Cronos chain: ${chainName}`);
+        }
+        this.currentChain = chainName;
+    }
+
+    private async readFromCache<T>(key: string): Promise<T | null> {
+        const cached = await this.cacheManager.get<T>(
+            path.join(this.cacheKey, key)
+        );
+        return cached;
+    }
+
+    private async writeToCache<T>(key: string, data: T): Promise<void> {
+        await this.cacheManager.set(path.join(this.cacheKey, key), data, {
+            expires: Date.now() + this.CACHE_EXPIRY_SEC * 1000,
+        });
+    }
+
+    private async getCachedData<T>(key: string): Promise<T | null> {
+        const cachedData = this.cache.get<T>(key);
+        if (cachedData) {
+            return cachedData;
+        }
+
+        const fileCachedData = await this.readFromCache<T>(key);
+        if (fileCachedData) {
+            this.cache.set(key, fileCachedData);
+            return fileCachedData;
+        }
+
+        return null;
+    }
+
+    private async setCachedData<T>(cacheKey: string, data: T): Promise<void> {
+        this.cache.set(cacheKey, data);
+        await this.writeToCache(cacheKey, data);
+    }
+
+    private setAccount = (
+        accountOrPrivateKey: PrivateKeyAccount | `0x${string}`
+    ) => {
+        if (typeof accountOrPrivateKey === "string") {
+            this.account = privateKeyToAccount(accountOrPrivateKey);
+        } else {
+            this.account = accountOrPrivateKey;
+        }
+    };
+
+    private createHttpTransport = (chainName: CronosChain) => {
+        const chain = this.chains[chainName];
+        return http(chain.rpcUrls.default.http[0]);
+    };
+}
+
+export const initCronosWalletProvider = async (runtime: IAgentRuntime) => {
+    const privateKey = runtime.getSetting("CRONOS_PRIVATE_KEY") as `0x${string}`;
+    if (!privateKey) {
+        throw new Error("CRONOS_PRIVATE_KEY is missing");
+    }
+    return new CronosWalletProvider(privateKey, runtime.cacheManager);
+};
+
+export const cronosWalletProvider: CronosProvider = {
+    async get(
+        runtime: IAgentRuntime,
+        _message: Memory,
+        state?: State
+    ): Promise<string | null> {
+        try {
+            const walletProvider = await initCronosWalletProvider(runtime);
+            const address = walletProvider.getAddress();
+            const balance = await walletProvider.getWalletBalance();
+            const chain = walletProvider.getCurrentChain();
+            const agentName = state?.agentName || "The agent";
+
+            return `${agentName}'s Cronos Wallet:
+Address: ${address}
+Balance: ${balance} ${chain.nativeCurrency.symbol}
+Chain: ${chain.name} (ID: ${chain.id})
+RPC: ${chain.rpcUrls.default.http[0]}`;
+        } catch (error) {
+            console.error("Error in Cronos wallet provider:", error);
+            return null;
+        }
+    },
+};
\ No newline at end of file
diff --git a/packages/plugin-cronos/src/templates/index.ts b/packages/plugin-cronos/src/templates/index.ts
new file mode 100644
index 00000000000..2f3de9ed1b5
--- /dev/null
+++ b/packages/plugin-cronos/src/templates/index.ts
@@ -0,0 +1,62 @@
+export const transferTemplate = `You are a helpful assistant that helps users transfer CRO tokens on the Cronos chain.
+
+First, review the recent messages from the conversation:
+
+<recent_messages>
+{{recentMessages}}
+</recent_messages>
+
+Current context:
+- Available chains: {{supportedChains}}
+
+Based on the context above, please provide the following transfer details in JSON format:
+{
+    "fromChain": "cronos" | "cronosTestnet",
+    "toAddress": "string (the recipient's address)",
+    "amount": "string (the amount of CRO to transfer)"
+}
+
+Before providing the final JSON output, show your reasoning process inside <analysis> tags:
+1. Identify the chain, amount, and recipient address from the messages
+2. Validate that:
+   - The chain is either "cronos" or "cronosTestnet"
+   - The address is a valid Ethereum-style address (0x...)
+   - The amount is a positive number
+
+Remember:
+- The chain name must be exactly "cronos" or "cronosTestnet"
+- The amount should be a string representing the number without any currency symbol
+- The recipient address must be a valid Ethereum address starting with "0x"
+
+Now, process the user's request and provide your response.`;
+
+export const balanceTemplate = `You are a helpful assistant that helps users check their CRO token balance on the Cronos chain.
+
+First, review the recent messages from the conversation:
+
+<recent_messages>
+{{recentMessages}}
+</recent_messages>
+
+Current context:
+- Available chains: {{supportedChains}}
+
+Based on the context above, please provide the following balance check details in JSON format:
+{
+    "chain": "cronos" | "cronosTestnet",
+    "address": "string (the address to check balance for)"
+}
+
+Before providing the final JSON output, show your reasoning process inside <analysis> tags:
+1. Identify which chain to check the balance on from the messages
+2. Identify the address to check balance for (if not specified, use the user's own address)
+3. Validate that:
+   - The chain is either "cronos" or "cronosTestnet"
+   - The address is a valid Ethereum-style address (0x...)
+
+Remember:
+- The chain name must be exactly "cronos" or "cronosTestnet"
+- If no specific chain is mentioned, default to "cronos"
+- The address must be a valid Ethereum address starting with "0x"
+
+Now, process the user's request and provide your response.`;
\ No newline at end of file
diff --git a/packages/plugin-cronos/src/types/index.ts b/packages/plugin-cronos/src/types/index.ts
new file mode 100644
index 00000000000..31c7a1806f5
--- /dev/null
+++ b/packages/plugin-cronos/src/types/index.ts
@@ -0,0 +1,39 @@
+import type { Hex, Chain } from "viem";
+import { z } from "zod";
+
+export type CronosChain = "cronos" | "cronosTestnet";
+
+export interface Transaction {
+    hash: Hex;
+    from: Hex;
+    to: Hex;
+    value: bigint;
+    data: Hex;
+    chainId?: number;
+}
+
+export interface TransferParams {
+    fromChain: CronosChain;
+    toAddress: Hex;
+    amount: string;
+    data?: Hex;
+}
+
+export const BalanceParamsSchema = z.object({
+    chain: z.enum(["cronos", "cronosTestnet"] as const),
+    address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format"),
+});
+
+export interface BalanceParams {
+    chain: CronosChain;
+    address: Hex;
+}
+
+export interface WalletConfig {
+    chains: Record<CronosChain, Chain>;
+    privateKey: Hex;
+}
+
+export interface CronosProvider {
+    get(runtime: any, message: any, state?: any): Promise<string | null>;
+}
\ No newline at end of file
diff --git a/packages/plugin-cronos/tsconfig.json b/packages/plugin-cronos/tsconfig.json
new file mode 100644
index 00000000000..311072f2822
--- /dev/null
+++ b/packages/plugin-cronos/tsconfig.json
@@ -0,0 +1,15 @@
+{
+    "extends": "../core/tsconfig.json",
+    "compilerOptions": {
+        "outDir": "dist",
+        "rootDir": "src",
+        "moduleResolution": "Bundler",
+        "module": "ESNext",
+        "target": "ESNext",
+        "declaration": true,
+        "emitDeclarationOnly": true
+    },
+    "include": [
+        "src/**/*.ts"
+    ]
+}
\ No newline at end of file
diff --git a/packages/plugin-cronos/tsup.config.ts b/packages/plugin-cronos/tsup.config.ts
new file mode 100644
index 00000000000..eb3d0007f9e
--- /dev/null
+++ b/packages/plugin-cronos/tsup.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+    entry: ["src/index.ts"],
+    outDir: "dist",
+    sourcemap: true,
+    clean: true,
+    format: ["esm"],
+    external: [
+        "dotenv",
+        "fs",
+        "path",
+        "@reflink/reflink",
+        "@node-llama-cpp",
+        "https",
+        "http",
+        "agentkeepalive",
+        "viem",
+        "@elizaos/core"
+    ],
+});
\ No newline at end of file

From 40245f59ead5a25c0c5e93fbe2a24b25e4b03bc4 Mon Sep 17 00:00:00 2001
From: Sayo <hi@sayo.wtf>
Date: Tue, 21 Jan 2025 16:15:22 +0530
Subject: [PATCH 2/2] Update packages/plugin-cronos/README.md

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
 packages/plugin-cronos/README.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/packages/plugin-cronos/README.md b/packages/plugin-cronos/README.md
index ff3021f5f30..4d07b0cc5d5 100644
--- a/packages/plugin-cronos/README.md
+++ b/packages/plugin-cronos/README.md
@@ -67,11 +67,13 @@ Create a `your-character.character.json` file with the following structure:
                     },
                     "toAddress": {
                         "type": "string",
-                        "description": "The recipient's wallet address"
+                        "description": "The recipient's wallet address",
+                        "pattern": "^0x[a-fA-F0-9]{40}$"
                     },
                     "amount": {
                         "type": "string",
-                        "description": "The amount of tokens to transfer"
+                        "description": "The amount of tokens to transfer",
+                        "pattern": "^[0-9]*(\\.[0-9]+)?$"
                     }
                 },
                 "required": ["fromChain", "toAddress", "amount"]