2026-02-22 15:38:13 +07:00
|
|
|
import dotenv from 'dotenv';
|
|
|
|
|
dotenv.config();
|
|
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
|
// Wallet
|
2026-02-23 23:03:06 +07:00
|
|
|
privateKey: process.env.PRIVATE_KEY, // EOA private key (for signing only)
|
|
|
|
|
proxyWallet: process.env.PROXY_WALLET_ADDRESS, // Polymarket proxy wallet (deposit USDC here)
|
2026-02-22 15:38:13 +07:00
|
|
|
|
|
|
|
|
// Polymarket API (optional, auto-derived if empty)
|
|
|
|
|
clobApiKey: process.env.CLOB_API_KEY || '',
|
|
|
|
|
clobApiSecret: process.env.CLOB_API_SECRET || '',
|
|
|
|
|
clobApiPassphrase: process.env.CLOB_API_PASSPHRASE || '',
|
|
|
|
|
|
|
|
|
|
// Polymarket endpoints
|
|
|
|
|
clobHost: 'https://clob.polymarket.com',
|
|
|
|
|
gammaHost: 'https://gamma-api.polymarket.com',
|
|
|
|
|
dataHost: 'https://data-api.polymarket.com',
|
|
|
|
|
chainId: 137,
|
|
|
|
|
|
2026-02-23 23:03:06 +07:00
|
|
|
// Polygon RPC
|
|
|
|
|
polygonRpcUrl: process.env.POLYGON_RPC_URL || 'https://polygon-bor-rpc.publicnode.com',
|
|
|
|
|
|
2026-02-22 15:38:13 +07:00
|
|
|
// Trader to copy
|
|
|
|
|
traderAddress: process.env.TRADER_ADDRESS,
|
|
|
|
|
|
|
|
|
|
// Trade sizing
|
|
|
|
|
sizeMode: process.env.SIZE_MODE || 'percentage', // "percentage" | "balance"
|
|
|
|
|
sizePercent: parseFloat(process.env.SIZE_PERCENT || '50'),
|
|
|
|
|
minTradeSize: parseFloat(process.env.MIN_TRADE_SIZE || '1'),
|
2026-02-23 23:03:06 +07:00
|
|
|
maxPositionSize: parseFloat(process.env.MAX_POSITION_SIZE || '10'),
|
2026-02-22 15:38:13 +07:00
|
|
|
|
|
|
|
|
// Auto sell
|
|
|
|
|
autoSellEnabled: process.env.AUTO_SELL_ENABLED === 'true',
|
|
|
|
|
autoSellProfitPercent: parseFloat(process.env.AUTO_SELL_PROFIT_PERCENT || '10'),
|
|
|
|
|
|
|
|
|
|
// Sell mode when copying sell
|
|
|
|
|
sellMode: process.env.SELL_MODE || 'market', // "market" | "limit"
|
|
|
|
|
|
2026-02-23 23:03:06 +07:00
|
|
|
// Redeem interval (seconds)
|
2026-02-22 15:38:13 +07:00
|
|
|
redeemInterval: parseInt(process.env.REDEEM_INTERVAL || '60', 10) * 1000,
|
|
|
|
|
|
|
|
|
|
// Dry run
|
|
|
|
|
dryRun: process.env.DRY_RUN === 'true',
|
|
|
|
|
|
|
|
|
|
// Retry settings
|
|
|
|
|
maxRetries: 5,
|
|
|
|
|
retryDelay: 3000,
|
2026-02-23 23:03:06 +07:00
|
|
|
|
2026-02-25 03:18:57 +07:00
|
|
|
// Skip buy if market closes within this many seconds (default 5 minutes)
|
|
|
|
|
minMarketTimeLeft: parseInt(process.env.MIN_MARKET_TIME_LEFT || '300', 10),
|
|
|
|
|
|
2026-02-25 04:30:12 +07:00
|
|
|
// Seconds to wait for a GTC limit order to fill when FAK finds no liquidity
|
|
|
|
|
// (happens when copying trades into "next market" before sellers arrive)
|
|
|
|
|
gtcFallbackTimeout: parseInt(process.env.GTC_FALLBACK_TIMEOUT || '60', 10),
|
|
|
|
|
|
2026-02-23 23:03:06 +07:00
|
|
|
// ── Market Maker ──────────────────────────────────────────────
|
2026-03-01 16:24:25 +07:00
|
|
|
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,
|
|
|
|
|
mmAdaptiveCL: process.env.MM_ADAPTIVE_CL !== 'false', // true = adaptive, false = legacy immediate market-sell
|
|
|
|
|
mmAdaptiveMinCombined: parseFloat(process.env.MM_ADAPTIVE_MIN_COMBINED || '1.20'), // min combined sell (both legs) to qualify for limit
|
|
|
|
|
mmAdaptiveMonitorSec: parseInt(process.env.MM_ADAPTIVE_MONITOR_SEC || '5', 10),
|
2026-02-23 23:03:06 +07:00
|
|
|
|
|
|
|
|
// ── 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.
|
2026-03-01 16:24:25 +07:00
|
|
|
mmRecoveryBuy: process.env.MM_RECOVERY_BUY === 'true',
|
2026-02-23 23:03:06 +07:00
|
|
|
mmRecoveryThreshold: parseFloat(process.env.MM_RECOVERY_THRESHOLD || '0.70'), // min price to qualify
|
2026-03-01 16:24:25 +07:00
|
|
|
mmRecoverySize: parseFloat(process.env.MM_RECOVERY_SIZE || '0'), // 0 = use mmTradeSize
|
2026-02-23 23:03:06 +07:00
|
|
|
|
|
|
|
|
// ── Orderbook Sniper ───────────────────────────────────────────
|
2026-03-03 14:52:37 +07:00
|
|
|
// 3-tier strategy: places GTC limit BUY orders at 3c, 2c, and 1c
|
|
|
|
|
// Tier 1 (3c): smallest size | Tier 2 (2c): medium size | Tier 3 (1c): largest size
|
|
|
|
|
// Min 5 shares per tier, total = SNIPER_MAX_SHARES_PER_SIDE
|
2026-02-23 23:03:06 +07:00
|
|
|
sniperAssets: (process.env.SNIPER_ASSETS || 'eth,sol,xrp')
|
2026-03-01 16:24:25 +07:00
|
|
|
.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean),
|
2026-03-03 14:52:37 +07:00
|
|
|
sniperTierPrices: [
|
|
|
|
|
parseFloat(process.env.SNIPER_TIER1_PRICE || '0.03'), // high price, small size
|
|
|
|
|
parseFloat(process.env.SNIPER_TIER2_PRICE || '0.02'), // mid price, medium size
|
|
|
|
|
parseFloat(process.env.SNIPER_TIER3_PRICE || '0.01'), // low price, large size
|
|
|
|
|
],
|
|
|
|
|
sniperMaxShares: parseFloat(process.env.SNIPER_MAX_SHARES || '15'), // max total per side
|
|
|
|
|
sniperMinSharesPerTier: 5, // minimum shares for each tier
|
2026-03-01 16:24:25 +07:00
|
|
|
|
2026-03-01 19:07:57 +07:00
|
|
|
// ── Sniper Schedule (UTC+8) ────────────────────────────────────
|
|
|
|
|
// Per-asset session windows. Format: SNIPER_SCHEDULE_{ASSET}=HH:MM-HH:MM,HH:MM-HH:MM
|
|
|
|
|
// Assets without a schedule are always active.
|
|
|
|
|
sniperSchedule: (() => {
|
|
|
|
|
const schedule = {};
|
|
|
|
|
const prefix = 'SNIPER_SCHEDULE_';
|
|
|
|
|
for (const [key, value] of Object.entries(process.env)) {
|
|
|
|
|
if (key.startsWith(prefix) && value) {
|
|
|
|
|
const asset = key.slice(prefix.length).toLowerCase();
|
|
|
|
|
schedule[asset] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return schedule;
|
|
|
|
|
})(),
|
|
|
|
|
|
2026-03-01 16:24:25 +07:00
|
|
|
// ── Proxy (Polymarket API only, NOT Polygon RPC) ──────────────
|
2026-03-01 19:07:57 +07:00
|
|
|
// Supports HTTP/HTTPS. Example: http://user:pass@host:port
|
2026-03-01 16:24:25 +07:00
|
|
|
proxyUrl: process.env.PROXY_URL || '',
|
2026-02-22 15:38:13 +07:00
|
|
|
};
|
|
|
|
|
|
2026-02-23 23:03:06 +07:00
|
|
|
// Validation for copy-trade bot
|
2026-02-22 15:38:13 +07:00
|
|
|
export function validateConfig() {
|
2026-02-23 23:03:06 +07:00
|
|
|
const required = ['privateKey', 'proxyWallet', 'traderAddress'];
|
2026-02-22 15:38:13 +07:00
|
|
|
const missing = required.filter((key) => !config[key]);
|
|
|
|
|
if (missing.length > 0) {
|
|
|
|
|
throw new Error(`Missing required config: ${missing.join(', ')}. Check your .env file.`);
|
|
|
|
|
}
|
|
|
|
|
if (!['percentage', 'balance'].includes(config.sizeMode)) {
|
|
|
|
|
throw new Error(`Invalid SIZE_MODE: ${config.sizeMode}. Use "percentage" or "balance".`);
|
|
|
|
|
}
|
|
|
|
|
if (!['market', 'limit'].includes(config.sellMode)) {
|
|
|
|
|
throw new Error(`Invalid SELL_MODE: ${config.sellMode}. Use "market" or "limit".`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 23:03:06 +07:00
|
|
|
// 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');
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 15:38:13 +07:00
|
|
|
export default config;
|