All

ElizaOS Plugin Action for Deploying Contracts on Neon EVM: A Developer’s Tutorial

ElizaOS Plugin Action for Deploying Contracts on Neon EVM: A Developer’s Tutorial
Written byYulia Petukhova
Published on14 Apr 2025

This is a follow-up to our Tutorial on building an ElizaOS plugin for token transfers on Neon EVM. If you haven't checked it out yet, we recommend starting there.


In this tutorial, you will learn how to build a plugin action for ElizaOS that deploys a smart contract to the Neon EVM network. We’ll walk through the following:

  • Project setup and prerequisites
  • Understanding and configuring the deployment action
  • Writing the deployment code
  • Testing your deployed smart contract

Let’s get started!


Prerequisites

Before you start, ensure you have the following installed and set up:

  • Node.js 23 (or later)
  • pnpm package manager
  • TypeScript knowledge
  • Access to a Neon EVM RPC URL (e.g., from chainlist.org)
  • Your smart contract’s ABI and bytecode (replace the sample paths/values with your actual contract details)
  • Familiarity with ElizaOS and Neon EVM fundamentals (start here!)


Step 1: Set Up Your Project

1.1 Clone the Starter Repository

If you haven’t already, clone your plugin’s starter repository:

git clone https://github.com/tmc/eliza-plugin-starter
cd eliza-plugin-starter
pnpm install


1.2 Open the Project in Your Code Editor

Launch your preferred code editor and open the project directory.


1.3 Configure Environment Variables

Duplicate the example environment file and add your Neon-specific keys:

cp .env.example .env


In your .env file, add variables such as NEONADDRESS_, _NEONPRIVATEKEY_, _NEONRPCURL_, and your _OPENAIKEY.


Step 2: Understand the Deployment Action Code

Below is the complete code for a deployment action. Let’s break down the key parts:


2.1 Imports and Setup

The file begins by importing core types and helper functions from @elizaos/core along with blockchain-specific libraries like viem and zod.

For example:

import type { Action } from "@elizaos/core";
import { Content, HandlerCallback, IAgentRuntime, Memory, ModelClass, State, elizaLogger, composeContext, generateObject } from "@elizaos/core";
import { z } from "zod";
import { neonDevnet } from "viem/chains";
import { useGetAccount, useGetWalletClient } from "../hooks";


Also, import your contract’s ABI and bytecode:

import sampleContractABI from "../utils/sampleContractABI.json";
import { wagmiContract } from "../utils/contract";


2.2 Schema Definition

Using zod, we define a schema to validate any deployment parameters (e.g., constructor arguments):

const DeployContractSchema = z.object({
    constructorArgs: z.array(z.any()).optional(),
});


2.3 Helper Function: Padding Bytecode

To ensure your smart contract bytecode meets Neon EVM’s requirements (divisible by 32 bytes and with an odd word count), a helper function is provided:

Code


2.3 Helper Function: Padding Bytecode

To ensure your smart contract bytecode meets Neon EVM’s requirements (divisible by 32 bytes and with an odd word count), a helper function is provided:


function padBytecode(hex: string): `0x${string}` {
    if (hex.startsWith("0x")) {
        hex = hex.slice(2);
    }
    const remainder = hex.length % 64;
    if (remainder !== 0) {
        const paddingLength = 64 - remainder;
        hex = hex + "0".repeat(paddingLength);
    }
    const wordCount = hex.length / 64;
    if (wordCount % 2 === 0) {
        hex = hex + "0".repeat(64);
    }
    return ("0x" + hex) as `0x${string}`;
}


2.4 Deployment Template

A prompt template is set up to ask the language model (LLM) for constructor arguments:

const deployTemplate = `
Please provide the constructor arguments for deploying the smart contract as JSON.
If no arguments are required, respond with an empty array.

Example response:
\`\`\`json
{
  "constructorArgs": ["arg1", 42]
}
\`\`\`

{{recentMessages}}
`;


2.5 Deploy Action Handler

The core of the deployment logic is within the _handler_ method of the action. Key steps include:

a. State Management:

Update the current conversation state using ElizaOS helpers.

if (!state) {
    state = (await runtime.composeState(message)) as State;
} else {
    state = await runtime.updateRecentMessageState(state);
}


b. Compose Deployment Context:

Generate a context with the prompt template to ask for deployment parameters:

