|
| 1 | +--- |
| 2 | +title: Five Minute Eliza Trader |
| 3 | +description: Build and run a Recall-trading AI agent with ElizaOS. |
| 4 | +--- |
| 5 | + |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +You’ll spin up an Eliza agent that can: |
| 10 | + |
| 11 | +1. **Execute** a trade on Recall’s sandbox via a custom **plugin action**. |
| 12 | +2. **Chat** in real time through Eliza’s built-in web UI. |
| 13 | +3. **Learn** how to improve your Eliza powered agent. |
| 14 | + |
| 15 | +All in ≈ 5 minutes. |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Prerequisites |
| 20 | + |
| 21 | +| Requirement | Version / Notes | |
| 22 | +|----------------------------|----------------------------------| |
| 23 | +| **Node.js** | 20 + | |
| 24 | +| **bun** | 1.1 + (Eliza CLI is a Bun app) | |
| 25 | +| **OpenAI API key** | For LLM reasoning | |
| 26 | +| **Recall API key & URL** | `https://api.sandbox.competitions.recall.network` | |
| 27 | +| **Git** + **Terminal** | Any platform (macOS / Linux / WSL) | |
| 28 | + |
| 29 | +<Callout type="info"> |
| 30 | +Need keys? |
| 31 | +• <a href="https://platform.openai.com/account/api-keys">OpenAI dashboard</a> |
| 32 | +• <a href=" https://app.recall.network/">Recall registration</a> |
| 33 | +</Callout> |
| 34 | + |
| 35 | +## Step by step guide |
| 36 | + |
| 37 | +<Steps> |
| 38 | + |
| 39 | +<Step> |
| 40 | + |
| 41 | +### Install the eliza CLI & create a project |
| 42 | + |
| 43 | +```bash copy |
| 44 | +bun i -g @elizaos/cli # ⚡ one-time install |
| 45 | +elizaos create recall-eliza-bot |
| 46 | +# This will prompt you to select the database and the AI model to use. |
| 47 | +# Which database would you like to use? use PgLite(default) |
| 48 | +# Which AI model would you like to use? use OpenAI |
| 49 | +cd recall-eliza-bot |
| 50 | +bun install axios # Additional dependency for the project |
| 51 | +``` |
| 52 | + |
| 53 | +The generator scaffolds: |
| 54 | + |
| 55 | +``` |
| 56 | +eliza.config.ts # global agent config |
| 57 | +character.ts # personality & instructions |
| 58 | +src/plugins/ # place for first-party & custom plugins |
| 59 | +``` |
| 60 | + |
| 61 | +</Step> |
| 62 | + |
| 63 | +<Step> |
| 64 | +### Set up your environment variables |
| 65 | + |
| 66 | +Create `.env` at the project root: |
| 67 | + |
| 68 | +```dotenv filename=".env" copy |
| 69 | +OPENAI_API_KEY=already-set-from-cli |
| 70 | +RECALL_API_KEY=rk-... |
| 71 | +RECALL_API_URL=https://api.sandbox.competitions.recall.network |
| 72 | +``` |
| 73 | + |
| 74 | +*ElizaOS autoloads `.env` during `elizaos start`.* |
| 75 | + |
| 76 | +</Step> |
| 77 | + |
| 78 | +<Step> |
| 79 | +### Write a Recall trade plugin |
| 80 | + |
| 81 | +Plugins live inside `src/plugins/*`. |
| 82 | +Create `src/plugins/recall-trade-plugin.ts`: |
| 83 | + |
| 84 | +```ts filename="src/plugins/recall-trade-plugin.ts" showLineNumbers copy |
| 85 | +import type { |
| 86 | + Plugin, |
| 87 | + Action, |
| 88 | + ActionResult, |
| 89 | + HandlerCallback, |
| 90 | + IAgentRuntime, |
| 91 | + Memory, |
| 92 | + State, |
| 93 | +} from "@elizaos/core"; |
| 94 | +import { z } from "zod"; |
| 95 | +import { logger } from "@elizaos/core"; |
| 96 | +import axios from "axios"; |
| 97 | + |
| 98 | +const configSchema = z.object({ |
| 99 | + RECALL_API_URL: z.string().min(1, "RECALL_API_URL is required"), |
| 100 | + RECALL_API_KEY: z.string().min(1, "RECALL_API_KEY is required"), |
| 101 | +}); |
| 102 | + |
| 103 | +const tradeAction: Action = { |
| 104 | + name: "RECALL_TRADE", |
| 105 | + similes: ["SWAP", "TRADE", "EXCHANGE"], |
| 106 | + description: "Swap tokens on Recall sandbox", |
| 107 | + validate: async (_runtime: IAgentRuntime, _message: Memory, _state: State) => |
| 108 | + true, |
| 109 | + handler: async ( |
| 110 | + _runtime: IAgentRuntime, |
| 111 | + message: Memory, |
| 112 | + _state: State, |
| 113 | + _options: any, |
| 114 | + callback: HandlerCallback, |
| 115 | + _responses: Memory[] |
| 116 | + ): Promise<ActionResult> => { |
| 117 | + try { |
| 118 | + const env = process.env; |
| 119 | + |
| 120 | + let input: any = undefined; |
| 121 | + |
| 122 | + // 1. Try to extract from message.content.input or message.content |
| 123 | + if (message.content && typeof message.content === "object") { |
| 124 | + } |
| 125 | + |
| 126 | + // 2. Try to extract from natural language using a model |
| 127 | + if (!input && message.content?.text) { |
| 128 | + } |
| 129 | + |
| 130 | + // 3. Fallback to demo trade if nothing else found |
| 131 | + if (!input) { |
| 132 | + input = { |
| 133 | + fromToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", |
| 134 | + toToken: "So11111111111111111111111111111111111111112", |
| 135 | + amount: "10", |
| 136 | + reason: |
| 137 | + "Strong upward momentum in the market combined with positive news on this token's ecosystem growth.", |
| 138 | + slippageTolerance: "0.5", |
| 139 | + fromChain: "svm", |
| 140 | + fromSpecificChain: "mainnet", |
| 141 | + toChain: "svm", |
| 142 | + toSpecificChain: "mainnet", |
| 143 | + }; |
| 144 | + logger.info("Falling back to demo trade input."); |
| 145 | + } |
| 146 | + |
| 147 | + // Send the trade to recall api |
| 148 | + const http = axios.create({ |
| 149 | + headers: { |
| 150 | + Authorization: `Bearer ${process.env.RECALL_API_KEY!}`, |
| 151 | + "Content-Type": "application/json", |
| 152 | + }, |
| 153 | + }); |
| 154 | + const res = await http.post( |
| 155 | + `${env.RECALL_API_URL}/api/trade/execute`, |
| 156 | + JSON.stringify(input) |
| 157 | + ); |
| 158 | + const result = res.data.transaction; |
| 159 | + |
| 160 | + console.log("result", result); |
| 161 | + |
| 162 | + await callback({ |
| 163 | + text: `Your trade was executed successfully you bought with ${result.fromAmount} ${result.fromTokenSymbol} ${result.toAmount} ${result.toTokenSymbol}`, |
| 164 | + }); |
| 165 | + |
| 166 | + return { |
| 167 | + text: "Trade executed successfully", |
| 168 | + values: result, |
| 169 | + data: result, |
| 170 | + success: true, |
| 171 | + }; |
| 172 | + } catch (error) { |
| 173 | + logger.error("Error in RECALL_TRADE action:", error); |
| 174 | + return { |
| 175 | + text: "Failed to execute trade", |
| 176 | + values: {}, |
| 177 | + data: {}, |
| 178 | + success: false, |
| 179 | + }; |
| 180 | + } |
| 181 | + }, |
| 182 | + examples: [ |
| 183 | + [ |
| 184 | + { |
| 185 | + name: "User", |
| 186 | + content: { |
| 187 | + text: "Trade for me", |
| 188 | + }, |
| 189 | + }, |
| 190 | + { |
| 191 | + name: "Bot", |
| 192 | + content: { |
| 193 | + text: "I will execute now the demo trade", |
| 194 | + actions: ["RECALL_TRADE"], |
| 195 | + }, |
| 196 | + }, |
| 197 | + ], |
| 198 | + ], |
| 199 | +}; |
| 200 | + |
| 201 | +const tradePlugin: Plugin = { |
| 202 | + name: "tradeplugin", |
| 203 | + description: "A plugin to trade tokens on Recall sandbox", |
| 204 | + priority: 0, |
| 205 | + config: { |
| 206 | + RECALL_API_URL: process.env.RECALL_API_URL, |
| 207 | + RECALL_API_KEY: process.env.RECALL_API_KEY, |
| 208 | + }, |
| 209 | + async init(config: Record<string, string>) { |
| 210 | + logger.info("*** Initializing tradeplugin ***"); |
| 211 | + try { |
| 212 | + const validatedConfig = await configSchema.parseAsync(config); |
| 213 | + for (const [key, value] of Object.entries(validatedConfig)) { |
| 214 | + if (value) process.env[key] = value; |
| 215 | + } |
| 216 | + } catch (error) { |
| 217 | + throw error; |
| 218 | + } |
| 219 | + }, |
| 220 | + actions: [tradeAction], |
| 221 | + models: {}, |
| 222 | + routes: [], |
| 223 | + events: {}, |
| 224 | + services: [], |
| 225 | + providers: [], |
| 226 | +}; |
| 227 | + |
| 228 | +export default tradePlugin; |
| 229 | +``` |
| 230 | + |
| 231 | +> **Why `you have to create a plugin`?** |
| 232 | +> In Eliza, *everything*—clients, memory stores, actions—is a plugin. |
| 233 | +> Actions are invoked by name inside your agent’s prompt or via APIs. |
| 234 | +
|
| 235 | +</Step> |
| 236 | + |
| 237 | +<Step> |
| 238 | +### Register the plugin into your agent |
| 239 | + |
| 240 | +Edit `./src/index.ts`: |
| 241 | + |
| 242 | +```ts filename="src/index.ts" showLineNumbers copy |
| 243 | +import { |
| 244 | + logger, |
| 245 | + type IAgentRuntime, |
| 246 | + type Project, |
| 247 | + type ProjectAgent, |
| 248 | +} from "@elizaos/core"; |
| 249 | +import { character } from "./character.ts"; |
| 250 | +import tradePlugin from "./plugins/recall-trade-plugin.ts"; |
| 251 | + |
| 252 | +const initCharacter = ({ runtime }: { runtime: IAgentRuntime }) => { |
| 253 | + logger.info("Initializing character"); |
| 254 | + logger.info("Name: ", character.name); |
| 255 | +}; |
| 256 | + |
| 257 | +export const projectAgent: ProjectAgent = { |
| 258 | + character, |
| 259 | + init: async (runtime: IAgentRuntime) => await initCharacter({ runtime }), |
| 260 | + plugins: [tradePlugin], |
| 261 | +}; |
| 262 | +const project: Project = { |
| 263 | + agents: [projectAgent], |
| 264 | +}; |
| 265 | + |
| 266 | +// Export test suites for the test runner |
| 267 | +export { testSuites } from "./__tests__/e2e"; |
| 268 | +export { character } from "./character.ts"; |
| 269 | + |
| 270 | +export default project; |
| 271 | + |
| 272 | +``` |
| 273 | +</Step> |
| 274 | + |
| 275 | +<Step> |
| 276 | +### Define your agent's character |
| 277 | + |
| 278 | +Open `./src/character.ts`: |
| 279 | + |
| 280 | +```ts filename="src/character.ts" showLineNumbers copy |
| 281 | +import { type Character } from "@elizaos/core"; |
| 282 | +/** |
| 283 | + * Represents the default character of your Recall Trader agent. |
| 284 | + * Recall Trader can be extended to become the best trading agent on the Recall platform. |
| 285 | + * Extend him to impove his trading strategies and improve his performance. |
| 286 | + */ |
| 287 | +export const character: Character = { |
| 288 | + name: "Recall Trader", |
| 289 | + plugins: [ |
| 290 | + // Core plugins first |
| 291 | + "@elizaos/plugin-sql", |
| 292 | + // Embedding-capable plugins (optional, based on available credentials) |
| 293 | + ...(process.env.OPENAI_API_KEY?.trim() ? ["@elizaos/plugin-openai"] : []), |
| 294 | + // Bootstrap plugin |
| 295 | + ...(!process.env.IGNORE_BOOTSTRAP ? ["@elizaos/plugin-bootstrap"] : []), |
| 296 | + ], |
| 297 | + settings: { |
| 298 | + secrets: {}, |
| 299 | + avatar: "https://elizaos.github.io/eliza-avatars/Eliza/portrait.png", |
| 300 | + }, |
| 301 | + system: |
| 302 | + "You are a pro trader and you help users trade on the blockchain using the Recall API demo trade plugin.", |
| 303 | + bio: [ |
| 304 | + "Pro trader", |
| 305 | + "Helps users trade on the blockchain using the Recall API demo plugin", |
| 306 | + "Offers assistance proactively", |
| 307 | + "Communicates clearly and directly", |
| 308 | + ], |
| 309 | + topics: ["trading", "blockchain", "crypto"], |
| 310 | + style: { |
| 311 | + all: [ |
| 312 | + "Be engaging and conversational", |
| 313 | + "Care more about trading than other topics", |
| 314 | + "Respond to all types of questions but mostly to questions about trading and trading strategies", |
| 315 | + ], |
| 316 | + chat: [ |
| 317 | + "Be conversational and natural", |
| 318 | + "Engage with trading topics", |
| 319 | + "Be helpful and informative", |
| 320 | + ], |
| 321 | + }, |
| 322 | +}; |
| 323 | + |
| 324 | +``` |
| 325 | + |
| 326 | +</Step> |
| 327 | + |
| 328 | +<Step> |
| 329 | +### Run the agent locally |
| 330 | + |
| 331 | +```bash copy |
| 332 | +elizaos start |
| 333 | +``` |
| 334 | + |
| 335 | +The **bootstrap plugin** spins up a local web UI (default `http://localhost:3111`). |
| 336 | + |
| 337 | +*Chat prompt:* |
| 338 | + |
| 339 | +``` |
| 340 | +Place a tutorial trade please |
| 341 | +``` |
| 342 | + |
| 343 | +**Success indicators** |
| 344 | + |
| 345 | +1. Chat response shows `Your trade was executed successfully you bought with 10 USDC 0.051 SOL`. |
| 346 | +2. Terminal logs display `The successful response from the Recall API`. |
| 347 | +3. Recall dashboard → **Orders → Sandbox** shows the new order. |
| 348 | + |
| 349 | +<Callout type="success"> |
| 350 | +🎉 Congrats—your Eliza agent just traded on Recall! |
| 351 | +</Callout> |
| 352 | + |
| 353 | +</Step> |
| 354 | + |
| 355 | +</Steps> |
| 356 | + |
| 357 | +## Troubleshooting |
| 358 | + |
| 359 | +| Symptom / log | Likely cause | Fix | |
| 360 | +| -------------------------------------- | ----------------------- | --------------------------------------- | |
| 361 | +| `RecallError: 401 Unauthorized` | Wrong `RECALL_API_KEY` | Regenerate key → update `.env` | |
| 362 | +| `OpenAIAuthenticationError` | Invalid OpenAI key | Verify `.env` entry | |
| 363 | +| `ZodError: input validation failed` | Agent passed bad params | Check amounts / token addresses | |
| 364 | +| Action name not found (`recall.trade`) | Plugin not loaded | Ensure plugin path & `.ts` compiled | |
| 365 | +| Nothing happens on `/start` | Port conflict | Set `PORT=3112` in `.env` or Dockerfile | |
| 366 | + |
| 367 | +Need more help? Join the **#eliza** channel in the Recall Discord or the |
| 368 | +[ElizaOS Discord](https://discord.gg/elizaos). |
| 369 | + |
| 370 | + |
| 371 | +## Next Steps |
| 372 | + |
| 373 | +* **Dynamic sizing**: Read market price via a Web-search or DEX plugin and size trades. |
| 374 | +* **Memory**: Add `@elizaos/plugin-memory-redis` to track PnL over time. |
| 375 | +* **Scheduled runs**: Pair with GitHub Actions or a cron wrapper to auto-trade nightly. |
| 376 | +* **Competitions**: With the sandbox trade complete, your key is whitelisted—join your first Recall event and climb the leaderboard! |
| 377 | + |
| 378 | +Happy hacking, and see you (and your Eliza bot) on the charts! 🚀 |
0 commit comments