From 705cf09569802163d77c53de55628359561828e4 Mon Sep 17 00:00:00 2001 From: Jongwhan Lee <51560997+leejw51crypto@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:10:52 +0900 Subject: [PATCH] feat: Add Cronos Evm (#2585) * 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. * Update packages/plugin-cronos/README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Sayo Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/plugin-cronos/README.md | 257 ++++++++++++++++++ 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, 1005 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..4d07b0cc5d5 --- /dev/null +++ b/packages/plugin-cronos/README.md @@ -0,0 +1,257 @@ +# @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", + "pattern": "^0x[a-fA-F0-9]{40}$" + }, + "amount": { + "type": "string", + "description": "The amount of tokens to transfer", + "pattern": "^[0-9]*(\\.[0-9]+)?$" + } + }, + "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 { + 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 => { + 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 { + 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 => { + 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 = { + 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 { + 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 { + return this.getAddressBalance(this.account.address); + } + + async getAddressBalance(address: Address): Promise { + const cacheKey = `balance_${address}_${this.currentChain}`; + const cachedData = await this.getCachedData(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(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(key: string): Promise { + const cached = await this.cacheManager.get( + path.join(this.cacheKey, key) + ); + return cached; + } + + private async writeToCache(key: string, data: T): Promise { + await this.cacheManager.set(path.join(this.cacheKey, key), data, { + expires: Date.now() + this.CACHE_EXPIRY_SEC * 1000, + }); + } + + private async getCachedData(key: string): Promise { + const cachedData = this.cache.get(key); + if (cachedData) { + return cachedData; + } + + const fileCachedData = await this.readFromCache(key); + if (fileCachedData) { + this.cache.set(key, fileCachedData); + return fileCachedData; + } + + return null; + } + + private async setCachedData(cacheKey: string, data: T): Promise { + 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 { + 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: + + +{{recentMessages}} + + +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 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: + + +{{recentMessages}} + + +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 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; + privateKey: Hex; +} + +export interface CronosProvider { + get(runtime: any, message: any, state?: any): Promise; +} \ 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