On this page How agents work Quickstart: run a strategy locally Writing a strategy What decide receives What decide returns Helpers in src/strategy.ts What the runner does for you Tips Markets Agent details Trust model Managing your agent Publish your agent: from a strategy to a live listing Step 1 — Get the kit and write your strategy Step 2 — Create two keys and fund them Step 3 — Register the agent on testnet Step 4 — Put your fork on GitHub Step 5 — Deploy to Railway Step 6 — Confirm it's live Step 7 — Get verified Day-to-day If something's wrong Key safety What buyers see
Developer guide

Building an agent

This guide takes you from an idea to a listed agent that buyers can hire. You'll write a trading strategy in one TypeScript file, test it against a local chain in about 10 minutes, then list it on testnet.

#How agents work

An agent has three parts:

Part What it is Where it lives
Seller Your wallet. It lists the agent and receives your fee (up to 8% of profit) on each profitable trade. A wallet you keep safe
Operator The key your bot signs trades with. It can only swap buyers' escrowed funds through owner-approved DEXes, with the proceeds going back to the contract. Your bot's server
Strategy Your code. It decides when to buy and when to sell. One .ts file, run by ./zai run

When a buyer hires your agent, their tokens go into escrow in the marketplace contract. Your bot sees the trade, and your strategy decides when to open (swap the buyer's base token into the asset) and when to close (swap back). On close, the contract measures the result itself and pays out automatically:

If your bot goes offline, buyers aren't stuck:

Only live agents are offered for hire. Once a minute, a running bot tells the marketplace website it is alive by posting a heartbeat: a short message signed with the operator key (it proves the key is online and nothing else — it can't move funds). The web app lists your agent as live and offers it in the hire form while it keeps reporting in; when your bot stops, it shows as offline within a few minutes and can't be hired until the bot is back. The runner does this for you; set HEARTBEAT_URL=off to opt out (your agent then never shows as live). The app also marks an agent may be offline when a hired trade sits unopened for more than 5 minutes.

Buyers only see verified agents. Anyone can list an agent on-chain, but zilliqa.ai shows it to buyers only after the marketplace team has reviewed it — see Step 7. Until then the app shows it only to you (with your seller wallet connected), marked awaiting verification, and you can hire it yourself to test it.

#Quickstart: run a strategy locally

Everything you need is in the agent kit. You need Node 20.12 or newer, and anvil from Foundry for the local test chain.

git clone https://github.com/jactheman/zilliqa-ai-agent-kit
cd zilliqa-ai-agent-kit
npm install

anvil                # terminal 1: a local chain (leave it running)
./zai dev up         # terminal 2: deploys a test marketplace, mock tokens and a mock DEX

Anvil starts empty every time it restarts, so run ./zai dev up again after each restart.

1. Register your agent. Locally, this signs with a funded test wallet:

./zai markets              # what this marketplace offers: mZIL/mUSD, mSEED/mUSD
./zai register --name "MyFirstBot" --fee 5 --new-operator \
  --market mZIL/mUSD --market mSEED/mUSD \
  --description "Buys as soon as it's hired, sells at +5% or -3%." \
  --strategy "Take-profit" --risk medium

This prints your agent id and a freshly generated operator key. Locally, it also funds that key with gas. The description, strategy and risk are what buyers see on your agent's card (see Agent details).

2. Write your strategy. Create one from the template:

./zai init my-strategy     # creates strategies/my-strategy.ts

3. Run it with the operator key from step 1:

OPERATOR_KEY=0x... ./zai run --agent 1 --strategy my-strategy

4. Hire it and move the market. In another terminal, act as a buyer and push the mock price around, then watch your bot react:

./zai dev hire 100 --agent 1    # a test buyer escrows 100 mUSD (default market)
./zai dev hire 50 --agent 1 --market mSEED/mUSD
./zai dev price -4%             # the asset drops 4%
./zai dev price +9%             # and recovers
./zai status                    # trades and P&L

#Writing a strategy

A strategy is one file that default-exports defineStrategy(...) with a decide function. The runner calls decide on every tick (every 2 seconds by default) for every pending or open trade hired to your agent:

import { defineStrategy, param } from "../src/strategy";

const TARGET_BPS = param("TARGET_BPS", 500); // configurable via env, default +5%

export default defineStrategy({
  name: "my-strategy",
  describe: () => `sell at +${TARGET_BPS / 100}%`,

  decide(position, market) {
    if (position.phase === "pending") return { action: "open" };
    if (position.pnlBps >= TARGET_BPS) return { action: "close", reason: "target hit" };
    return { action: "wait" };
  },
});

#What decide receives

position is one trade:

Field Meaning
phase "pending" (buyer's funds escrowed, you haven't opened yet) or "open" (you hold the asset)
amountIn Base tokens the buyer escrowed
assetAmount Asset tokens held (0 while pending)
value What the position is worth in base tokens right now
pnlBps Unrealized P&L in basis points (100 = 1%)
heldSec Seconds since the position was opened
secsToDeadline Seconds until the buyer may reclaim
baseToken, assetToken, buyer, id Identifiers

market holds prices:

Field Meaning
price Latest price of 1 asset in base (1e18-scaled bigint)
history Price samples, oldest first, one per tick, capped at 1,000. Starts empty when the runner starts.
now Chain timestamp

#What decide returns

Decision When it's valid Effect
{ action: "open" } pending Swap all escrowed base into the asset
{ action: "close", reason } open Swap back to base and settle
{ action: "wait", note? } always Do nothing this tick. The note is logged when it changes.

#Helpers in src/strategy.ts

strategies/mean-reversion.ts shows a strategy that uses price history.

#What the runner does for you

These rules apply whatever your strategy returns:

#Tips

#Markets

A market is a pair written ASSET/BASE. For example, WZIL/USDC means buyers deposit and are paid out in USDC, and your agent trades WZIL.

Testnet markets today: WZIL/USDC and SEED/USDC, both on PlunderSwap.

#Agent details

Your agent's card shows a description, a strategy label, a risk level and links, next to the on-chain name and fee. The contract stores these as a metadataURI pointing to a small JSON document:

{
  "description": "Buys 3% dips below the 20-tick average and sells at the mean.",
  "strategy": "Mean reversion",
  "risk": "medium",
  "links": {
    "source": "https://github.com/you/dip-buyer",
    "website": "https://example.com",
    "twitter": "https://x.com/you"
  },
  "markets": [
    { "base": "0x1fD0…USDC", "asset": "0x878c…WZIL" },
    { "base": "0x1fD0…USDC", "asset": "0x28e8…SEED" }
  ]
}

markets is written for you by --market. All fields are optional. Limits: description 280 characters, strategy 60, links must be http(s) and at most 200 characters, document at most 16 KB. Unknown fields are ignored. The name shown is always the on-chain name, not anything in this file.

You can store this document in three ways:

URI Hosting Can it change?
data:application/json,{…} None: stored on-chain (up to 2,048 bytes) Only through setAgentMetadata
ipfs://<cid> Pin it on IPFS No: new content means a new CID
https://…/agent.json Your server, which must allow CORS Yes, whenever you edit the file

The CLI and the web form use the on-chain data: option unless you give them a URI. For buyers, data: and ipfs:// details can't change without a transaction or a new CID, which makes them more trustworthy than an https:// file.

# set or edit details with flags (edits on top of what's there)
./zai update 1 --description "…" --strategy "Mean reversion" --risk low --source https://github.com/you/bot

# or from a JSON file (stored on-chain), or point at a hosted document
./zai update 1 --metadata ./agent.json
./zai update 1 --metadata ipfs://bafy…

# see exactly what buyers will see
./zai show 1

Once the marketplace is live you can also use Edit on your agent's card in the web app.

#Trust model

What the contract guarantees, and what it relies on:

#Managing your agent

./zai agents --mine                   # your agents
./zai update 1 --fee 3                # new trades only; existing trades keep their fee
./zai update 1 --operator 0xNEW       # rotate the bot key (e.g. if a server was compromised)
./zai update 1 --pause                # stop receiving new trades
./zai update 1 --resume

Once the marketplace is live, you can also list, edit, pause and resume agents from the web app.

#Publish your agent: from a strategy to a live listing

This is the whole path, in order, with the exact commands and what each one prints. Budget about 30 minutes the first time. Steps 1–3 happen on your computer; steps 4–6 put the bot on Railway so it runs 24/7; step 7 gets it in front of buyers.

You need: Node 20.12+, git, a GitHub account, a free Railway account, and a browser wallet such as MetaMask for the faucet.

#Step 1 — Get the kit and write your strategy

git clone https://github.com/jactheman/zilliqa-ai-agent-kit
cd zilliqa-ai-agent-kit
npm install
./zai init my-strategy          # creates strategies/my-strategy.ts from the template

Edit strategies/my-strategy.ts. It exports one decide() function; the sections above (Writing a strategy) explain what it receives and can return. Two ready-made examples live next to it: take-profit.ts (buys as soon as it's hired, exits at +10% / −5% / 1 hour) and mean-reversion.ts.

Test it on the local chain first (Quickstart) — hire your own agent, move the mock price, and watch it open and close. Nothing on testnet costs real money, but a local run turns bugs around in seconds.

#Step 2 — Create two keys and fund them

Your agent has two keys. Keep them separate.

Key What it does Where it lives
Seller Lists the agent, edits it, receives your fee Your own wallet, on your computer only
Operator Signs the bot's trades. It can only swap buyers' escrowed funds through approved DEXes, never withdraw them Railway's secret store
./zai keygen        # run it twice: once for the seller (if you don't want to use an existing wallet), once for the operator

Each run prints:

address:     0x1234…abcd
private key: 0xabc…

Save both keys somewhere safe (a password manager). The kit never stores them.

Fund them with testnet ZIL from the Zilliqa testnet faucet: paste each address. The seller needs a little ZIL for the listing transaction; the operator pays gas for every open and close, so give it more (20 ZIL lasts a long time on testnet). To check a balance: cast balance --rpc-url https://api.testnet.zilliqa.com -e 0xADDRESS, or look it up on otterscan.testnet.zilliqa.com.

Put the seller key in .env in the kit folder (the file is gitignored):

NETWORK=testnet
SELLER_KEY=0x...        # seller private key

#Step 3 — Register the agent on testnet

./zai markets        # the pairs you can declare: WZIL/USDC, SEED/USDC
./zai register \
  --name "MyBot" \
  --fee 8 \
  --operator 0xOPERATOR_ADDRESS \
  --market WZIL/USDC \
  --description "Buys on a 21-bar breakout, sells at +6% or -4%." \
  --strategy "Breakout" \
  --risk medium \
  --source https://github.com/YOU/YOUR-FORK

It prints:

✓ listed agent #7 "MyBot" at 8% of profit on testnet
  seller (earns fees): 0x1234…abcd
  operator (bot key):  0x5678…ef01

Write down the agent id (#7 here) — the bot needs it. Check the listing the way buyers will see it:

./zai show 7

It appears in the app at zilliqa.ai/app straight away, marked offline until the bot runs.

Do a one-minute test run from your computer before deploying, using the operator key just this once:

OPERATOR_KEY=0x... ./zai run --agent 7 --strategy my-strategy --network testnet

You should see:

agent #7 "MyBot" (8% fee) on testnet, marketplace 0x8fD4…3B05
operator 0x5678…ef01 · 1 approved DEX router(s)
strategy: my-strategy — …
trading 1 market(s): WZIL/USDC
heartbeat: reporting live to https://zilliqa.ai/api/heartbeat every 60s

Refresh the app: your agent now shows live. Stop the bot with Ctrl-C (it goes back to offline within three minutes) and move on.

#Step 4 — Put your fork on GitHub

Railway builds from a git repository. Create an empty repository on GitHub (for example my-zilliqa-bot), then in the kit folder:

git remote rename origin upstream          # keep the kit as "upstream" so you can pull updates later
git remote add origin [email protected]:YOU/my-zilliqa-bot.git
git add strategies/my-strategy.ts
git commit -m "Add my-strategy"
git push -u origin main

.env is ignored by git and by the Docker build, so your keys never leave your machine this way. Double-check with git status that no .env is staged.

#Step 5 — Deploy to Railway

The kit already contains the Dockerfile and railway.json Railway needs. Everything else is environment variables.

Option A — from the dashboard

  1. railway.com → New Project → Deploy from GitHub repo → pick my-zilliqa-bot. Railway detects the Dockerfile and starts a build; it will fail the first time because the variables aren't set yet — that's expected.

  2. Open the service → Variables → add these five, then click Deploy:

    Variable Value
    NETWORK testnet
    OPERATOR_KEY the operator private key from step 2 (use the sealed option so it can't be read back)
    AGENT_ID the id from step 3, e.g. 7
    STRATEGY your file name without .ts, e.g. my-strategy
    POLL_MS 4000
  3. Under Settings, leave replicas at 1 and don't add a public domain — the bot needs no inbound traffic.

Option B — from the terminal

npm install -g @railway/cli        # or: brew install railway
railway login                      # opens the browser once
railway init                       # creates a project; name it e.g. my-zilliqa-bot
railway variables --set NETWORK=testnet --set OPERATOR_KEY=0x... \
  --set AGENT_ID=7 --set STRATEGY=my-strategy --set POLL_MS=4000 --skip-deploys
railway up --detach                # uploads this folder and builds the Dockerfile
railway logs                       # follow the bot

railway up uploads your working folder directly, so with Option B the GitHub push in step 4 is only for keeping your code safe and for the --source link buyers see. To redeploy after a change, run railway up --detach again.

#Step 6 — Confirm it's live

In Railway's Deployments → View logs you should see the same five lines as in the local test run, ending with heartbeat: reporting live. Within a minute zilliqa.ai/app shows your agent as live — to you, with your seller wallet connected, until it's verified. Hire it yourself with a small amount to see a full cycle (or from the terminal: ./zai dev hire 10 --agent 7 --network testnet).

Optional strategy parameters (TAKE_PROFIT_BPS, STOP_LOSS_BPS, MAX_HOLD_SEC, or any param() you define) are also plain Railway variables; change one and redeploy.

#Step 7 — Get verified

Email [email protected] with your agent number and network. The review checks that:

Once approved, your agent appears to every buyer within a minute. Verification is for this marketplace address and for the details that were reviewed: the list pins a hash of your agent's metadataURI. If you change your details afterwards (zai update with any details flag, or the app's edit form), including the markets your bot trades, buyers stop seeing your agent and you see details changed since verification instead. Email again to be re-reviewed. If the marketplace is redeployed, agents are reviewed again. An agent that misbehaves can lose its verification.

#Day-to-day

./zai update 7 --fee 5                 # new trades only; open trades keep the fee they were hired at
./zai update 7 --description "…"       # edit details
./zai update 7 --pause                 # stop accepting trades (the app shows "paused")
./zai update 7 --resume
./zai update 7 --operator 0xNEW        # rotate the bot key: then update OPERATOR_KEY on Railway and redeploy
git pull upstream main                 # pick up kit updates (new marketplace address, fixes), then redeploy

#If something's wrong

Symptom Cause and fix
App shows offline although the bot is running Look for heartbeat failed (…) in the logs. 403 — the operator key on Railway isn't the one registered for this agent id. 503 — the marketplace's storage is unavailable; the bot keeps trading and the app recovers when it does.
Agent #7's operator is 0x…, but OPERATOR_KEY belongs to 0x… at start-up Wrong key or wrong AGENT_ID. Fix the variable and redeploy, or ./zai update 7 --operator <address of the key you have>.
Agent #7 doesn't exist on this marketplace The kit is pointing at an older marketplace address, or the id is wrong. git pull upstream main, then check ./zai agents.
insufficient funds when a trade should open The operator ran out of ZIL for gas. Send it more from the faucet.
no route from … on any approved DEX You declared a market no approved DEX can route yet. Remove it with ./zai update 7 --market WZIL/USDC.
Trades stay pending for a long time while the agent is live Your strategy hasn't returned open yet. That's fine if it's selective; the app tells buyers it's "waiting for signal".
Railway build fails The log shows the failing step. Most often: package-lock.json missing from the commit, or Node version — the image uses Node 22.

#Key safety

#What buyers see

Buyers see your agent's name, fee, number of settled trades and cumulative buyer P&L, all read straight from the chain, so there's no way to fake a track record. Before they hire, the app spells out the full cost of a winning trade: 4% of profit to the protocol plus your fee, with the total never above 12%. Next to those they see your agent details.

The app only lists verified agents, so a buyer only sees agents that passed review. The review covers your bot's uptime, trade record, details and source; it doesn't guarantee profits. The web app refuses a name that's already taken (ignoring case), but the contract itself doesn't check. Linking your strategy's source code is the best way to earn buyers' trust.