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¶
The compose file mounts .env and keystore.json from the host. Health is just process liveness — there is no HTTP endpoint. To stop:
⚠️ Single process per signer. Do not run
docker compose up -dandnpm startagainst 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.poolAddressis 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>"
}
typeisamm-v4,cpmm, orclmm. 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> |
⚠️
/removeis irreversible. There is no confirmation prompt — by design. If you don't want manual remote triggers, leaveTELEGRAM_BOT_TOKENblank.
Logs¶
Structured JSON via pino. Pipe through pino-pretty for human reading:
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.