simplify: use HttpsProxyAgent directly for proxy routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
direkturcrypto
2026-03-01 22:33:45 +07:00
co-authored by Claude Opus 4.6
parent 048d4433c2
commit f5c777ec0c
4 changed files with 215 additions and 206 deletions
+3 -3
View File
@@ -9,9 +9,6 @@
* For the TUI dashboard version, use: npm run sniper-tui
*/
// Load proxy patch BEFORE any other imports (must patch https before axios is loaded)
import './utils/proxy-patch.cjs';
import { validateMMConfig } from './config/index.js';
import config from './config/index.js';
import logger from './utils/logger.js';
@@ -21,6 +18,9 @@ import { executeSnipe } from './services/sniperExecutor.js';
import { redeemSniperPositions } from './services/ctf.js';
import { getSchedule, isAssetInSession, getNextSessionInfo } from './services/schedule.js';
// Set proxy before any network calls
import './utils/proxy-patch.cjs';
// ── Validate config ────────────────────────────────────────────────────────────
try {
+70 -202
View File
@@ -1,261 +1,129 @@
/**
* proxy-patch.cjs
*
* Strategy: Patch https.request at the lowest level with domain filtering.
* This affects ALL HTTPS requests, but we only route Polymarket domains through proxy.
*
* RPC Polygon calls are NOT affected because:
* - They go to polygon-rpc.com (not in whitelist)
* - They use ethers.js JsonRpcProvider which uses fetch or its own transport
* Simple proxy patch using https-proxy-agent.
* Only routes Polymarket domains through proxy.
*/
const PROXY_URL = process.env.PROXY_URL || '';
if (PROXY_URL) {
const { HttpsProxyAgent } = require('https-proxy-agent');
const https = require('https');
const http = require('http');
const { URL } = require('url');
const net = require('net');
const tls = require('tls');
const proxyUrl = new URL(PROXY_URL);
const proxyHost = proxyUrl.hostname;
const proxyPort = parseInt(proxyUrl.port) || (proxyUrl.protocol === 'https:' ? 443 : 80);
const proxyAuth = (proxyUrl.username || proxyUrl.password)
? Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString('base64')
: null;
const agent = new HttpsProxyAgent(PROXY_URL);
// Polymarket domains ONLY
const POLY_HOSTS = new Set([
// Polymarket domains
const POLY_DOMAINS = [
'polymarket.com',
'www.polymarket.com',
'clob.polymarket.com',
'gamma-api.polymarket.com',
'data-api.polymarket.com',
]);
];
function shouldProxy(hostname) {
// Check exact match or subdomain
if (POLY_HOSTS.has(hostname)) return true;
for (const host of POLY_HOSTS) {
if (hostname.endsWith('.' + host)) return true;
if (!hostname) return false;
for (const domain of POLY_DOMAINS) {
if (hostname === domain || hostname.endsWith('.' + domain)) {
return true;
}
}
return false;
}
// Store original request
// Store originals
const originalHttpsRequest = https.request;
const originalHttpRequest = http.request;
// Create proxy-aware https.request
// Patch https.request
https.request = function(options, callback) {
let hostname;
if (typeof options === 'string') {
try {
hostname = new URL(options).hostname;
const url = new URL(options);
hostname = url.hostname;
} catch {
return originalHttpsRequest.apply(https, arguments);
}
} else if (options.hostname || options.host) {
hostname = options.hostname || (typeof options.host === 'string' ? options.host.split(':')[0] : options.host);
} else {
hostname = options.hostname || options.host;
if (typeof hostname === 'string' && hostname.includes(':')) {
hostname = hostname.split(':')[0];
}
}
// If not Polymarket, use direct connection
if (!hostname || !shouldProxy(hostname)) {
// Only proxy Polymarket
if (!shouldProxy(hostname)) {
return originalHttpsRequest.apply(https, arguments);
}
// For Polymarket: create CONNECT tunnel through proxy
const targetHost = hostname;
const targetPort = 443;
// Build CONNECT request headers
const connectHeaders = {
'Host': `${targetHost}:${targetPort}`,
};
if (proxyAuth) {
connectHeaders['Proxy-Authorization'] = `Basic ${proxyAuth}`;
}
// Create socket connection through proxy
const connectOptions = {
host: proxyHost,
port: proxyPort,
method: 'CONNECT',
path: `${targetHost}:${targetPort}`,
headers: connectHeaders,
};
// Make CONNECT request to proxy
const connectReq = originalHttpRequest(connectOptions);
// Return a promise-like request that waits for tunnel
const deferredReq = {
on: function(event, handler) {
if (event === 'response' || event === 'error' || event === 'timeout') {
this._deferredHandlers = this._deferredHandlers || {};
this._deferredHandlers[event] = handler;
}
return this;
},
once: function(event, handler) {
return this.on(event, handler);
},
write: function(chunk) {
if (this._realRequest) {
this._realRequest.write(chunk);
} else {
this._buffer = this._buffer || [];
this._buffer.push(chunk);
}
return true;
},
end: function(chunk) {
if (chunk) this.write(chunk);
if (this._realRequest) {
this._realRequest.end();
} else {
this._ended = true;
}
return this;
},
setTimeout: function(ms) {
this._timeout = ms;
if (this._realRequest) {
this._realRequest.setTimeout(ms);
}
return this;
},
setHeader: function(name, value) {
this._headers = this._headers || {};
this._headers[name] = value;
return this;
},
abort: function() {
if (this._realRequest) {
this._realRequest.abort();
}
if (this._connectReq) {
this._connectReq.abort();
}
}
};
connectReq.on('connect', (res, socket) => {
if (res.statusCode !== 200) {
const err = new Error(`Proxy CONNECT failed: ${res.statusCode}`);
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.error) {
deferredReq._deferredHandlers.error(err);
}
return;
}
// Wrap socket in TLS
const tlsOptions = {
socket: socket,
servername: targetHost,
rejectUnauthorized: options.rejectUnauthorized !== false,
// Apply proxy agent
if (typeof options === 'string') {
// Convert string URL to options object with agent
const url = new URL(options);
const newOptions = {
protocol: url.protocol,
hostname: url.hostname,
port: url.port || 443,
path: url.pathname + url.search,
method: 'GET',
headers: {},
agent: agent,
};
const tlsSocket = tls.connect(tlsOptions);
// Now make actual HTTPS request through tunnel
const requestOptions = typeof options === 'string'
? { ...new URL(options), createConnection: () => tlsSocket }
: { ...options, createConnection: () => tlsSocket };
const realReq = originalHttpsRequest(requestOptions, (res) => {
if (callback) callback(res);
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.response) {
deferredReq._deferredHandlers.response(res);
}
});
// Apply deferred operations
if (deferredReq._headers) {
for (const [name, value] of Object.entries(deferredReq._headers)) {
realReq.setHeader(name, value);
}
if (callback) {
return originalHttpsRequest.call(https, newOptions, callback);
}
if (deferredReq._timeout) {
realReq.setTimeout(deferredReq._timeout);
}
if (deferredReq._buffer) {
for (const chunk of deferredReq._buffer) {
realReq.write(chunk);
}
}
if (deferredReq._ended) {
realReq.end();
}
// Forward events
realReq.on('error', (err) => {
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.error) {
deferredReq._deferredHandlers.error(err);
}
});
realReq.on('timeout', () => {
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.timeout) {
deferredReq._deferredHandlers.timeout();
}
});
deferredReq._realRequest = realReq;
deferredReq._connectReq = connectReq;
});
connectReq.on('error', (err) => {
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.error) {
deferredReq._deferredHandlers.error(err);
}
});
connectReq.end();
return deferredReq;
return originalHttpsRequest.call(https, newOptions);
} else {
// Options object - just add agent
options.agent = agent;
return originalHttpsRequest.apply(https, arguments);
}
};
// Also patch http.request for completeness (though CLOB uses HTTPS)
// Patch http.request (for HTTP proxy connections)
http.request = function(options, callback) {
let hostname;
if (typeof options === 'string') {
try {
hostname = new URL(options).hostname;
const url = new URL(options);
hostname = url.hostname;
} catch {
return originalHttpRequest.apply(http, arguments);
}
} else if (options.hostname || options.host) {
hostname = options.hostname || (typeof options.host === 'string' ? options.host.split(':')[0] : options.host);
} else {
hostname = options.hostname || options.host;
}
// Only proxy Polymarket domains
if (!hostname || !shouldProxy(hostname)) {
// Only proxy Polymarket
if (!shouldProxy(hostname)) {
return originalHttpRequest.apply(http, arguments);
}
// For HTTP, route through proxy directly
const targetUrl = typeof options === 'string' ? new URL(options) : null;
const path = targetUrl ? targetUrl.pathname + targetUrl.search : (options.path || '/');
const proxyOptions = {
host: proxyHost,
port: proxyPort,
method: options.method || 'GET',
path: path,
headers: { ...(options.headers || {}) },
};
if (proxyAuth) {
proxyOptions.headers['Proxy-Authorization'] = `Basic ${proxyAuth}`;
// Apply same logic for HTTP
if (typeof options === 'string') {
const url = new URL(options);
const newOptions = {
protocol: url.protocol,
hostname: url.hostname,
port: url.port || 80,
path: url.pathname + url.search,
method: 'GET',
headers: {},
agent: agent,
};
if (callback) {
return originalHttpRequest.call(http, newOptions, callback);
}
return originalHttpRequest.call(http, newOptions);
} else {
options.agent = agent;
return originalHttpRequest.apply(http, arguments);
}
return originalHttpRequest.call(http, proxyOptions, callback);
};
console.log(`[proxy-patch] HTTPS/HTTP CONNECT tunnel active for Polymarket only`);
console.log(`[proxy-patch] RPC Polygon and other domains use direct connection`);
} else {
console.log('[proxy-patch] No PROXY_URL set, skipping patch');
console.log('[proxy-patch] Proxy active for Polymarket domains');
}