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
+141 -1
View File
@@ -13,6 +13,7 @@
"blessed": "^0.1.81", "blessed": "^0.1.81",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"ethers": "^5.8.0", "ethers": "^5.8.0",
"global-agent": "^4.1.2",
"https-proxy-agent": "^7.0.6", "https-proxy-agent": "^7.0.6",
"undici": "^7.22.0", "undici": "^7.22.0",
"ws": "^8.19.0" "ws": "^8.19.0"
@@ -1107,6 +1108,40 @@
} }
} }
}, },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -1208,6 +1243,18 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ethereum-cryptography": { "node_modules/ethereum-cryptography": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz",
@@ -1405,6 +1452,37 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/global-agent": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-4.1.2.tgz",
"integrity": "sha512-CvUwIRwmDHu33Y71P8v1Pr8yo5/U2inzrdRNEtPdiYKBd8J4oQ6aDkt9WqZhMQDtAcTAl+akDTHssRYaBTTlhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"globalthis": "^1.0.2",
"matcher": "^4.0.0",
"semver": "^7.3.5",
"serialize-error": "^8.1.0"
},
"engines": {
"node": ">=10.0"
}
},
"node_modules/globalthis": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"license": "MIT",
"dependencies": {
"define-properties": "^1.2.1",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1427,6 +1505,18 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": { "node_modules/has-symbols": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -1575,6 +1665,21 @@
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/matcher": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-4.0.0.tgz",
"integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1684,6 +1789,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -1733,7 +1847,6 @@
"version": "7.7.4", "version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -1742,6 +1855,21 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/serialize-error": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",
"integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",
"license": "MIT",
"dependencies": {
"type-fest": "^0.20.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/simple-update-notifier": { "node_modules/simple-update-notifier": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -1822,6 +1950,18 @@
"integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
"license": "Unlicense" "license": "Unlicense"
}, },
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undefsafe": { "node_modules/undefsafe": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+1
View File
@@ -35,6 +35,7 @@
"blessed": "^0.1.81", "blessed": "^0.1.81",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"ethers": "^5.8.0", "ethers": "^5.8.0",
"global-agent": "^4.1.2",
"https-proxy-agent": "^7.0.6", "https-proxy-agent": "^7.0.6",
"undici": "^7.22.0", "undici": "^7.22.0",
"ws": "^8.19.0" "ws": "^8.19.0"
+3 -3
View File
@@ -9,9 +9,6 @@
* For the TUI dashboard version, use: npm run sniper-tui * 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 { validateMMConfig } from './config/index.js';
import config from './config/index.js'; import config from './config/index.js';
import logger from './utils/logger.js'; import logger from './utils/logger.js';
@@ -21,6 +18,9 @@ import { executeSnipe } from './services/sniperExecutor.js';
import { redeemSniperPositions } from './services/ctf.js'; import { redeemSniperPositions } from './services/ctf.js';
import { getSchedule, isAssetInSession, getNextSessionInfo } from './services/schedule.js'; import { getSchedule, isAssetInSession, getNextSessionInfo } from './services/schedule.js';
// Set proxy before any network calls
import './utils/proxy-patch.cjs';
// ── Validate config ──────────────────────────────────────────────────────────── // ── Validate config ────────────────────────────────────────────────────────────
try { try {
+70 -202
View File
@@ -1,261 +1,129 @@
/** /**
* proxy-patch.cjs * proxy-patch.cjs
* *
* Strategy: Patch https.request at the lowest level with domain filtering. * Simple proxy patch using https-proxy-agent.
* This affects ALL HTTPS requests, but we only route Polymarket domains through proxy. * Only routes 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
*/ */
const PROXY_URL = process.env.PROXY_URL || ''; const PROXY_URL = process.env.PROXY_URL || '';
if (PROXY_URL) { if (PROXY_URL) {
const { HttpsProxyAgent } = require('https-proxy-agent');
const https = require('https'); const https = require('https');
const http = require('http'); const http = require('http');
const { URL } = require('url');
const net = require('net');
const tls = require('tls');
const proxyUrl = new URL(PROXY_URL); const agent = new HttpsProxyAgent(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;
// Polymarket domains ONLY // Polymarket domains
const POLY_HOSTS = new Set([ const POLY_DOMAINS = [
'polymarket.com', 'polymarket.com',
'www.polymarket.com',
'clob.polymarket.com', 'clob.polymarket.com',
'gamma-api.polymarket.com', 'gamma-api.polymarket.com',
'data-api.polymarket.com', 'data-api.polymarket.com',
]); ];
function shouldProxy(hostname) { function shouldProxy(hostname) {
// Check exact match or subdomain if (!hostname) return false;
if (POLY_HOSTS.has(hostname)) return true; for (const domain of POLY_DOMAINS) {
for (const host of POLY_HOSTS) { if (hostname === domain || hostname.endsWith('.' + domain)) {
if (hostname.endsWith('.' + host)) return true; return true;
}
} }
return false; return false;
} }
// Store original request // Store originals
const originalHttpsRequest = https.request; const originalHttpsRequest = https.request;
const originalHttpRequest = http.request; const originalHttpRequest = http.request;
// Create proxy-aware https.request // Patch https.request
https.request = function(options, callback) { https.request = function(options, callback) {
let hostname; let hostname;
if (typeof options === 'string') { if (typeof options === 'string') {
try { try {
hostname = new URL(options).hostname; const url = new URL(options);
hostname = url.hostname;
} catch { } catch {
return originalHttpsRequest.apply(https, arguments); return originalHttpsRequest.apply(https, arguments);
} }
} else if (options.hostname || options.host) { } else {
hostname = options.hostname || (typeof options.host === 'string' ? options.host.split(':')[0] : options.host); hostname = options.hostname || options.host;
if (typeof hostname === 'string' && hostname.includes(':')) {
hostname = hostname.split(':')[0];
}
} }
// If not Polymarket, use direct connection // Only proxy Polymarket
if (!hostname || !shouldProxy(hostname)) { if (!shouldProxy(hostname)) {
return originalHttpsRequest.apply(https, arguments); return originalHttpsRequest.apply(https, arguments);
} }
// For Polymarket: create CONNECT tunnel through proxy // Apply proxy agent
const targetHost = hostname; if (typeof options === 'string') {
const targetPort = 443; // Convert string URL to options object with agent
const url = new URL(options);
// Build CONNECT request headers const newOptions = {
const connectHeaders = { protocol: url.protocol,
'Host': `${targetHost}:${targetPort}`, hostname: url.hostname,
}; port: url.port || 443,
if (proxyAuth) { path: url.pathname + url.search,
connectHeaders['Proxy-Authorization'] = `Basic ${proxyAuth}`; method: 'GET',
} headers: {},
agent: agent,
// 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,
}; };
const tlsSocket = tls.connect(tlsOptions); if (callback) {
return originalHttpsRequest.call(https, newOptions, callback);
// 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 (deferredReq._timeout) { return originalHttpsRequest.call(https, newOptions);
realReq.setTimeout(deferredReq._timeout); } else {
} // Options object - just add agent
if (deferredReq._buffer) { options.agent = agent;
for (const chunk of deferredReq._buffer) { return originalHttpsRequest.apply(https, arguments);
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;
}; };
// Also patch http.request for completeness (though CLOB uses HTTPS) // Patch http.request (for HTTP proxy connections)
http.request = function(options, callback) { http.request = function(options, callback) {
let hostname; let hostname;
if (typeof options === 'string') { if (typeof options === 'string') {
try { try {
hostname = new URL(options).hostname; const url = new URL(options);
hostname = url.hostname;
} catch { } catch {
return originalHttpRequest.apply(http, arguments); return originalHttpRequest.apply(http, arguments);
} }
} else if (options.hostname || options.host) { } else {
hostname = options.hostname || (typeof options.host === 'string' ? options.host.split(':')[0] : options.host); hostname = options.hostname || options.host;
} }
// Only proxy Polymarket domains // Only proxy Polymarket
if (!hostname || !shouldProxy(hostname)) { if (!shouldProxy(hostname)) {
return originalHttpRequest.apply(http, arguments); return originalHttpRequest.apply(http, arguments);
} }
// For HTTP, route through proxy directly // Apply same logic for HTTP
const targetUrl = typeof options === 'string' ? new URL(options) : null; if (typeof options === 'string') {
const path = targetUrl ? targetUrl.pathname + targetUrl.search : (options.path || '/'); const url = new URL(options);
const newOptions = {
const proxyOptions = { protocol: url.protocol,
host: proxyHost, hostname: url.hostname,
port: proxyPort, port: url.port || 80,
method: options.method || 'GET', path: url.pathname + url.search,
path: path, method: 'GET',
headers: { ...(options.headers || {}) }, headers: {},
}; agent: agent,
};
if (proxyAuth) { if (callback) {
proxyOptions.headers['Proxy-Authorization'] = `Basic ${proxyAuth}`; 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] Proxy active for Polymarket domains');
console.log(`[proxy-patch] RPC Polygon and other domains use direct connection`);
} else {
console.log('[proxy-patch] No PROXY_URL set, skipping patch');
} }