const deployContext = composeContext({ state, template: deployTemplate });


c. Generate Deployment Parameters:

Use the generateObject function to parse user input based on the DeployContractSchema:

const content = (await generateObject({
    runtime,
    context: deployContext,
    modelClass: ModelClass.SMALL,
    schema: DeployContractSchema,
})).object as DeployContractContent;


d. Retrieve Wallet Information:

Get the user’s account and wallet client:

const account = useGetAccount(runtime);
const walletClient = useGetWalletClient();


e. Deploy the Contract:

Call the wallet client’s deployContract method with the contract’s ABI, padded bytecode, and Neon devnet configuration:

const deploymentResult = await walletClient.deployContract({
    abi: wagmiContract.abi,
    account,
    bytecode: padBytecode(wagmiContract.bytecode),
    chain: neonDevnet,
    kzg: {} as any,
});


f. Logging and Callback: Log the successful deployment and use the callback to notify the conversation with the deployed contract address:

const deployedAddress = deploymentResult;
elizaLogger.success(`Contract deployed successfully at address: ${deployedAddress}`);
if (callback) {
    callback({
        text: `Contract deployed successfully! Address: ${deployedAddress}`,
        content: { deployedAddress },
    });
}


g. Error Handling:

If any error occurs during deployment, log the error and send an appropriate callback message.


Step 3: Implement the Deployment Action

3.1 Create the Deployment File

Create a new file in your project (e.g., deployContract.ts) and paste the complete code snippet provided above.


3.2 Customize the Code

  • Replace the sample contract ABI and bytecode with your own smart contract files.
  • Adjust any additional deployment parameters as needed.


3.3 Integrate with ElizaOS

Ensure your new action is registered with your ElizaOS agent so it can listen to deployment commands. Update your plugin configuration if necessary.


Step 4: Testing Your Deployment

4.1 Run the Eliza Client

Start your Eliza client:

pnpm start:client

This should launch the client (usually on a localhost URL).

Start Eliza backend:

pnpm build
pnpm start


4.2 Initiate a Deployment

Send a command to deploy a contract (for example: “Deploy a new smart contract”).

The agent will prompt for constructor arguments. Provide the arguments in the expected JSON format:

{
  "constructorArgs": ["arg1", 42]
}


4.3 Review Deployment Logs

Monitor the logs to confirm that the deployment process starts and finishes. The log should display:

  • A message indicating that deployment has started.
  • A success message with the deployed contract address.
  • In case of errors, review the error messages for troubleshooting.


Conclusion and Next Steps

Congratulations—you’ve just built an action that deploys a smart contract on Neon EVM via ElizaOS! This opens up even more possibilities for AI-driven blockchain interactions on Solana through Neon's EVM compatibility layer. Here are some ideas for further development:

  • Event Listeners: Add listeners to watch for transaction confirmations or contract events to build more robust automation workflows.
  • Enhanced Validation: Introduce additional Neon-specific validation logic in the validate method to further secure and streamline the deployment process.
  • Expand Functionality: Create additional actions (e.g., token transfers, contract calls) to expand the scope of your Neon EVM plugin.
  • Security Considerations: Store your private keys securely and consider the autonomy levels granted to your AI agents.


Your Turn

You’re now set to integrate AI-driven actions into your decentralized applications on Neon EVM. Don’t forget to share your progress with us, we can’t wait to see what you build next.

  • Upload a README to your own GitHub plugin repo.
  • Contribute docs or tutorials back to the ElizaOS and Neon EVM communities. Tag us on X!
  • Write about your experiences on dev blogs (Dev.to, Medium, etc.).

Got questions, feedback, or just wanna vibe? Our Discord is the place to be.

Happy building!


Appendix

Here you can find the full action code:


// A new action to deploy a smart contract on Neon EVM
import type { Action } from "@elizaos/core";
import {
    Content,
    HandlerCallback,
    IAgentRuntime,
    Memory,
    ModelClass,
    State,
    elizaLogger,
    composeContext,
    generateObject,
} from "@elizaos/core";
import { z } from "zod";
import { neonDevnet } from "viem/chains";
import { useGetAccount, useGetWalletClient } from "../hooks";

// Import your contract's ABI and bytecode
// Replace these with the actual paths or values for your smart contract
import sampleContractABI from "../utils/sampleContractABI.json";
import { wagmiContract } from "../utils/contract";

