Skip to main content

Create Your First Skill

In this tutorial, you will learn how to create your first Moxie Skill for your Creator Agent. The Creator Skill demonstrated here is straightforward and enables your Creator Agent to provide the most up-to-date summarized information on the Moxie protocol.

Pre-requisites

Creator Skills are essentially custom Eliza plugins that you can build to enhance your Creator Agents.

Before you begin development, please ensure you have all the prerequisites for the Eliza Framework:

  • Node.js 23+
  • pnpm 9+
  • Git for version control
  • A code editor (VS Code or VSCodium recommended)
  • CUDA Toolkit (optional, for GPU acceleration)

Once your environment is set up, confirm you have completed the Eliza Quickstart guide and have your own AI agent running.

Step 1: Define A New Plugin

Create a new file under the src/plugins directory with the following content to define your plugin instance:

src/plugins/myFirstMoxieSkill.ts
import { Plugin } from "@elizaos/core";

const myFirstMoxieSkill: Plugin = {
name: "my-first-moxie-skill",
description:
"This plugin is invoked when the user is asking for the current state of the Moxie protocol.",
actions: [],
providers: [],
};

Step 2: Create New Provider & Action For The Agent

After defining your plugin, you will create a new Eliza provider and a new action to give your agent the ability to handle protocol-specific data.

Eliza providers and actions are core components of the Eliza framework that help modularize and extend the functionality of your Creator Agent. A provider injects dynamic, real-time context into the agent’s interactions, while an action defines how the agent responds to user inputs.

Step 2.1: Create A New Provider

First, create a provider to pull the Moxie protocol information from the subgraph and integrate that data into the agent’s context. Place this new file under the src/providers directory:

src/providers/moxieSummaryProvider.ts
import {
type Provider,
type IAgentRuntime,
type Memory,
type State,
elizaLogger,
} from "@elizaos/core";
import { gql, GraphQLClient } from "graphql-request";

const graphQLClient = new GraphQLClient(
"https://api.studio.thegraph.com/query/23537/moxie_protocol_stats_mainnet/version/latest"
);

const moxieSummaryrovider: Provider = {
get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
const query = gql`
query MyQuery {
summaries {
numberOfAuctionOrders
numberOfBuyOrders
numberOfSellOrders
numberOfUsers
protocolBuyFeePct
protocolSellFeePct
subjectBuyFeePct
subjectSellFeePct
totalBuyVolume
totalProtocolFee
totalProtocolFeeFromAuction
totalProtocolTokenInvested
totalReserve
totalSellVolume
totalStakedSubjectTokens
totalSubjectFee
totalSubjectFeeFromAuction
totalSubjectTokensIssued
}
}
`;
// fetch data from Moxie protocol subgraph here
// to provide context for creator agent
const data = await graphQLClient.request(query);

return data;
},
};

export default moxieSummaryrovider;

Step 2.2: Create A New Action

Next, define an action that tells your agent how to respond when a user requests Moxie protocol information. To achieve this, you will need to fill out the following fields:

interface Action {
// Unique identifier for the action
name: string;
// Array of alternative names/variations
similes: string[];
// Detailed explanation of the action's purpose
description: string;
// Function that checks if action is appropriate
examples: ActionExample[][];
// Demonstrates proper usage patterns
handler: Handler;
// Determines if the action can be executed
validate: Validator;
// When true, suppresses the initial response message before processing the action.
// Useful for actions that generate their own responses (like image generation)
suppressInitialMessage?: boolean;
}

Create a new file in the src/actions directory, and define the new action with your chosen name, similes, and description:

src/actions/moxieSummaryAction.ts
import type {
Action,
HandlerCallback,
IAgentRuntime,
Memory,
State,
} from "@elizaos/core";
import moxieSummaryProvider from "../providers/moxieSummaryProvider";

const getMoxieSummary: Action = {
name: "MOXIE_SUMMARY",
similes: [
"MOXIE_DATA",
"MOXIE_SUMMARY_DATA",
"MOXIE_PROTOCOL_DATA",
"MOXIE_TRADES_DATA",
"MOXIE_ECONOMY_DATA",
"MOXIE_ECONOMIC_DATA",
"MOXIE_STATS_DATA",
],
description:
"This plugin is used to fetch and display the current state of the Moxie protocol.",
} as Action;

export default getMoxieSummary;

These metadata guide the Creator Agent in selecting the appropriate action when a user sends prompts, so be sure to provide a clear and informative name, similes, and description.

Next, for the validate field, keep it simple by returning true to ensure requests are always validated:

src/actions/moxieSummaryAction.ts
import type {
Action,
HandlerCallback,
IAgentRuntime,
Memory,
State,
} from "@elizaos/core";
import moxieSummaryProvider from "../providers/moxieSummaryProvider";

const getMoxieSummary: Action = {
// ... same as above
validate: async (
runtime: IAgentRuntime,
message: Memory,
state?: State
): Promise<boolean> => {
// Add validation logic that returns true/false here
return true;
},
} as Action;

