Skip to content

Usage

How to run, configure, and operate the bot day-to-day.

Last updated: 2026-05-13


npm scripts

Command Purpose When to use
npm install Install dependencies Once after clone, or when package.json changes
npm run encrypt-key Generate ./keystore.json from a raw private key Once during setup
npm run dryrun Run with DRY_RUN=true — no on-chain tx, full simulation Every time before going live; ≥24h continuous before flipping production
npm start Production run; honors NODE_ENV and DRY_RUN from .env When validation is complete
npm run dev node --watch — restarts on file changes Local code iteration only
npm test Run vitest in watch mode While developing tests
npm run test:run Run vitest once and exit CI, or to verify before commit
npm run lint ESLint on src/ Before commit
npm run audit npm audit excluding dev deps Periodically; before any release
npm run audit:evm-only Audit excluding optional Solana deps EVM-only deployments

Docker

docker compose build
docker compose up -d
docker compose logs -f

The compose file mounts .env and keystore.json from the host. Health is just process liveness — there is no HTTP endpoint. To stop:

docker compose down

⚠️ Single process per signer. Do not run docker compose up -d and npm start against the same wallet — the reentrancy guard is in-memory and you will hit nonce collisions / double-spends.


Environment variables

Grouped by purpose. All variables are validated by zod in 📁 src/config.js; the bot refuses to start on schema violation.

Global

Var Default Description
LOG_LEVEL info trace/debug/info/warn/error
DRY_RUN true If true, executors log intent without broadcasting
NODE_ENV production If production, raw EVM_PRIVATE_KEY is refused

Key management

See Getting Started → Set up your signer. Priority: Ledger > keystore > raw key.

Var Description
USE_LEDGER Enable Ledger signer
LEDGER_DERIVATION_PATH BIP44 path; 44'/60'/0'/0/0 = first account
KEYSTORE_PATH Path to encrypted JSON keystore
KEYSTORE_PASSWORD Optional; if blank, prompts at startup
EVM_PRIVATE_KEY Raw key (dev only — blocked in production)
SOLANA_PRIVATE_KEY Base58 secret key

RPC

Var Description
ETH_RPC_HTTP / ETH_RPC_WS Ethereum mainnet
BASE_RPC_HTTP / BASE_RPC_WS Base mainnet
SOLANA_RPC_HTTP / SOLANA_RPC_WS Solana mainnet-beta

WebSocket URLs are required for any chain you watch — the bot subscribes to events, it does not poll.

Triggers

Var Default Description
TRIGGER_PRICE_DROP_PCT 15 Remove if price drops ≥N% from entry
TRIGGER_PRICE_RISE_PCT 50 Remove if price rises ≥N% (take profit)
TRIGGER_MAX_AGE_SEC 0 Auto-remove after N seconds (0 = disabled)
TRIGGER_VOLUME_SPIKE_MULT 10 Fire if recent vol > N × rolling avg
TRIGGER_VOLUME_WINDOW_BLOCKS 50 Window size for volume average
TRIGGER_CUSTOM_EVENT_ADDRESS Contract to watch for custom log
TRIGGER_CUSTOM_EVENT_TOPIC Topic0 of the event
EMERGENCY_WALLET Dead-man's switch — see Safety

Execution safety

Var Default Description
MAX_SLIPPAGE_BPS 100 100 = 1%. Applied to amountMin calculations
MAX_GAS_GWEI 150 Hard cap; tx aborts before broadcast if (base+priority) exceeds
GAS_PRIORITY_GWEI 2 Priority fee component
TX_DEADLINE_SEC 600 V2 deadline param
MIN_LIQUIDITY_USD 50 Skip dust positions
TOKEN_BLACKLIST Comma-separated, lowercase. Enforced per-executor
AUTO_REVOKE_APPROVALS true After removal, allowance → 0

Rate limiting

Var Default Description
MAX_TX_PER_MINUTE 3 Token-bucket cap
MAX_TX_PER_HOUR 20 Same

Alerts

Var Description
TELEGRAM_BOT_TOKEN From @BotFather
TELEGRAM_CHAT_ID Numeric only — no leading colon
DISCORD_WEBHOOK_URL Server → channel webhook
ALERT_ON_TRIGGER Send when a trigger fires
ALERT_ON_SUCCESS Send when tx confirms
ALERT_ON_ERROR Send on failure

Positions config

POSITIONS_JSON is a JSON array. Each element describes one LP position to watch.

V2 (Uniswap V2, PancakeSwap V2, SushiSwap, …)

{
  "chain": "ethereum",
  "dex": "uniswap-v2",
  "type": "v2",
  "pair": "WETH/USDC",
  "address": "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc",
  "entryPrice": 1850.0,
  "triggers": { "priceDropPct": 10, "priceRisePct": 40 }
}
  • address = LP pair contract address.
  • entryPrice (optional) = override the auto-detected entry; used by percent triggers.
  • triggers (optional) = per-position overrides of the global trigger env vars.

V3 (Uniswap V3, PancakeSwap V3)

{
  "chain": "ethereum",
  "dex": "uniswap-v3",
  "type": "v3",
  "pair": "WETH/USDC",
  "tokenId": 123456,
  "poolAddress": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
  "feeTier": 500
}
  • tokenId = your NFT ID from the NonfungiblePositionManager.
  • poolAddress is required — the price feed listens on the pool, not the NFT.

Raydium (Solana)

{
  "chain": "solana",
  "dex": "raydium",
  "type": "amm-v4",
  "pair": "SOL/USDC",
  "address": "<pool-id>",
  "lpMint": "<lp-mint>"
}
  • type is amm-v4, cpmm, or clmm. The executor dispatches accordingly.

Telegram bot commands

If TELEGRAM_BOT_TOKEN is set, the bot listens for the following commands from the configured chat:

Command Effect
/status Reply with current monitored positions, prices, last trigger evaluation
/remove Manually trigger removal for all positions
/remove <label> Trigger removal for a single position whose pair matches <label>

⚠️ /remove is irreversible. There is no confirmation prompt — by design. If you don't want manual remote triggers, leave TELEGRAM_BOT_TOKEN blank.


Logs

Structured JSON via pino. Pipe through pino-pretty for human reading:

npm start | npx pino-pretty

The redaction list in 📁 src/logger.js masks: EVM_PRIVATE_KEY, SOLANA_PRIVATE_KEY, KEYSTORE_PASSWORD, TELEGRAM_BOT_TOKEN, DISCORD_WEBHOOK_URL, signer objects. If you add new sensitive env vars, update the redaction list. See Safety.