// Define a schema for deployment parameters
const DeployContractSchema = z.object({
    // Provide constructor arguments as an array, if needed
    // If the contract constructor doesn't require arguments, an empty array is fine
    constructorArgs: z.array(z.any()).optional(),
});

export interface DeployContractContent extends Content {
    constructorArgs?: any[];
}

/* Helper function to pad bytecode so that its length in hex digits (excluding the '0x' prefix) is divisible by 64 (i.e. 32 bytes) and the 32-byte word count is odd. */
/* Helper function to pad bytecode so that its length in hex digits (excluding the '0x' prefix) is divisible by 64 (i.e. 32 bytes) and the 32-byte word count is odd. */
function padBytecode(hex: string): `0x${string}` {
    if (hex.startsWith("0x")) {
        hex = hex.slice(2);
    }
    const remainder = hex.length % 64;
    if (remainder !== 0) {
        const paddingLength = 64 - remainder;
        hex = hex + "0".repeat(paddingLength);
    }
    const wordCount = hex.length / 64;
    if (wordCount % 2 === 0) {
        hex = hex + "0".repeat(64);
    }
    return ("0x" + hex) as `0x${string}`;
}

// Template for prompting the LLM for deployment parameters
const deployTemplate = `
Please provide the constructor arguments for deploying the smart contract as JSON.
If no arguments are required, respond with an empty array.

Example response:
\`\`\`json
{
  "constructorArgs": ["arg1", 42]
}
\`\`\`

{{recentMessages}}
`;

export const deployContractAction: Action = {
    name: "DEPLOY_CONTRACT",
    similes: [
        "DEPLOY_SMART_CONTRACT",
        "DEPLOY_CONTRACT_ON_NEON",
        "LAUNCH_CONTRACT",
        "CREATE_CONTRACT",
    ],
    validate: async (runtime: IAgentRuntime, message: Memory) => {
        // Add any additional validation if required
        return true;
    },
    description: "Deploy a new smart contract on the Neon EVM network",
    handler: async (
        runtime: IAgentRuntime,
        message: Memory,
        state: State,
        _options: { [key: string]: unknown },
        callback?: HandlerCallback
    ): Promise<boolean> => {
        elizaLogger.log("Starting smart contract deployment...");

        // Initialize or update state
        if (!state) {
            state = (await runtime.composeState(message)) as State;
        } else {
            state = await runtime.updateRecentMessageState(state);
        }
        // Compose deployment context
        const deployContext = composeContext({
            state,
            template: deployTemplate,
        });

        // Generate deployment parameters from conversation
        const content = (
            await generateObject({
                runtime,
                context: deployContext,
                modelClass: ModelClass.SMALL,
                schema: DeployContractSchema,
            })
        ).object as unknown as DeployContractContent;

        const account = useGetAccount(runtime);
        const walletClient = useGetWalletClient();

        try {
           API.
            const deploymentResult = await walletClient.deployContract(
                {
                    abi: wagmiContract.abi,
                    account,
                    bytecode: padBytecode(wagmiContract.bytecode),
                    chain: neonDevnet,
                    kzg: {} as any,
                }
                // {
                //     account,
                //     chain: neonDevnet,
                // }
            );

            const deployedAddress = deploymentResult;
            elizaLogger.success(
                `Contract deployed successfully at address: ${deployedAddress}`
            );
            if (callback) {
                callback({
                    text: `Contract deployed successfully! Address: ${deployedAddress}`,
                    content: { deployedAddress },
                });
            }
            return true;
        } catch (error: any) {
            elizaLogger.error("Error during contract deployment:", error);
            if (callback) {
                callback({
                    text: `Error deploying contract: ${error.message}`,
                    content: { error: error.message },
                });
            }
            return false;
        }
    },
    examples: [
        [
            {
                user: "{{user1}}",
                content: {
                    text: "Deploy a new smart contract.",
                },
            },
            {
                user: "{{agent}}",
                content: {
                    text: "Deploying the contract with the provided arguments...",
                    action: "DEPLOY_CONTRACT",
                },
            },
            {
                user: "{{agent}}",
                content: {
                    text: "Contract deployed successfully at address: 0xABC123...",
                },
            },
        ],
    ],
};
Latest

From the blog

The latest industry news, interviews, technologies, and resources.