export default getMoxieSummary;

If you would like to add any gating logic, then you can add additional if-else statements to the validate field where false is returned if the requests is invalid.

After that, define the handler field to retrieve the Moxie protocol data from the Moxie Summary Provider and return this information to the user.

To achieve this, you also need a template that helps the LLM model extract JSON data from the provider:

src/template.ts
const summaryDetailsTemplate = `
Extract the following JSON fields to get all the summary data and return it in JSON format:

{
"numberOfAuctionOrders": <string>,
"numberOfBuyOrders": <string>,
"numberOfSellOrders": <string>,
"numberOfUsers": <string>,
"protocolBuyFeePct": <string>,
"protocolSellFeePct": <string>,
"subjectBuyFeePct": <string>,
"subjectSellFeePct": <string>,
"totalBuyVolume": <string>,
"totalProtocolFee": <string>,
"totalProtocolFeeFromAuction": <string>,
"totalProtocolTokenInvested": <string>,
"totalReserve": <string>,
"totalSellVolume": <string>,
"totalStakedSubjectTokens": <string>,
"totalSubjectFee": <string>,
"totalSubjectFeeFromAuction": <string>,
"totalSubjectTokensIssued": <string>
}

Here are the providers for context:
{{providers}}
`;

export default summaryDetailsTemplate;

And use a schema (with the zod library) to validate the data:

src/types.ts
import { z } from "zod";

const MoxieSummarySchema = z.object({
numberOfAuctionOrders: z.string().nullable(),
numberOfBuyOrders: z.string().nullable(),
numberOfSellOrders: z.string().nullable(),
numberOfUsers: z.string().nullable(),
protocolBuyFeePct: z.string().nullable(),
protocolSellFeePct: z.string().nullable(),
subjectBuyFeePct: z.string().nullable(),
subjectSellFeePct: z.string().nullable(),
totalBuyVolume: z.string().nullable(),
totalProtocolFee: z.string().nullable(),
totalProtocolFeeFromAuction: z.string().nullable(),
totalProtocolTokenInvested: z.string().nullable(),
totalReserve: z.string().nullable(),
totalSellVolume: z.string().nullable(),
totalStakedSubjectTokens: z.string().nullable(),
totalSubjectFee: z.string().nullable(),
totalSubjectFeeFromAuction: z.string().nullable(),
totalSubjectTokensIssued: z.string().nullable(),
});

export default MoxieSummarySchema;

and the handler field full code should look as following:

src/actions/moxieSummaryAction.ts
import type {
Action,
HandlerCallback,
IAgentRuntime,
Memory,
State,
} from "@elizaos/core";
import moxieSummaryProvider from "../providers/moxieSummaryProvider";
import { summaryDetailsTemplate } from "../template";
import MoxieSummarySchema from "../types";

const getMoxieSummary: Action = {
// ... same as above
handler: async (
runtime: IAgentRuntime,
message: Memory,
state?: State,
options?: { [key: string]: unknown },
callback?: HandlerCallback
) => {
try {
const state = await runtime.composeState(message);
const summaryDetails = await generateObject({
runtime,
context: composeContext({
state,
template: summaryDetailsTemplate,
}),
modelClass: ModelClass.LARGE,
schema: MoxieSummarySchema,
});
const {
numberOfAuctionOrders,
numberOfBuyOrders,
numberOfSellOrders,
numberOfUsers,
protocolBuyFeePct,
protocolSellFeePct,
subjectBuyFeePct,
subjectSellFeePct,
totalBuyVolume,
totalProtocolFee,
totalProtocolFeeFromAuction,
totalProtocolTokenInvested,
totalReserve,
totalSellVolume,
totalStakedSubjectTokens,
totalSubjectFee,
totalSubjectFeeFromAuction,
totalSubjectTokensIssued,
} = (summaryDetails.object as MoxieSummary) ?? {};

// reply back to the user with the summary data
callback?.({
text: `Here's the summary of the current state of the Moxie protocol:
- Number of Auction Orders: ${numberWithCommas(numberOfAuctionOrders)}
- Number of Buy Orders: ${numberWithCommas(numberOfBuyOrders)}
- Number of Sell Orders: ${numberWithCommas(numberOfSellOrders)}
- Number of Users: ${numberWithCommas(numberOfUsers)}
- Protocol Buy Fee (%): ${formatEther(BigInt(protocolBuyFeePct))}%
- Protocol Sell Fee (%): ${formatEther(BigInt(protocolSellFeePct))}%
- Subject Buy Fee (%): ${formatEther(BigInt(subjectBuyFeePct))}%
- Subject Sell Fee (%): ${formatEther(BigInt(subjectSellFeePct))}%
- Total Buy Volume: ${numberWithCommas(
Math.trunc(Number.parseInt(formatEther(BigInt(totalBuyVolume))))
)}
- Total Sell Volume: ${numberWithCommas(
Math.trunc(Number.parseInt(formatEther(BigInt(totalSellVolume))))
)}
- Total Protocol Fee: ${numberWithCommas(
Math.trunc(Number.parseInt(formatEther(BigInt(totalProtocolFee))))
)}
- Total Protocol Fee (Auction): ${numberWithCommas(
Math.trunc(
Number.parseInt(formatEther(BigInt(totalProtocolFeeFromAuction)))
)
)}
- Total Protocol Token Invested: ${numberWithCommas(
Math.trunc(
Number.parseInt(formatEther(BigInt(totalProtocolTokenInvested)))
)
)}
- Total Reserve: ${numberWithCommas(
Math.trunc(Number.parseInt(formatEther(BigInt(totalReserve))))
)}
- Total Staked Subject Tokens: ${numberWithCommas(
Math.trunc(
Number.parseInt(formatEther(BigInt(totalStakedSubjectTokens)))
)
)}
- Total Subject Fee: ${numberWithCommas(
Math.trunc(Number.parseInt(formatEther(BigInt(totalSubjectFee))))
)}
- Total Subject Fee (Auction): ${numberWithCommas(
Math.trunc(
Number.parseInt(formatEther(BigInt(totalSubjectFeeFromAuction)))
)
)}
- Total Subject Tokens Issued: ${numberWithCommas(
Math.trunc(
Number.parseInt(formatEther(BigInt(totalSubjectTokensIssued)))
)
)}`,
});
return true;
} catch (error) {
callback?.({
text: `Sorry, there was an error fetching Moxie summary data: ${
error instanceof Error ? error.message : "Unknown error"
}`,
});
return false;
}
},
} as Action;

