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 address — EMERGENCY_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.jssubscribes to new blocks and scansblock.prefetchedTransactions[*].from.- If
from.toLowerCase() === EMERGENCY_WALLET.toLowerCase(), it emits anemergencytrigger 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 firedfor every position.
4. Signers and keys¶
Priority order in 📁 src/security/keystore.js:
- 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.
- Encrypted keystore —
./keystore.jsonproduced bynpm run encrypt-key. Password protects the key at rest. Suitable for VPS / Docker deployments. - Raw private key —
EVM_PRIVATE_KEYin.env. Refused inNODE_ENV=production. Dev-only.
Rules¶
- Never commit
.envorkeystore.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
firingstate until the executor returns; on return, the engine callsmarkFailed, which clearsfiring. 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:
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/amount1Minfrom adecreaseLiquidity.staticCallso 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
WalletorKeypairdirectly. Logsigner.addressorkeypair.publicKey.toBase58(). - Default log level for production is
info.debug/tracemay 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) -
.envreviewed line-by-line — no secrets in plain text where avoidable -
DRY_RUN=trueuntil validated -
MAX_SLIPPAGE_BPSset per pool type (tight for stable, looser for volatile, never above ~300 bps without explicit reason) -
MAX_GAS_GWEIset per chain -
EMERGENCY_WALLETset and funded with gas -
TOKEN_BLACKLISTreviewed - 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
/removetest completed in dry-run - One person standing by for the first hour live
If you can't tick every box, stay on dry-run.