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:
@@ -0,0 +1,126 @@
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
const blessed = require('blessed');
|
||||
|
||||
let screen = null;
|
||||
let logBox = null;
|
||||
let statusBox = null;
|
||||
let active = false;
|
||||
|
||||
|
||||
export function initDashboard() {
|
||||
screen = blessed.screen({
|
||||
smartCSR: false, // avoid complex cursor escape sequences
|
||||
title: 'Polymarket Copy Trade',
|
||||
fullUnicode: true,
|
||||
forceUnicode: true,
|
||||
});
|
||||
|
||||
// ── Left panel: event log (60%) ────────────────────────────
|
||||
// blessed.log auto-tails (newest line always at bottom).
|
||||
// Keys are swallowed globally via screen.on('keypress') below,
|
||||
// so no raw escape codes can leak into this widget.
|
||||
logBox = blessed.log({
|
||||
parent: screen,
|
||||
label: ' LIVE EVENTS ',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '60%',
|
||||
height: '100%-1',
|
||||
border: { type: 'line' },
|
||||
tags: true,
|
||||
scrollable: true,
|
||||
alwaysScroll: true,
|
||||
mouse: false,
|
||||
keys: false,
|
||||
input: false,
|
||||
style: {
|
||||
border: { fg: 'cyan' },
|
||||
label: { fg: 'cyan', bold: true },
|
||||
},
|
||||
});
|
||||
|
||||
// ── Right panel: positions & balance (40%) ─────────────────
|
||||
statusBox = blessed.box({
|
||||
parent: screen,
|
||||
label: ' POSITIONS & BALANCE ',
|
||||
left: '60%',
|
||||
top: 0,
|
||||
width: '40%',
|
||||
height: '100%-1',
|
||||
border: { type: 'line' },
|
||||
tags: true,
|
||||
scrollable: false,
|
||||
input: false,
|
||||
clickable: false,
|
||||
style: {
|
||||
border: { fg: 'yellow' },
|
||||
label: { fg: 'yellow', bold: true },
|
||||
},
|
||||
content: '\n {gray-fg}Initializing...{/gray-fg}',
|
||||
});
|
||||
|
||||
// ── Bottom status bar ──────────────────────────────────────
|
||||
blessed.box({
|
||||
parent: screen,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: 1,
|
||||
tags: true,
|
||||
content:
|
||||
' {gray-fg}powered by{/gray-fg} {cyan-fg}@direkturcrypto{/cyan-fg} {gray-fg}terminal{/gray-fg}' +
|
||||
' {gray-fg}Ctrl+C / q = exit{/gray-fg}',
|
||||
style: { bg: 'black', fg: 'white' },
|
||||
});
|
||||
|
||||
// ── Capture ALL keypresses at screen level ─────────────────
|
||||
// This prevents any raw escape sequence from leaking into panels
|
||||
screen.on('keypress', (_ch, key) => {
|
||||
if (!key) return;
|
||||
if (key.full === 'C-c' || key.sequence === '\x03' || key.name === 'q') {
|
||||
screen.destroy();
|
||||
process.exit(0);
|
||||
}
|
||||
// every other key: swallowed here, never reaches any widget
|
||||
});
|
||||
|
||||
// Redirect raw console so nothing bypasses the TUI
|
||||
console.log = (...a) => appendLog(a.join(' '));
|
||||
console.info = (...a) => appendLog(a.join(' '));
|
||||
console.warn = (...a) => appendLog(`{yellow-fg}${a.join(' ')}{/yellow-fg}`);
|
||||
console.error = (...a) => appendLog(`{red-fg}${a.join(' ')}{/red-fg}`);
|
||||
|
||||
active = true;
|
||||
screen.render();
|
||||
|
||||
// Force a clean redraw after the event loop starts — fixes the
|
||||
// "looks messy until resized" issue common in blessed on first paint.
|
||||
setTimeout(() => {
|
||||
screen.alloc(); // reallocate internal screen buffer
|
||||
screen.render();
|
||||
}, 50);
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
/** Append a line to the live event log (auto-tails to newest) */
|
||||
export function appendLog(text) {
|
||||
if (!active || !logBox) {
|
||||
process.stdout.write(String(text) + '\n');
|
||||
return;
|
||||
}
|
||||
logBox.log(String(text));
|
||||
screen.render();
|
||||
}
|
||||
|
||||
/** Replace the right-panel content */
|
||||
export function updateStatus(content) {
|
||||
if (!active || !statusBox) return;
|
||||
statusBox.setContent(content);
|
||||
screen.render();
|
||||
}
|
||||
|
||||
export function isDashboardActive() {
|
||||
return active;
|
||||
}
|
||||
Reference in New Issue
Block a user