export default getMoxieSummary;

Lastly, add an examples field to your action. By providing a range of prompts, the agent can learn different question patterns and how to respond appropriately:

src/actions/moxieSummaryAction.ts
import type {
Action,
HandlerCallback,
IAgentRuntime,
Memory,
State,
} from "@elizaos/core";
import moxieSummaryProvider from "../providers/moxieSummaryProvider";

const getMoxieSummary: Action = {
// ... same as above
examples: [
[
{
user: "{{user1}}",
content: {
text: "What is the current state of the Moxie protocol?",
},
},
{
user: "{{agentName}}",
content: {
text: `
Here's the summary of the current state of the Moxie protocol:
- Number of Auction Orders: 50
- Number of Buy Orders: 300
- Number of Sell Orders: 200
- Number of Users: 100
- Protocol Buy Fee (%): 5
- Protocol Sell Fee (%): 5
- Subject Buy Fee (%): 3
- Subject Sell Fee (%): 3
- Total Buy Volume: 120,000
- Total Sell Volume: 80,000
- Total Protocol Fee: 500
- Total Protocol Fee (Auction): 200
- Total Protocol Token Invested: 1,000
- Total Reserve: 2,000
- Total Staked Subject Tokens: 3,000
- Total Subject Fee: 300
- Total Subject Fee (Auction): 200
- Total Subject Tokens Issued: 1,500
`,
action: "MOXIE_SUMMARY",
},
},
],
],
} as Action;

export default getMoxieSummary;

Step 2.3: Integrate The Action Into Your Plugin

Finally, integrate the provider and action above into your plugin by importing it and adding it to the actions array:

src/plugins/index.ts
import { Action } from "@elizaos/core";
import moxieSummaryAction from "../actions/moxieSummaryAction";
import moxieSummaryrovider from "./providers/moxie_summary";

const myFirstCreatorAgentSkill: Plugin = {
name: "my-first-creator-agent-skill",
description: "My First Moxie Skill",
actions: [moxieSummaryAction],
providers: [moxieSummaryrovider],
};

export default myFirstCreatorAgentSkill;

and add it to your agent instance:

import { Agent } from "@elizaos/core";
import myFirstCreatorAgentSkill from "./plugins/index";

const runtime = new AgentRuntime({
plugins: [myFirstCreatorAgentSkill],
});

Beyond Eliza providers and actions, you can further tailor your Moxie Skill by incorporating custom evaluators or services to introduce advanced features and monetization options.

Step 3: Test Your Plugin With The Agent

To test your plugin, start the Eliza framework locally by running:

npm run start --character="characters/<character>.character.json"

With the agent running, you can then start a client with a chat interface to test interactions with your Creator Agent:

npm run start:client

Try out various of the following prompts to test your newly created Creator Agent Skills yourself:

  • What is the current state of the Moxie protocol?
  • Give me an update on the Moxie protocol.
  • What is the latest stats on the Moxie protoco?
  • etc.

Congratulations! 🥳🎉 You have successfully created your first Moxie Skill for your Creator Agent.

More Resources

For adding more advanced features to your Creator Agent, you can refer to the following resources to further develop your Creator Agent Skills:

Developer Support

If you have any questions or need help with other use cases, feel free to join the Moxie Official Warpcast channel and ask your questions there.

Our team is always ready to help you with any questions you may have.