Skip to content

Safety

This bot signs and broadcasts irreversible mainnet transactions. The defaults are conservative but not foolproof. Read this whole page before any change that touches executors, slippage, gas, approvals, or signers.

Last updated: 2026-05-13


1. The risk profile

What this bot can do badly if misconfigured: - Over-slip on removal — accept far less than fair value because MAX_SLIPPAGE_BPS is too high. - Burn gas in a fee spike — broadcast at any price because MAX_GAS_GWEI is too high. - Get sandwich-attacked — public-mempool exposure on Ethereum mainnet. - Approve a malicious LP token — interact with a fake pool. Mitigated by TOKEN_BLACKLIST and V2 honeypot pre-flight, but not eliminated. - Drain to the wrong addressEMERGENCY_WALLET is treated as trusted; if it's compromised, all positions get drained. - Get front-run on the /remove Telegram command — anyone with Telegram chat access can trigger a tx.

Treat the wallet running this bot as a hot wallet. Hold only what you're willing to lose to a worst-case bug.


2. Safety knobs (env vars)

Var Purpose Recommended starting value Notes
DRY_RUN Simulate only true for any new config Set to false only after ≥24h clean dry run
MAX_SLIPPAGE_BPS Max accepted slippage on removal 100 (1%) Tighten for stable pairs (e.g., 30); loosen only for very thin pools
MAX_GAS_GWEI Hard cap; tx aborts above this 150 mainnet, 5 Base Tx aborts before broadcast — does not retry until gas drops
GAS_PRIORITY_GWEI Priority fee 2 mainnet, 0.001 Base Too low → tx stuck pending
TX_DEADLINE_SEC V2 removeLiquidity deadline 600 Tx reverts if not mined within this window — protects against stale prices
MIN_LIQUIDITY_USD Skip dust positions 50 Below this, gas exceeds redeemable value
TOKEN_BLACKLIST Comma-separated lowercase addresses; never interacted with tokens you know are scams Per-executor check; mirror in any new executor
AUTO_REVOKE_APPROVALS Set LP allowance → 0 after removal true Cheap insurance against future router exploits
MAX_TX_PER_MINUTE Token-bucket cap 3 Cascading triggers cannot run away
MAX_TX_PER_HOUR Same 20 Same
EMERGENCY_WALLET Dead-man's switch wallet dedicated cold address See §3

3. Emergency wallet

A dead-man's switch. Designate a wallet other than the one running this bot. If you ever spot trouble (suspected key compromise, anomaly on the pool, you're away from a keyboard), send any transaction from the emergency wallet — even a 1-wei self-transfer. The bot, on seeing that tx confirmed in a block, fires removal on every position immediately.

How it works internally

  • eventMonitor.js subscribes to new blocks and scans block.prefetchedTransactions[*].from.
  • If from.toLowerCase() === EMERGENCY_WALLET.toLowerCase(), it emits an emergency trigger to the engine.
  • The trigger engine bypasses all other predicates and dispatches removal for every position.

Trade-offs

Property Value
Latency ~12 s (one block confirmation)
False positives Low (you control the wallet)
Mempool spoofing risk None — we only check confirmed inclusion
MEV race None — by the time we see the tx, it's already mined
Cost to trigger One trivial tx fee from the emergency wallet

Operational notes

  • The emergency wallet must hold a small amount of native gas. Refill periodically.
  • Keep its private key on hardware or cold storage.
  • Do not reuse this wallet for anything else.
  • Test it: in dry-run mode, send a tx from the emergency wallet and confirm the bot logs emergency trigger fired for every position.

4. Signers and keys

Priority order in 📁 src/security/keystore.js:

  1. Ledger — preferred for non-trivial position size. Every removal tx requires a physical button press, which kills any silent-drain attack but means you must be near the device.
  2. Encrypted keystore./keystore.json produced by npm run encrypt-key. Password protects the key at rest. Suitable for VPS / Docker deployments.
  3. Raw private keyEVM_PRIVATE_KEY in .env. Refused in NODE_ENV=production. Dev-only.

Rules

  • Never commit .env or keystore.json. Both are in .gitignore — keep it that way.
  • Rotate keys after any suspected exposure (you pasted in chat, plugged USB into untrusted host, etc.).
  • Use a dedicated wallet for this bot. Do not use your main wallet.
  • Solana has no Ledger / keystore support yet — use a separate hot wallet, minimal SOL.

