A professional, modular arbitrage trading bot for Polymarket with Telegram control interface.
The PolyArb Engine is a Rust-based trading bot that scans for arbitrage opportunities on Polymarket and executes trades automatically. It features:
- Modular Architecture: Clean separation of concerns (config, types, clients, engine, execution, utils)
- Telegram Bot Interface: Easy-to-use control panel with inline buttons
- 24/7 Operation: Designed to run continuously on a VPS
- Real-time Monitoring: Live orderbook feeds and balance tracking
- Cloudflare Bypass: Uses FlareSolverr with residential proxies to bypass Cloudflare protection
- Varied Profit Thresholds: Randomly selects profit thresholds (0.8%, 1.5%, 2.0%, 2.5%) per order
- Rust (latest stable version)
- Cargo (comes with Rust)
- Ubuntu/Debian VPS (for production)
- Polymarket API credentials
- Telegram Bot Token (from @BotFather)
- Docker (for FlareSolverr)
- Residential Proxy Service (for Cloudflare bypass)
poly-arb-engine/
├── Cargo.toml # Dependencies
├── .env # Configuration (create from .env.example)
├── poly-bot.service # Systemd service file
├── src/
│ ├── main.rs # Entry point
│ ├── config/ # Settings & environment loading
│ ├── types/ # Data models
│ ├── clients/ # Network layer (Polymarket, Polygon, Telegram)
│ ├── engine/ # Logic layer (discovery, strategy, risk)
│ ├── execution/ # Action layer (signing, trading)
│ ├── telegram/ # Telegram bot
│ └── utils/ # Helpers
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustc --versioncd poly-arb-engine
cargo build --releasecurl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
docker --versionCreate a .env file in the project root with the following variables:
# Blockchain Configuration
RPC_URL=https://polygon-rpc.com
WS_URL=wss://clob.polymarket.com
PRIVATE_KEY=0x_your_private_key_here
# Polymarket API Credentials (L2 Authentication)
POLY_API_KEY=your_api_key_here
POLY_API_SECRET=your_api_secret_here
POLY_PASSPHRASE=your_passphrase_here
# Telegram Bot Configuration
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
TELEGRAM_CHAT_ID=your_chat_id_here# Cloudflare Bypass (MANDATORY for VPS deployment)
FLARESOLVERR_URL=http://localhost:8191
FLARESOLVERR_PROXY=http://username:password@residential-proxy.com:8080
# Cloudflare Cookie (alternative method, less reliable)
CLOUDFLARE_COOKIE="cookie1=value1; cookie2=value2"
# Safety Settings
MAX_USDC_PER_MARKET=100.0
MAX_WALLET_PERCENT_PER_ORDER=0.1
ONE_MARKET_AT_A_TIME=false
MIN_PROFIT_PCT=0.2
# Trading Mode
TEST_MODE=false
DRY_RUN=false
MANUAL_TRADING_ENABLED=true- Open Polymarket in your browser and log in
- Open Developer Tools (F12)
- Go to Network tab
- Place a small test trade or view your portfolio
- Find requests to
clob.polymarket.com - In the request headers, find
x-api-keyand copy the value - Add to
.envasPOLY_API_KEY
Note: Manual extraction only gives you the API key. You will need to obtain the secret and passphrase through Polymarket's official API or by using their official client libraries (py-clob-client for Python or clob-client for TypeScript).
- Open Telegram and search for @BotFather
- Send
/newbotand follow instructions - Save the bot token provided
- Add token to
.envasTELEGRAM_BOT_TOKEN
To get your Chat ID:
- Send
/startto your bot - Run:
curl "https://api.telegram.org/botYOUR_TOKEN/getUpdates" - Find the
chat.idin the response - Add to
.envasTELEGRAM_CHAT_ID
Cloudflare blocks datacenter IPs (VPS, AWS, DigitalOcean, etc.) with hard CAPTCHAs. Your VPS IP is flagged as "Low Reputation" while residential IPs are "High Reputation."
You MUST use a residential proxy to make your VPS appear as a home internet connection. Datacenter proxies will still be blocked.
Recommended providers:
- Bright Data (brightdata.com) - Premium, ~$500/month
- Oxylabs (oxylabs.io) - Premium, ~$300/month
- Smartproxy (smartproxy.com) - Budget-friendly, ~$150/month
For trading bots, you typically need ~1-5GB/month depending on trade frequency.
Get your proxy credentials in the format:
http://username:password@proxy-host:port
FlareSolverr is a proxy server that runs a headless Chrome browser to automatically solve Cloudflare challenges.
# Run FlareSolverr in Docker
docker run -d \
--name=flaresolverr \
-p 8191:8191 \
-e LOG_LEVEL=info \
--restart unless-stopped \
ghcr.io/flaresolverr/flaresolverr:latest
# Verify it's running
docker ps | grep flaresolverr
# Check logs
docker logs flaresolverrTest FlareSolverr:
curl -X POST http://localhost:8191/v1 \
-H "Content-Type: application/json" \
-d '{
"cmd": "request.get",
"url": "https://clob.polymarket.com",
"maxTimeout": 60000
}'Add to your .env file:
FLARESOLVERR_URL=http://localhost:8191
FLARESOLVERR_PROXY=http://username:password@residential-proxy.com:8080If you prefer to configure the proxy at the Docker level:
docker stop flaresolverr
docker rm flaresolverr
docker run -d --name=flaresolverr -p 8191:8191 \
-e PROXY="http://username:password@residential-proxy.com:8080" \
-e LOG_LEVEL=info \
--restart unless-stopped \
ghcr.io/flaresolverr/flaresolverr:latest- Bot makes POST request → Routes through FlareSolverr (if
FLARESOLVERR_URLis set) - FlareSolverr → Uses residential proxy → Opens headless Chrome → Solves Cloudflare challenge
- Response → Returns 200 OK with order ID
After setup, watch the logs. You should see:
- "Routing request through FlareSolverr (may take 10-30 seconds)..."
- "Using proxy: http://..."
- "Order placed successfully! Order ID: ..."
If you still see 403 Forbidden:
- Check proxy credentials are correct
- Verify proxy is residential (not datacenter)
- Try a different proxy IP from your provider
- Contact proxy provider support
If you cannot use a residential proxy, you can try extracting a Cloudflare cookie manually using browser developer tools:
- Open Polymarket in your browser
- Open Developer Tools (F12)
- Go to Application/Storage tab
- Find Cookies for
polymarket.com - Copy the
cf_clearancecookie value - Add to
.envas:CLOUDFLARE_COOKIE="cf_clearance=value; other_cookies=values"
Note: Cookies expire after a few hours/days and must be refreshed. The residential proxy method is more reliable.
# Run in development mode
cargo run
# Run release binary
./target/release/poly-arb-engine# Upload binary
scp ./target/release/poly-arb-engine root@your_vps_ip:/root/poly-arb-engine/
# Upload .env file
scp ./.env root@your_vps_ip:/root/poly-arb-engine/
# Upload service file
scp ./poly-bot.service root@your_vps_ip:/root/poly-arb-engine/ssh root@your_vps_ip
cd /root/poly-arb-engine
# Make binary executable
chmod +x poly-arb-engine
# Verify .env file exists
ls -la .env# Copy service file
sudo cp poly-bot.service /etc/systemd/system/
# Reload systemd
sudo systemctl daemon-reload
# Enable service (start on boot)
sudo systemctl enable poly-bot
# Start the service
sudo systemctl start poly-bot# Check status
sudo systemctl status poly-bot
# View logs
sudo journalctl -u poly-bot -fSet DRY_RUN=true in .env to prevent any orders from being placed. Useful for testing:
DRY_RUN=trueSet TEST_MODE=true to place real orders but with deep limits (95% discount) that won't fill immediately:
TEST_MODE=trueThis allows you to verify order placement on the Polymarket website without risking immediate fills.
Leave both DRY_RUN and TEST_MODE unset or set to false for live trading.
The bot uses varied profit thresholds to make behavior less predictable. For each order evaluation, the bot randomly selects from:
- 0.8%
- 1.5%
- 2.0%
- 2.5%
Orders are only placed if the expected profit meets or exceeds the randomly selected threshold.
Configure risk management in .env:
# Maximum USDC to risk per market
MAX_USDC_PER_MARKET=100.0
# Maximum percentage of wallet to use per order (0.0-1.0)
MAX_WALLET_PERCENT_PER_ORDER=0.1
# Only trade one market at a time
ONE_MARKET_AT_A_TIME=false# Start bot
sudo systemctl start poly-bot
# Stop bot
sudo systemctl stop poly-bot
# Restart bot
sudo systemctl restart poly-bot
# Check status
sudo systemctl status poly-bot
# Enable on boot
sudo systemctl enable poly-bot
# Disable on boot
sudo systemctl disable poly-bot# View live logs
sudo journalctl -u poly-bot -f
# View recent logs (last 100 lines)
sudo journalctl -u poly-bot -n 100
# View logs from today
sudo journalctl -u poly-bot --since today- Clear message queue: Have user stop/restart bot in Telegram
- Check for multiple instances:
ps aux | grep poly-arb-engine - Verify token: Check token in
.envmatches BotFather - Check privacy mode: Disable in BotFather if enabled
- Check logs:
sudo journalctl -u poly-bot -ffor errors
- Verify FlareSolverr is running:
docker ps | grep flaresolverr - Check proxy credentials: Verify
FLARESOLVERR_PROXYin.envis correct - Verify proxy is residential: Datacenter proxies will still be blocked
- Test FlareSolverr directly: Use the curl command in the FlareSolverr setup section
- Check FlareSolverr logs:
docker logs flaresolverr
- Check binary:
ls -lh /root/poly-arb-engine/poly-arb-engine - Check permissions:
chmod +x /root/poly-arb-engine/poly-arb-engine - Check logs:
sudo journalctl -u poly-bot -n 50 - Test manually: Run binary directly to see errors
- Verify paths: Ensure all paths in service file are correct
- Verify API credentials: Check
POLY_API_KEY,POLY_API_SECRET,POLY_PASSPHRASEin.env - Regenerate credentials: Use Polymarket's official client libraries or contact Polymarket support
- Check private key: Ensure
PRIVATE_KEYmatches the wallet used to generate API credentials - Restart bot:
sudo systemctl restart poly-bot
- Verify RPC URL: Check
RPC_URLin.envis correct - Verify private key: Ensure
PRIVATE_KEYis correct (starts with 0x) - Check network: Ensure VPS can reach Polygon RPC endpoint
- Check wallet: Verify wallet has funds on Polygon network
- Never commit
.envfile - it's in.gitignore - API keys can only trade, not withdraw (Polymarket restriction)
- Private key is secure - only used for signing transactions
- Telegram communication is encrypted
- Keep
.envfile permissions restricted:chmod 600 .env - Use residential proxies from reputable providers
- Rotate API credentials periodically
The engine is designed to run on a Residential IP to bypass Cloudflare bot detection naturally, without needing expensive heavy-duty proxies.
graph TD
%% Nodes
User[Local Machine / Residential IP]
subgraph "Rust Engine Core"
Scanner[Market Scanner]
Strategy[Strategy Module<br/>Negative Risk Fishing]
Signer[EIP-712 Signer]
Safety[Safety & Balance Checks]
end
subgraph "External Infrastructure"
PolyAPI[Polymarket CLOB API<br/>Cloudflare Protected]
Blockchain[Polygon RPC<br/>On-Chain Data]
end
%% Flow
User -->|Starts| Scanner
Scanner -->|Fetch Markets| PolyAPI
PolyAPI -->|Market Data| Strategy
Strategy -->|1. Identify Opportunity| Safety
Safety -->|2. Verify Balance| Blockchain
Safety -->|3. Approve Trade| Signer
Signer -->|4. Sign Order Local Key| Signer
Signer -->|5. Submit Signed Order| PolyAPI
PolyAPI -->|6. Execution Report| Safety
Safety -->|7. Auto-Redeem Winnings| Blockchain
%% Styling
style User fill:#d4f1f4,stroke:#333,stroke-width:2px
style Signer fill:#ffcccb,stroke:#333,stroke-width:2px
style PolyAPI fill:#fffbe7,stroke:#f66,stroke-width:2px,stroke-dasharray: 5 5
The bot follows a clean, modular architecture:
- config/: Configuration management and environment variables
- types/: Data structures for markets, orderbooks, responses
- clients/: External API connections (Polymarket, Polygon, Telegram)
- engine/: Core trading logic (discovery, strategy, risk)
- execution/: Trade execution and transaction signing
- telegram/: User interface and command handling
- utils/: Helper functions and logging
This structure makes it easy to:
- Change strategies without touching network code
- Swap API providers without changing logic
- Test components in isolation
- Maintain and extend functionality
The poly-bot.service file includes:
- WorkingDirectory:
/root/poly-arb-engine(where .env file is located) - EnvironmentFile: Loads variables from
.envfile - Restart: Always (auto-restart on failure)
- Logging: All output to journalctl
Key settings:
[Service]
WorkingDirectory=/root/poly-arb-engine
EnvironmentFile=/root/poly-arb-engine/.env
ExecStart=/root/poly-arb-engine/poly-arb-engine
Restart=always
RestartSec=10Important: The EnvironmentFile directive is critical - it ensures systemd loads all environment variables from .env before starting the process.
curl "https://api.telegram.org/botYOUR_TOKEN/getMe"curl -X POST "https://api.telegram.org/botYOUR_TOKEN/sendMessage" \
-d chat_id=YOUR_CHAT_ID \
-d text="Test message"curl -X POST http://localhost:8191/v1 \
-H "Content-Type: application/json" \
-d '{
"cmd": "request.get",
"url": "https://clob.polymarket.com",
"maxTimeout": 60000
}'This software is for educational purposes only. Trading cryptocurrencies and binary options involves significant risk. The authors are not responsible for any financial losses incurred while using this bot. Review the code before running with real funds.
Use at your own risk. Always test thoroughly in TEST_MODE or DRY_RUN mode before using real funds.
MIT License
Copyright (c) 2025 PolyArb Engine Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For issues or questions, check the logs first:
sudo journalctl -u poly-bot -fMost issues can be resolved by:
- Checking service status
- Verifying
.envconfiguration - Reviewing logs for errors
- Testing Telegram token validity
- Verifying FlareSolverr and proxy setup