feat: full project overhaul — market maker, sniper, WebSocket watcher, terminal UI

- Rename project to polymarket-terminal
- Add Market Maker bot (src/mm.js) with on-chain CTF split/merge/redeem via Gnosis Safe
- Add Orderbook Sniper bot (src/sniper.js) with multi-asset GTC low-price orders
- Add WebSocket watcher (src/services/wsWatcher.js) for real-time RTDS trade events
- Add terminal dashboard UI (src/ui/dashboard.js) using blessed
- Add CTF contract helpers (src/services/ctf.js) for splitPosition, mergePositions, redeemPositions
- Add mmDetector, mmExecutor, sniperDetector, sniperExecutor services
- Add simStats utility for dry-run P&L tracking
- Translate all Indonesian-language strings to professional English across all files
- Rewrite README.md in English with full setup guide, configuration reference, and architecture overview
- Rewrite AGENT.MD in English as comprehensive AI agent and developer reference
- Update package.json name, description, scripts, and keywords

Co-Authored-By: direkturcrypto <direkturcrypto.x@mail3.me>
This commit is contained in:
direkturcrypto
2026-02-23 23:03:06 +07:00
parent 7c7fad45f3
commit 526076fe6e
22 changed files with 3485 additions and 558 deletions
+47 -6
View File
@@ -3,8 +3,8 @@ dotenv.config();
const config = {
// Wallet
privateKey: process.env.PRIVATE_KEY,
walletAddress: process.env.WALLET_ADDRESS,
privateKey: process.env.PRIVATE_KEY, // EOA private key (for signing only)
proxyWallet: process.env.PROXY_WALLET_ADDRESS, // Polymarket proxy wallet (deposit USDC here)
// Polymarket API (optional, auto-derived if empty)
clobApiKey: process.env.CLOB_API_KEY || '',
@@ -17,6 +17,9 @@ const config = {
dataHost: 'https://data-api.polymarket.com',
chainId: 137,
// Polygon RPC
polygonRpcUrl: process.env.POLYGON_RPC_URL || 'https://polygon-bor-rpc.publicnode.com',
// Trader to copy
traderAddress: process.env.TRADER_ADDRESS,
@@ -24,6 +27,7 @@ const config = {
sizeMode: process.env.SIZE_MODE || 'percentage', // "percentage" | "balance"
sizePercent: parseFloat(process.env.SIZE_PERCENT || '50'),
minTradeSize: parseFloat(process.env.MIN_TRADE_SIZE || '1'),
maxPositionSize: parseFloat(process.env.MAX_POSITION_SIZE || '10'),
// Auto sell
autoSellEnabled: process.env.AUTO_SELL_ENABLED === 'true',
@@ -32,8 +36,7 @@ const config = {
// Sell mode when copying sell
sellMode: process.env.SELL_MODE || 'market', // "market" | "limit"
// Polling intervals (seconds)
pollInterval: parseInt(process.env.POLL_INTERVAL || '15', 10) * 1000,
// Redeem interval (seconds)
redeemInterval: parseInt(process.env.REDEEM_INTERVAL || '60', 10) * 1000,
// Dry run
@@ -42,11 +45,37 @@ const config = {
// Retry settings
maxRetries: 5,
retryDelay: 3000,
// ── Market Maker ──────────────────────────────────────────────
mmAssets: (process.env.MM_ASSETS || 'btc')
.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean),
mmDuration: process.env.MM_DURATION || '5m', // '5m' or '15m'
mmTradeSize: parseFloat(process.env.MM_TRADE_SIZE || '5'), // USDC per side
mmSellPrice: parseFloat(process.env.MM_SELL_PRICE || '0.60'), // limit sell target
mmCutLossTime: parseInt( process.env.MM_CUT_LOSS_TIME || '60', 10), // seconds before close
mmMarketKeyword: process.env.MM_MARKET_KEYWORD || 'Bitcoin Up or Down',
mmEntryWindow: parseInt( process.env.MM_ENTRY_WINDOW || '45', 10), // max secs after open
mmPollInterval: parseInt( process.env.MM_POLL_INTERVAL || '10', 10) * 1000,
// ── Recovery Buy (after cut-loss) ─────────────────────────────
// When enabled: after cutting loss, monitor prices for 10s and
// market-buy the dominant side if it's above threshold and rising/stable.
mmRecoveryBuy: process.env.MM_RECOVERY_BUY === 'true',
mmRecoveryThreshold: parseFloat(process.env.MM_RECOVERY_THRESHOLD || '0.70'), // min price to qualify
mmRecoverySize: parseFloat(process.env.MM_RECOVERY_SIZE || '0'), // 0 = use mmTradeSize
// ── Orderbook Sniper ───────────────────────────────────────────
// Places tiny GTC limit BUY orders at a very low price on each side
// of ETH/SOL/XRP 5-minute markets — catches panic dumps near $0.
sniperAssets: (process.env.SNIPER_ASSETS || 'eth,sol,xrp')
.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean),
sniperPrice: parseFloat(process.env.SNIPER_PRICE || '0.01'), // $ per share
sniperShares: parseFloat(process.env.SNIPER_SHARES || '5'), // shares per side
};
// Validation
// Validation for copy-trade bot
export function validateConfig() {
const required = ['privateKey', 'walletAddress', 'traderAddress'];
const required = ['privateKey', 'proxyWallet', 'traderAddress'];
const missing = required.filter((key) => !config[key]);
if (missing.length > 0) {
throw new Error(`Missing required config: ${missing.join(', ')}. Check your .env file.`);
@@ -59,4 +88,16 @@ export function validateConfig() {
}
}
// Validation for market-maker bot
export function validateMMConfig() {
const required = ['privateKey', 'proxyWallet'];
const missing = required.filter((key) => !config[key]);
if (missing.length > 0) {
throw new Error(`Missing required config: ${missing.join(', ')}. Check your .env file.`);
}
if (config.mmTradeSize <= 0) throw new Error('MM_TRADE_SIZE must be > 0');
if (config.mmSellPrice <= 0 || config.mmSellPrice >= 1)
throw new Error('MM_SELL_PRICE must be between 0 and 1');
}
export default config;