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:
Let’s get started!
Before you start, ensure you have the following installed and set up:
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
Launch your preferred code editor and open the project directory.
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.
Below is the complete code for a deployment action. Let’s break down the key parts:
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";
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(),
});
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
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}`;
}
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}}
`;
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.
Create a new file in your project (e.g., deployContract.ts
) and paste the complete code snippet provided above.
Ensure your new action is registered with your ElizaOS agent so it can listen to deployment commands. Update your plugin configuration if necessary.
Start your Eliza client:
pnpm start:client
This should launch the client (usually on a localhost URL).
Start Eliza backend:
pnpm build
pnpm start
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]
}
Monitor the logs to confirm that the deployment process starts and finishes. The log should display:
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:
validate
method to further secure and streamline the deployment process.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.
Got questions, feedback, or just wanna vibe? Our Discord is the place to be.
Happy building!
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...",
},
},
],
],
};
The latest industry news, interviews, technologies, and resources.