5. Gas cap behavior

utils/gas.js::gasAboveCap() is called inside every EVM executor before the broadcast. If (baseFee + priorityFee) > MAX_GAS_GWEI:

  • Tx is not broadcast.
  • Logged as a warning, not an error.
  • The trigger remains in firing state until the executor returns; on return, the engine calls markFailed, which clears firing. The next trigger event will re-evaluate.

This is intentional — the bot does not retry until you drop the cap or gas drops. During severe congestion, you may need to manually raise MAX_GAS_GWEI and restart.


6. Slippage math

utils/slippage.js::applySlippage(amount, bps) is bigint-only. Mixing Number and bigint will throw at runtime. Reads:

amountMin = amount - (amount * BigInt(bps)) / 10000n
  • bps=0 → no slippage tolerance (often reverts; use only on stable pairs in calm conditions).
  • bps=10000 → 100% slippage; accepts any amount. Never use this in prod.
  • V3 derives amount0Min/amount1Min from a decreaseLiquidity.staticCall so the slippage is applied to a realistic expectation, not the theoretical max.

7. MEV

On Ethereum mainnet, the public mempool is hostile. Any sufficiently large removeLiquidity tx may get sandwiched.

Mitigations the bot already provides: - Slippage cap (MAX_SLIPPAGE_BPS) — sandwicher cannot extract more than this. - Honeypot pre-flight (V2) — staticCall simulation aborts if the pool refuses removal.

Mitigations you must add yourself for non-trivial size: - Use a private mempool RPC — Flashbots Protect, MEV Blocker, or BloXroute. Set ETH_RPC_HTTP to the private endpoint; keep ETH_RPC_WS on a public endpoint for monitoring. - Split large positions into multiple smaller LP tokens and stagger removal — reduces the per-tx attacker payoff. - Avoid trigger thresholds that everyone else uses (e.g., round 10%/20%/30% drops) — those are easy to front-run.

L2 / Solana are less hostile but not immune; the same private-RPC pattern helps where available.


8. Logging

pino logger in 📁 src/logger.js uses a redaction list:

redact: ['EVM_PRIVATE_KEY', 'SOLANA_PRIVATE_KEY', 'KEYSTORE_PASSWORD',
         'TELEGRAM_BOT_TOKEN', 'DISCORD_WEBHOOK_URL', '*.signer', '*.wallet', /* ... */]

Rules

  • If you add a new sensitive env var, add it to the redact list. Tests do not catch this; rely on code review.
  • Never log a Wallet or Keypair directly. Log signer.address or keypair.publicKey.toBase58().
  • Default log level for production is info. debug/trace may emit pool addresses + amounts that, while not secrets, leak strategy.

9. Blacklist

TOKEN_BLACKLIST is enforced per executor, not globally registered. Every executeXxxRemoval calls _assertNotBlacklisted([token0, token1]) against config.blacklist before submitting.

When adding a new DEX, copy this pattern. If you forget, blacklist entries silently do nothing on that DEX.

What to put on the blacklist

  • Known scam tokens / rugs you've identified.
  • Tokens with fee-on-transfer mechanics that break LP math.
  • Tokens that have been used as honeypots against pools you watched.
  • Stale tokens whose contracts have been paused or had ownership transferred to suspicious addresses.

10. Pre-deploy safety checklist

Run through this every time you change executors, signers, chains, or risk parameters:

  • Unit tests green (npm run test:run)
  • Lint clean (npm run lint)
  • Audit reviewed (npm run audit)
  • .env reviewed line-by-line — no secrets in plain text where avoidable
  • DRY_RUN=true until validated
  • MAX_SLIPPAGE_BPS set per pool type (tight for stable, looser for volatile, never above ~300 bps without explicit reason)
  • MAX_GAS_GWEI set per chain
  • EMERGENCY_WALLET set and funded with gas
  • TOKEN_BLACKLIST reviewed
  • Signer = Ledger or encrypted keystore (raw key blocked in production — but verify)
  • Logger redaction list still covers any new env vars
  • Telegram alerts test-pinged
  • ≥24 h continuous dry run, no unhandled errors
  • Sensitivity probe completed (forced trigger, verified executor logged correct tx)
  • Manual /remove test completed in dry-run
  • One person standing by for the first hour live

If you can't tick every box, stay on dry-run.