Skip to Content
Recipes

Phase 1 — Monitor a Pool

A read-only agent that watches a Cetus trading pool. No funds at risk — it just observes price movement and simulates what a position would do.

Time: ~10 minutes | Risk: None (read-only)


What you’ll build

An agent that runs in a loop and:

  1. Connects to a Cetus pool on Sui
  2. Reads the current market price every 5 minutes
  3. Simulates a position and checks if it would need repositioning
  4. Logs everything to a JSON file

The code

Create agent.js:

import { initCetusSDK } from '@cetusprotocol/cetus-sui-clmm-sdk'; import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import fs from 'fs'; import dotenv from 'dotenv'; dotenv.config(); const AGENT_ID = 'sui-cetus-yield'; const LOG_FILE = process.env.LOG_FILE || `${AGENT_ID}.log`; const CHECK_INTERVAL = parseInt(process.env.CHECK_INTERVAL_MS || '300000'); const POOL_ID = process.env.CETUS_POOL_ID; const THRESHOLD = parseInt(process.env.REBALANCE_THRESHOLD_TICKS || '100'); const RANGE = parseInt(process.env.POSITION_RANGE_TICKS || '200'); // --- Logging --- function log(level, message, data = {}) { const entry = { ts: new Date().toISOString(), agent: AGENT_ID, level, message, ...data }; const line = JSON.stringify(entry); console.log(line); try { fs.appendFileSync(LOG_FILE, line + '\n'); } catch {} } // --- Cetus SDK --- const client = new SuiClient({ url: process.env.SUI_RPC || getFullnodeUrl('mainnet') }); const sdk = initCetusSDK({ network: process.env.NETWORK || 'mainnet' }); async function getPoolState() { return await sdk.Pool.getPool(POOL_ID); } // --- Simulated Position --- let simPosition = null; // { tickLower, tickUpper, center, openedAt } function simulatePositionCheck(currentTick) { if (!simPosition) { // Open a simulated position centered on current price const tickLower = currentTick - RANGE; const tickUpper = currentTick + RANGE; simPosition = { tickLower, tickUpper, center: currentTick, openedAt: new Date().toISOString() }; log('event', 'sim_position_opened', { tickLower, tickUpper, center: currentTick }); return; } const drift = Math.abs(currentTick - simPosition.center); const outOfRange = currentTick < simPosition.tickLower || currentTick > simPosition.tickUpper; if (outOfRange || drift > THRESHOLD) { log('event', 'sim_drift_detected', { drift, threshold: THRESHOLD, currentTick }); // Simulate rebalance: close old, open new simPosition = { tickLower: currentTick - RANGE, tickUpper: currentTick + RANGE, center: currentTick, openedAt: new Date().toISOString(), }; log('event', 'sim_rebalance', { newTickLower: simPosition.tickLower, newTickUpper: simPosition.tickUpper }); } else { log('info', 'Simulated position in range', { currentTick, drift, threshold: THRESHOLD }); } } // --- Main Loop --- async function runAgent() { log('info', 'Agent starting', { mode: 'monitor', pool: POOL_ID, checkInterval: CHECK_INTERVAL }); while (true) { try { const pool = await getPoolState(); const currentTick = pool.current_tick_index; log('info', 'Cycle', { mode: 'monitor', tick: currentTick, sqrtPrice: pool.current_sqrt_price, }); simulatePositionCheck(currentTick); } catch (error) { log('error', 'Cycle failed', { error: error.message }); } log('info', `Next check in ${CHECK_INTERVAL / 1000}s`); await new Promise(r => setTimeout(r, CHECK_INTERVAL)); } } runAgent().catch(err => { log('error', 'Fatal', { error: err.message }); process.exit(1); });

Run it

node agent.js

You should see output like:

{"ts":"...","agent":"sui-cetus-yield","level":"info","message":"Agent starting","mode":"monitor","pool":"0xb8d7..."} {"ts":"...","agent":"sui-cetus-yield","level":"info","message":"Cycle","mode":"monitor","tick":69758} {"ts":"...","agent":"sui-cetus-yield","level":"event","message":"sim_position_opened","tickLower":69558,"tickUpper":69958}

The agent logs every check to sui-cetus-yield.log. Let it run for a few cycles to see how the price moves.

What just happened

The agent is watching the SUI/USDC pool on Cetus. Every 5 minutes it reads the current price (represented as a “tick” — an index into a price grid). It simulates holding a position and checks whether the price has drifted far enough to require a reposition.

No transactions are sent. No funds are needed. This is purely observational.

Next step

Ready to earn real fees? Phase 2 — Earn Fees adds transaction signing via WaaP CLI and opens a real position.

Last updated on