@agenticmail/enterprise 0.5.443 → 0.5.445
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/dist/agent-tools-QXSZOYCX.js +14629 -0
- package/dist/chunk-46UEEFMW.js +2214 -0
- package/dist/chunk-5NJ3WE4B.js +7592 -0
- package/dist/chunk-B73UUNMT.js +7689 -0
- package/dist/chunk-NQRWBIX4.js +1728 -0
- package/dist/chunk-QROWKQVL.js +5387 -0
- package/dist/chunk-XNMDISPO.js +1728 -0
- package/dist/chunk-XTRFUWIR.js +1645 -0
- package/dist/chunk-YXSIPPBW.js +5596 -0
- package/dist/cli-agent-7TB4WA57.js +2761 -0
- package/dist/cli-serve-4YXKRC3R.js +322 -0
- package/dist/cli-serve-WTRKWP5I.js +322 -0
- package/dist/cli.js +3 -3
- package/dist/index.js +6 -6
- package/dist/polymarket-ZVUDH7EV.js +17 -0
- package/dist/polymarket-runtime-6LTI2BL7.js +108 -0
- package/dist/polymarket-watcher-2JCPZUKA.js +23 -0
- package/dist/runtime-RDDBAMV3.js +50 -0
- package/dist/server-34DOMSXN.js +36 -0
- package/dist/server-T5S3OKDE.js +36 -0
- package/dist/setup-G456OG4S.js +20 -0
- package/dist/setup-QUDFN4L6.js +20 -0
- package/logs/cloudflared-error.log +78 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1645 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SecureVault,
|
|
3
|
+
init_vault
|
|
4
|
+
} from "./chunk-WUAWWKTN.js";
|
|
5
|
+
|
|
6
|
+
// src/agent-tools/tools/polymarket-runtime.ts
|
|
7
|
+
init_vault();
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
var _vaultInstance = null;
|
|
14
|
+
function getVaultInstance() {
|
|
15
|
+
if (_vaultInstance) return _vaultInstance;
|
|
16
|
+
try {
|
|
17
|
+
_vaultInstance = new SecureVault();
|
|
18
|
+
return _vaultInstance;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
var CLOB_URL = "https://clob.polymarket.com";
|
|
24
|
+
var _sshProcess = null;
|
|
25
|
+
var _proxyState = { connected: false, pid: null, socksPort: 0, startedAt: null, error: null };
|
|
26
|
+
var _proxyConfig = null;
|
|
27
|
+
async function loadProxyConfig(db) {
|
|
28
|
+
if (_proxyConfig) return _proxyConfig;
|
|
29
|
+
try {
|
|
30
|
+
const row = await db?.get?.(`SELECT * FROM poly_proxy_config WHERE id = 1`);
|
|
31
|
+
if (!row || !row.enabled) return null;
|
|
32
|
+
const vault = getVaultInstance();
|
|
33
|
+
_proxyConfig = {
|
|
34
|
+
enabled: !!row.enabled,
|
|
35
|
+
proxyMode: row.proxy_mode || "ssh",
|
|
36
|
+
proxyUrl: row.proxy_url || void 0,
|
|
37
|
+
proxyToken: row.encrypted_proxy_token && vault ? vault.decrypt(row.encrypted_proxy_token) : void 0,
|
|
38
|
+
vpsHost: row.vps_host || "",
|
|
39
|
+
vpsUser: row.vps_user || "root",
|
|
40
|
+
vpsPort: row.vps_port || 22,
|
|
41
|
+
socksPort: row.socks_port || 1080,
|
|
42
|
+
authMethod: row.auth_method || "password",
|
|
43
|
+
sshKeyPath: row.ssh_key_path || void 0,
|
|
44
|
+
sshKeyContent: row.encrypted_ssh_key && vault ? vault.decrypt(row.encrypted_ssh_key) : void 0,
|
|
45
|
+
password: row.encrypted_password && vault ? vault.decrypt(row.encrypted_password) : void 0
|
|
46
|
+
};
|
|
47
|
+
return _proxyConfig;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function saveProxyConfig(db, config) {
|
|
53
|
+
const vault = getVaultInstance();
|
|
54
|
+
const encPassword = config.password && vault ? vault.encrypt(config.password) : null;
|
|
55
|
+
const encProxyToken = config.proxyToken && vault ? vault.encrypt(config.proxyToken) : null;
|
|
56
|
+
const encSshKey = config.sshKeyContent && vault ? vault.encrypt(config.sshKeyContent) : null;
|
|
57
|
+
await db?.run?.(`
|
|
58
|
+
INSERT INTO poly_proxy_config (id, enabled, proxy_mode, proxy_url, encrypted_proxy_token, vps_host, vps_user, vps_port, socks_port, auth_method, ssh_key_path, encrypted_ssh_key, encrypted_password, updated_at)
|
|
59
|
+
VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
|
60
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
61
|
+
enabled = EXCLUDED.enabled, proxy_mode = EXCLUDED.proxy_mode, proxy_url = EXCLUDED.proxy_url,
|
|
62
|
+
encrypted_proxy_token = EXCLUDED.encrypted_proxy_token, vps_host = EXCLUDED.vps_host,
|
|
63
|
+
vps_user = EXCLUDED.vps_user, vps_port = EXCLUDED.vps_port, socks_port = EXCLUDED.socks_port,
|
|
64
|
+
auth_method = EXCLUDED.auth_method, ssh_key_path = EXCLUDED.ssh_key_path,
|
|
65
|
+
encrypted_ssh_key = EXCLUDED.encrypted_ssh_key, encrypted_password = EXCLUDED.encrypted_password,
|
|
66
|
+
updated_at = NOW()
|
|
67
|
+
`, [
|
|
68
|
+
config.enabled ? 1 : 0,
|
|
69
|
+
config.proxyMode || "http",
|
|
70
|
+
config.proxyUrl || null,
|
|
71
|
+
encProxyToken,
|
|
72
|
+
config.vpsHost || null,
|
|
73
|
+
config.vpsUser || "root",
|
|
74
|
+
config.vpsPort || 22,
|
|
75
|
+
config.socksPort || 1080,
|
|
76
|
+
config.authMethod || "password",
|
|
77
|
+
config.sshKeyPath || null,
|
|
78
|
+
encSshKey,
|
|
79
|
+
encPassword
|
|
80
|
+
]);
|
|
81
|
+
_proxyConfig = null;
|
|
82
|
+
}
|
|
83
|
+
async function startProxy(db) {
|
|
84
|
+
if (_sshProcess && _proxyState.connected) return _proxyState;
|
|
85
|
+
if (_proxyState.connected && _proxyConfig?.proxyMode === "http") return _proxyState;
|
|
86
|
+
const config = await loadProxyConfig(db);
|
|
87
|
+
if (!config || !config.enabled) {
|
|
88
|
+
return { connected: false, pid: null, socksPort: 0, startedAt: null, error: "Proxy not configured or disabled" };
|
|
89
|
+
}
|
|
90
|
+
await stopProxy();
|
|
91
|
+
if (config.proxyMode === "http" && config.proxyUrl) {
|
|
92
|
+
try {
|
|
93
|
+
const headers = {};
|
|
94
|
+
if (config.proxyToken) headers["x-proxy-token"] = config.proxyToken;
|
|
95
|
+
const res = await fetch(config.proxyUrl.replace(/\/$/, "") + "/_health", { headers, signal: AbortSignal.timeout(8e3) });
|
|
96
|
+
if (!res.ok) throw new Error(`Health check returned ${res.status}`);
|
|
97
|
+
_proxyState = { connected: true, pid: null, socksPort: 0, startedAt: (/* @__PURE__ */ new Date()).toISOString(), error: null, mode: "http", proxyUrl: config.proxyUrl };
|
|
98
|
+
clientInstances.clear();
|
|
99
|
+
try {
|
|
100
|
+
const { setProxyFetchHook } = await import("./shared-TLWZZZSK.js");
|
|
101
|
+
const proxyHeaders = { Accept: "application/json" };
|
|
102
|
+
if (config.proxyToken) proxyHeaders["x-proxy-token"] = config.proxyToken;
|
|
103
|
+
const proxyBase = config.proxyUrl.replace(/\/$/, "");
|
|
104
|
+
setProxyFetchHook(async (url, opts) => {
|
|
105
|
+
const rewritten = url.replace("https://clob.polymarket.com", proxyBase);
|
|
106
|
+
return fetch(rewritten, { ...opts, headers: { ...opts?.headers, ...proxyHeaders } });
|
|
107
|
+
});
|
|
108
|
+
console.log(`[polymarket-proxy] Proxy fetch hook installed \u2014 CLOB reads also routed through proxy`);
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
console.log(`[polymarket-proxy] HTTP proxy connected: ${config.proxyUrl} \u2014 CLOB orders will route through proxy`);
|
|
112
|
+
return _proxyState;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
_proxyState = { connected: false, pid: null, socksPort: 0, startedAt: null, error: `Cannot reach proxy: ${err.message}` };
|
|
115
|
+
return _proxyState;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const args = [
|
|
119
|
+
"-D",
|
|
120
|
+
String(config.socksPort),
|
|
121
|
+
"-N",
|
|
122
|
+
"-o",
|
|
123
|
+
"StrictHostKeyChecking=accept-new",
|
|
124
|
+
"-o",
|
|
125
|
+
"ServerAliveInterval=30",
|
|
126
|
+
"-o",
|
|
127
|
+
"ServerAliveCountMax=3",
|
|
128
|
+
"-o",
|
|
129
|
+
"ExitOnForwardFailure=yes",
|
|
130
|
+
"-o",
|
|
131
|
+
"ConnectTimeout=10",
|
|
132
|
+
"-p",
|
|
133
|
+
String(config.vpsPort)
|
|
134
|
+
];
|
|
135
|
+
let tmpKeyFile = null;
|
|
136
|
+
if (config.authMethod === "key") {
|
|
137
|
+
if (config.sshKeyContent) {
|
|
138
|
+
const { writeFileSync, chmodSync } = await import("fs");
|
|
139
|
+
const { join } = await import("path");
|
|
140
|
+
const { tmpdir } = await import("os");
|
|
141
|
+
tmpKeyFile = join(tmpdir(), `.poly_ssh_key_${Date.now()}`);
|
|
142
|
+
writeFileSync(tmpKeyFile, config.sshKeyContent + "\n", { mode: 384 });
|
|
143
|
+
chmodSync(tmpKeyFile, 384);
|
|
144
|
+
args.push("-i", tmpKeyFile);
|
|
145
|
+
} else if (config.sshKeyPath) {
|
|
146
|
+
args.push("-i", config.sshKeyPath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
args.push(`${config.vpsUser}@${config.vpsHost}`);
|
|
150
|
+
try {
|
|
151
|
+
const sshEnv = { ...process.env };
|
|
152
|
+
if (config.authMethod === "password" && config.password) {
|
|
153
|
+
const proc = spawn("sshpass", ["-p", config.password, "ssh", ...args], {
|
|
154
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
155
|
+
env: sshEnv,
|
|
156
|
+
detached: true
|
|
157
|
+
});
|
|
158
|
+
_sshProcess = proc;
|
|
159
|
+
} else {
|
|
160
|
+
const proc = spawn("ssh", args, {
|
|
161
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
162
|
+
env: sshEnv,
|
|
163
|
+
detached: true
|
|
164
|
+
});
|
|
165
|
+
_sshProcess = proc;
|
|
166
|
+
}
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
168
|
+
if (_sshProcess.exitCode !== null) {
|
|
169
|
+
const err = `SSH tunnel exited immediately with code ${_sshProcess.exitCode}`;
|
|
170
|
+
_proxyState = { connected: false, pid: null, socksPort: config.socksPort, startedAt: null, error: err };
|
|
171
|
+
_sshProcess = null;
|
|
172
|
+
return _proxyState;
|
|
173
|
+
}
|
|
174
|
+
_sshProcess.on("exit", (code) => {
|
|
175
|
+
console.log(`[polymarket-proxy] SSH tunnel exited with code ${code}`);
|
|
176
|
+
_proxyState = { connected: false, pid: null, socksPort: config.socksPort, startedAt: null, error: `Tunnel exited (code ${code})` };
|
|
177
|
+
_sshProcess = null;
|
|
178
|
+
});
|
|
179
|
+
_sshProcess.stderr?.on("data", (data) => {
|
|
180
|
+
const msg = data.toString().trim();
|
|
181
|
+
if (msg) console.log(`[polymarket-proxy] SSH: ${msg}`);
|
|
182
|
+
});
|
|
183
|
+
_proxyState = {
|
|
184
|
+
connected: true,
|
|
185
|
+
pid: _sshProcess.pid || null,
|
|
186
|
+
socksPort: config.socksPort,
|
|
187
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
188
|
+
error: null
|
|
189
|
+
};
|
|
190
|
+
console.log(`[polymarket-proxy] SSH SOCKS tunnel started on localhost:${config.socksPort} via ${config.vpsUser}@${config.vpsHost} (pid: ${_sshProcess.pid})`);
|
|
191
|
+
return _proxyState;
|
|
192
|
+
} catch (err) {
|
|
193
|
+
_proxyState = { connected: false, pid: null, socksPort: config.socksPort, startedAt: null, error: err.message };
|
|
194
|
+
return _proxyState;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function stopProxy() {
|
|
198
|
+
if (_sshProcess) {
|
|
199
|
+
try {
|
|
200
|
+
_sshProcess.kill("SIGTERM");
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
_sshProcess = null;
|
|
204
|
+
}
|
|
205
|
+
_proxyState = { connected: false, pid: null, socksPort: 0, startedAt: null, error: null };
|
|
206
|
+
}
|
|
207
|
+
function getProxyState() {
|
|
208
|
+
if (_proxyState.connected && _sshProcess) {
|
|
209
|
+
try {
|
|
210
|
+
process.kill(_sshProcess.pid, 0);
|
|
211
|
+
} catch {
|
|
212
|
+
_proxyState = { connected: false, pid: null, socksPort: _proxyState.socksPort, startedAt: null, error: "Tunnel process died" };
|
|
213
|
+
_sshProcess = null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { ..._proxyState };
|
|
217
|
+
}
|
|
218
|
+
function isProxyEnabled() {
|
|
219
|
+
return _proxyState.connected;
|
|
220
|
+
}
|
|
221
|
+
var _autoConnectAttempted = false;
|
|
222
|
+
async function autoConnectProxy(db) {
|
|
223
|
+
if (_autoConnectAttempted || _proxyState.connected) return;
|
|
224
|
+
_autoConnectAttempted = true;
|
|
225
|
+
try {
|
|
226
|
+
const config = await loadProxyConfig(db);
|
|
227
|
+
if (!config?.enabled || config.proxyMode !== "http" || !config.proxyUrl) return;
|
|
228
|
+
const headers = {};
|
|
229
|
+
if (config.proxyToken) headers["x-proxy-token"] = config.proxyToken;
|
|
230
|
+
const res = await fetch(config.proxyUrl.replace(/\/$/, "") + "/_health", { headers, signal: AbortSignal.timeout(8e3) });
|
|
231
|
+
if (res.ok) {
|
|
232
|
+
_proxyState = { connected: true, pid: null, socksPort: 0, startedAt: (/* @__PURE__ */ new Date()).toISOString(), error: null, mode: "http", proxyUrl: config.proxyUrl };
|
|
233
|
+
_proxyConfig = null;
|
|
234
|
+
clientInstances.clear();
|
|
235
|
+
console.log(`[polymarket-proxy] Auto-connected HTTP proxy: ${config.proxyUrl} \u2014 getClobUrl() will now return proxy URL`);
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
var PROXY_SERVER_SCRIPT = `
|
|
241
|
+
const http = require("http");
|
|
242
|
+
const https = require("https");
|
|
243
|
+
const { URL } = require("url");
|
|
244
|
+
|
|
245
|
+
const PORT = process.env.PORT || 8787;
|
|
246
|
+
const TARGET = "https://clob.polymarket.com";
|
|
247
|
+
const AUTH_TOKEN = process.env.PROXY_TOKEN || "";
|
|
248
|
+
const ALLOWED_IPS = (process.env.ALLOWED_IPS || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
249
|
+
|
|
250
|
+
console.log("[proxy] Polymarket CLOB Proxy");
|
|
251
|
+
console.log("[proxy] Target: " + TARGET);
|
|
252
|
+
|
|
253
|
+
function getClientIP(req) {
|
|
254
|
+
return req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress?.replace("::ffff:", "") || "";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const server = http.createServer((req, res) => {
|
|
258
|
+
const clientIP = getClientIP(req);
|
|
259
|
+
const token = req.headers["x-proxy-token"];
|
|
260
|
+
const ipAllowed = ALLOWED_IPS.length === 0 || ALLOWED_IPS.includes(clientIP);
|
|
261
|
+
const tokenValid = AUTH_TOKEN && token === AUTH_TOKEN;
|
|
262
|
+
if (!ipAllowed && !tokenValid) {
|
|
263
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
264
|
+
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (req.url === "/_health") {
|
|
268
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
269
|
+
res.end(JSON.stringify({ status: "ok", target: TARGET, ts: Date.now() }));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const targetUrl = new URL(req.url, TARGET);
|
|
273
|
+
const headers = { ...req.headers };
|
|
274
|
+
delete headers["x-proxy-token"];
|
|
275
|
+
delete headers["host"];
|
|
276
|
+
headers["host"] = "clob.polymarket.com";
|
|
277
|
+
const options = { hostname: targetUrl.hostname, port: 443, path: targetUrl.pathname + targetUrl.search, method: req.method, headers };
|
|
278
|
+
const proxyReq = https.request(options, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); });
|
|
279
|
+
proxyReq.on("error", (err) => { res.writeHead(502, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: err.message })); });
|
|
280
|
+
req.pipe(proxyReq);
|
|
281
|
+
});
|
|
282
|
+
server.listen(PORT, "0.0.0.0", () => console.log("[proxy] Listening on 0.0.0.0:" + PORT));
|
|
283
|
+
`.trim();
|
|
284
|
+
async function deployProxyToVPS(opts) {
|
|
285
|
+
const { randomBytes } = await import("crypto");
|
|
286
|
+
const { readFileSync } = await import("fs");
|
|
287
|
+
const logs = [];
|
|
288
|
+
const user = opts.user || "root";
|
|
289
|
+
const sshPort = opts.port || 22;
|
|
290
|
+
const token = randomBytes(16).toString("hex");
|
|
291
|
+
const proxyUrl = `http://${opts.host}:8787`;
|
|
292
|
+
let sshConfig = {
|
|
293
|
+
host: opts.host,
|
|
294
|
+
port: sshPort,
|
|
295
|
+
username: user,
|
|
296
|
+
readyTimeout: 2e4,
|
|
297
|
+
// Accept any host key (like StrictHostKeyChecking=accept-new)
|
|
298
|
+
algorithms: { serverHostKey: ["ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa"] }
|
|
299
|
+
};
|
|
300
|
+
if (opts.password) {
|
|
301
|
+
sshConfig.password = opts.password;
|
|
302
|
+
} else if (opts.sshKeyContent) {
|
|
303
|
+
sshConfig.privateKey = opts.sshKeyContent;
|
|
304
|
+
} else if (opts.sshKeyPath) {
|
|
305
|
+
try {
|
|
306
|
+
const keyPath = opts.sshKeyPath.replace(/^~/, process.env.HOME || "/root");
|
|
307
|
+
sshConfig.privateKey = readFileSync(keyPath, "utf-8");
|
|
308
|
+
} catch (e) {
|
|
309
|
+
return { success: false, proxyUrl: "", proxyToken: "", error: "Cannot read SSH key file: " + opts.sshKeyPath, logs };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function runSSH(conn2, cmd, timeoutMs = 12e4) {
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
const timer = setTimeout(() => reject(new Error("Command timed out")), timeoutMs);
|
|
315
|
+
conn2.exec(cmd, (err, stream) => {
|
|
316
|
+
if (err) {
|
|
317
|
+
clearTimeout(timer);
|
|
318
|
+
return reject(err);
|
|
319
|
+
}
|
|
320
|
+
let stdout = "";
|
|
321
|
+
let stderr = "";
|
|
322
|
+
stream.on("data", (d) => {
|
|
323
|
+
stdout += d.toString();
|
|
324
|
+
});
|
|
325
|
+
stream.stderr.on("data", (d) => {
|
|
326
|
+
stderr += d.toString();
|
|
327
|
+
});
|
|
328
|
+
stream.on("close", (code) => {
|
|
329
|
+
clearTimeout(timer);
|
|
330
|
+
if (code !== 0 && code !== null) {
|
|
331
|
+
const e = new Error(stderr.trim() || `Command failed with code ${code}`);
|
|
332
|
+
e.stderr = stderr;
|
|
333
|
+
e.code = code;
|
|
334
|
+
reject(e);
|
|
335
|
+
} else {
|
|
336
|
+
resolve(stdout.trim());
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
let conn;
|
|
343
|
+
try {
|
|
344
|
+
const { Client } = await import("ssh2");
|
|
345
|
+
logs.push("Connecting to server...");
|
|
346
|
+
conn = new Client();
|
|
347
|
+
await new Promise((resolve, reject) => {
|
|
348
|
+
const timer = setTimeout(() => reject(new Error("Connection timed out \u2014 check the IP address")), 2e4);
|
|
349
|
+
conn.on("ready", () => {
|
|
350
|
+
clearTimeout(timer);
|
|
351
|
+
resolve();
|
|
352
|
+
});
|
|
353
|
+
conn.on("error", (err) => {
|
|
354
|
+
clearTimeout(timer);
|
|
355
|
+
if (err.level === "client-authentication") {
|
|
356
|
+
reject(new Error("Login failed \u2014 check your password or key"));
|
|
357
|
+
} else if (err.code === "ECONNREFUSED") {
|
|
358
|
+
reject(new Error("Connection refused \u2014 check the IP address and make sure SSH is enabled"));
|
|
359
|
+
} else if (err.code === "ETIMEDOUT" || err.code === "ECONNRESET") {
|
|
360
|
+
reject(new Error("Cannot reach server \u2014 check the IP address"));
|
|
361
|
+
} else {
|
|
362
|
+
reject(new Error(err.message || "Connection failed"));
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
conn.connect(sshConfig);
|
|
366
|
+
});
|
|
367
|
+
logs.push("Connected successfully");
|
|
368
|
+
logs.push("Detecting server type...");
|
|
369
|
+
let osType = "linux";
|
|
370
|
+
try {
|
|
371
|
+
const uname = await runSSH(conn, "uname -s");
|
|
372
|
+
osType = uname.toLowerCase().includes("linux") ? "linux" : uname.toLowerCase();
|
|
373
|
+
const osInfo = await runSSH(conn, `cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"' || uname -a`);
|
|
374
|
+
logs.push("Server: " + osInfo);
|
|
375
|
+
} catch {
|
|
376
|
+
logs.push("Server: Linux");
|
|
377
|
+
}
|
|
378
|
+
let arch = "x64";
|
|
379
|
+
try {
|
|
380
|
+
const archStr = await runSSH(conn, "uname -m");
|
|
381
|
+
if (archStr.includes("arm") || archStr.includes("aarch64")) arch = "arm";
|
|
382
|
+
if (arch === "arm") logs.push("ARM architecture detected (Raspberry Pi or similar)");
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
logs.push("Checking for Node.js...");
|
|
386
|
+
let hasNode = false;
|
|
387
|
+
try {
|
|
388
|
+
const ver = await runSSH(conn, 'node --version 2>/dev/null || echo "none"');
|
|
389
|
+
hasNode = ver.startsWith("v");
|
|
390
|
+
if (hasNode) logs.push("Node.js " + ver + " found");
|
|
391
|
+
} catch {
|
|
392
|
+
hasNode = false;
|
|
393
|
+
}
|
|
394
|
+
if (!hasNode) {
|
|
395
|
+
logs.push("Installing Node.js (this may take 1-2 minutes)...");
|
|
396
|
+
try {
|
|
397
|
+
const installCmd = [
|
|
398
|
+
// Try nodesource (Ubuntu/Debian)
|
|
399
|
+
"(curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt-get install -y nodejs) 2>/dev/null",
|
|
400
|
+
// Fallback: try dnf/yum (RHEL/Fedora/Amazon Linux)
|
|
401
|
+
"|| (curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - && (dnf install -y nodejs || yum install -y nodejs)) 2>/dev/null",
|
|
402
|
+
// Fallback: try apk (Alpine)
|
|
403
|
+
"|| (apk add --no-cache nodejs npm) 2>/dev/null",
|
|
404
|
+
// Last resort: download binary
|
|
405
|
+
"|| (curl -fsSL https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-" + (arch === "arm" ? "arm64" : "x64") + ".tar.xz | tar -xJ -C /usr/local --strip-components=1) 2>/dev/null"
|
|
406
|
+
].join(" ");
|
|
407
|
+
await runSSH(conn, installCmd, 18e4);
|
|
408
|
+
const ver = await runSSH(conn, "node --version");
|
|
409
|
+
logs.push("Node.js " + ver + " installed");
|
|
410
|
+
} catch (e) {
|
|
411
|
+
return { success: false, proxyUrl: "", proxyToken: "", error: "Failed to install Node.js. You may need to install it manually on this server.", logs };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
logs.push("Setting up process manager...");
|
|
415
|
+
try {
|
|
416
|
+
await runSSH(conn, "which pm2 >/dev/null 2>&1 || npm install -g pm2", 6e4);
|
|
417
|
+
logs.push("Process manager ready");
|
|
418
|
+
} catch {
|
|
419
|
+
return { success: false, proxyUrl: "", proxyToken: "", error: "Failed to install process manager (PM2)", logs };
|
|
420
|
+
}
|
|
421
|
+
logs.push("Deploying proxy...");
|
|
422
|
+
try {
|
|
423
|
+
await runSSH(conn, "mkdir -p /opt/polymarket-proxy");
|
|
424
|
+
await runSSH(conn, `cat > /opt/polymarket-proxy/server.js << 'PROXYEOF'
|
|
425
|
+
${PROXY_SERVER_SCRIPT}
|
|
426
|
+
PROXYEOF`);
|
|
427
|
+
logs.push("Proxy deployed");
|
|
428
|
+
} catch {
|
|
429
|
+
return { success: false, proxyUrl: "", proxyToken: "", error: "Failed to deploy proxy to server", logs };
|
|
430
|
+
}
|
|
431
|
+
logs.push("Configuring firewall...");
|
|
432
|
+
try {
|
|
433
|
+
await runSSH(conn, '(which ufw >/dev/null 2>&1 && ufw allow 22/tcp >/dev/null 2>&1 && ufw allow 8787/tcp >/dev/null 2>&1 && echo "y" | ufw enable >/dev/null 2>&1) || (which firewall-cmd >/dev/null 2>&1 && firewall-cmd --permanent --add-port=8787/tcp && firewall-cmd --reload) || true');
|
|
434
|
+
logs.push("Firewall configured");
|
|
435
|
+
} catch {
|
|
436
|
+
logs.push("Firewall: skipped (not available)");
|
|
437
|
+
}
|
|
438
|
+
logs.push("Starting proxy...");
|
|
439
|
+
try {
|
|
440
|
+
await runSSH(conn, `cd /opt/polymarket-proxy && pm2 delete polymarket-proxy >/dev/null 2>&1; PORT=8787 PROXY_TOKEN=${token} pm2 start server.js --name polymarket-proxy && pm2 save && (pm2 startup 2>/dev/null | tail -1 | bash 2>/dev/null || true)`);
|
|
441
|
+
logs.push("Proxy running on port 8787");
|
|
442
|
+
} catch {
|
|
443
|
+
return { success: false, proxyUrl: "", proxyToken: "", error: "Failed to start proxy on server", logs };
|
|
444
|
+
}
|
|
445
|
+
conn.end();
|
|
446
|
+
conn = null;
|
|
447
|
+
logs.push("Verifying proxy is reachable...");
|
|
448
|
+
await new Promise((r) => setTimeout(r, 2500));
|
|
449
|
+
try {
|
|
450
|
+
const res = await fetch(proxyUrl + "/_health", {
|
|
451
|
+
headers: { "x-proxy-token": token },
|
|
452
|
+
signal: AbortSignal.timeout(1e4)
|
|
453
|
+
});
|
|
454
|
+
if (!res.ok) throw new Error("Status " + res.status);
|
|
455
|
+
logs.push("Proxy verified and working!");
|
|
456
|
+
} catch {
|
|
457
|
+
logs.push("Warning: Could not verify proxy yet (it may still be starting). Try connecting in a moment.");
|
|
458
|
+
}
|
|
459
|
+
return { success: true, proxyUrl, proxyToken: token, logs };
|
|
460
|
+
} catch (e) {
|
|
461
|
+
return { success: false, proxyUrl: "", proxyToken: "", error: e.message || "Unknown error", logs };
|
|
462
|
+
} finally {
|
|
463
|
+
if (conn) try {
|
|
464
|
+
conn.end();
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function getClobUrl() {
|
|
470
|
+
if (_proxyState.connected && _proxyState.mode === "http" && _proxyState.proxyUrl) {
|
|
471
|
+
return _proxyState.proxyUrl.replace(/\/$/, "");
|
|
472
|
+
}
|
|
473
|
+
return CLOB_URL;
|
|
474
|
+
}
|
|
475
|
+
async function getSocksAgent() {
|
|
476
|
+
if (!_proxyState.connected) return null;
|
|
477
|
+
try {
|
|
478
|
+
const { SocksProxyAgent } = await import("socks-proxy-agent");
|
|
479
|
+
return new SocksProxyAgent(`socks5://127.0.0.1:${_proxyState.socksPort}`);
|
|
480
|
+
} catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
var sdkAvailable = null;
|
|
485
|
+
var sdkInstalling = false;
|
|
486
|
+
async function ensureSDK() {
|
|
487
|
+
if (sdkAvailable === true) return { ready: true };
|
|
488
|
+
if (sdkInstalling) return { ready: false, message: "SDK installation in progress, please retry in ~30 seconds" };
|
|
489
|
+
const installDir = getSDKInstallDir();
|
|
490
|
+
if (canResolveSDK(installDir)) {
|
|
491
|
+
sdkAvailable = true;
|
|
492
|
+
return { ready: true };
|
|
493
|
+
}
|
|
494
|
+
sdkInstalling = true;
|
|
495
|
+
try {
|
|
496
|
+
console.log("[polymarket] Auto-installing @polymarket/clob-client and ethers to " + installDir + "...");
|
|
497
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
498
|
+
const pkgPath = path.join(installDir, "package.json");
|
|
499
|
+
if (!fs.existsSync(pkgPath)) {
|
|
500
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ name: "polymarket-sdk-deps", private: true }, null, 2));
|
|
501
|
+
}
|
|
502
|
+
execSync("npm install --save @polymarket/clob-client @ethersproject/wallet ethers@5 2>&1", {
|
|
503
|
+
cwd: installDir,
|
|
504
|
+
timeout: 12e4,
|
|
505
|
+
stdio: "pipe",
|
|
506
|
+
env: { ...process.env, NODE_ENV: "production" }
|
|
507
|
+
});
|
|
508
|
+
if (canResolveSDK(installDir)) {
|
|
509
|
+
sdkAvailable = true;
|
|
510
|
+
sdkInstalling = false;
|
|
511
|
+
console.log("[polymarket] SDK installed successfully at " + installDir);
|
|
512
|
+
return { ready: true };
|
|
513
|
+
} else {
|
|
514
|
+
sdkInstalling = false;
|
|
515
|
+
return { ready: false, message: "SDK installed but cannot be resolved at " + installDir };
|
|
516
|
+
}
|
|
517
|
+
} catch (err) {
|
|
518
|
+
sdkInstalling = false;
|
|
519
|
+
console.error("[polymarket] SDK installation failed:", err.message);
|
|
520
|
+
return { ready: false, message: `SDK auto-install failed: ${err.message}. Manual install: cd ${installDir} && npm install @polymarket/clob-client @ethersproject/wallet ethers@5` };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function getSDKInstallDir() {
|
|
524
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
525
|
+
return path.join(homeDir, ".agenticmail", "polymarket-sdk");
|
|
526
|
+
}
|
|
527
|
+
function canResolveSDK(installDir) {
|
|
528
|
+
try {
|
|
529
|
+
const nmDir = path.join(installDir, "node_modules");
|
|
530
|
+
return fs.existsSync(path.join(nmDir, "@polymarket", "clob-client")) && fs.existsSync(path.join(nmDir, "@ethersproject", "wallet"));
|
|
531
|
+
} catch {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async function importSDK(pkg) {
|
|
536
|
+
const installDir = getSDKInstallDir();
|
|
537
|
+
try {
|
|
538
|
+
const { createRequire } = await import("module");
|
|
539
|
+
const sdkRequire = createRequire(path.join(installDir, "node_modules", ".package.json"));
|
|
540
|
+
return sdkRequire(pkg);
|
|
541
|
+
} catch {
|
|
542
|
+
try {
|
|
543
|
+
return await import(pkg);
|
|
544
|
+
} catch {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
var clientInstances = /* @__PURE__ */ new Map();
|
|
550
|
+
async function getClobClient(agentId, db) {
|
|
551
|
+
if (!_proxyState.connected) {
|
|
552
|
+
try {
|
|
553
|
+
await autoConnectProxy(db);
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const cached = clientInstances.get(agentId);
|
|
558
|
+
if (cached && Date.now() - cached.createdAt < 36e5) return cached;
|
|
559
|
+
const creds = await loadWalletCredentials(agentId, db);
|
|
560
|
+
if (!creds) return null;
|
|
561
|
+
const sdk = await ensureSDK();
|
|
562
|
+
if (!sdk.ready) return null;
|
|
563
|
+
try {
|
|
564
|
+
const { ClobClient } = await importSDK("@polymarket/clob-client");
|
|
565
|
+
const { Wallet } = await importSDK("@ethersproject/wallet");
|
|
566
|
+
const signer = new Wallet(creds.privateKey);
|
|
567
|
+
const funder = creds.funderAddress || signer.address;
|
|
568
|
+
let apiCreds = creds.apiCreds;
|
|
569
|
+
if (!apiCreds) {
|
|
570
|
+
const origWarn = console.error;
|
|
571
|
+
console.error = (...args) => {
|
|
572
|
+
const msg = String(args[0] || "");
|
|
573
|
+
if (msg.includes("[CLOB Client] request error")) return;
|
|
574
|
+
origWarn.apply(console, args);
|
|
575
|
+
};
|
|
576
|
+
try {
|
|
577
|
+
const tempClient = new ClobClient(getClobUrl(), 137, signer);
|
|
578
|
+
apiCreds = await tempClient.createOrDeriveApiKey();
|
|
579
|
+
} finally {
|
|
580
|
+
console.error = origWarn;
|
|
581
|
+
}
|
|
582
|
+
await saveWalletCredentials(agentId, db, {
|
|
583
|
+
...creds,
|
|
584
|
+
apiCreds
|
|
585
|
+
});
|
|
586
|
+
console.log(`[polymarket] API credentials derived for agent ${agentId}`);
|
|
587
|
+
}
|
|
588
|
+
const client = new ClobClient(
|
|
589
|
+
getClobUrl(),
|
|
590
|
+
137,
|
|
591
|
+
signer,
|
|
592
|
+
apiCreds,
|
|
593
|
+
creds.signatureType || 0,
|
|
594
|
+
funder
|
|
595
|
+
);
|
|
596
|
+
if (_proxyState.connected && _proxyState.mode === "http" && _proxyConfig?.proxyToken) {
|
|
597
|
+
try {
|
|
598
|
+
const axiosInstance = client.http || client.client || client.axios;
|
|
599
|
+
if (axiosInstance?.defaults?.headers) {
|
|
600
|
+
axiosInstance.defaults.headers.common = axiosInstance.defaults.headers.common || {};
|
|
601
|
+
axiosInstance.defaults.headers.common["x-proxy-token"] = _proxyConfig.proxyToken;
|
|
602
|
+
console.log(`[polymarket-proxy] Proxy auth token injected into ClobClient for agent ${agentId}`);
|
|
603
|
+
}
|
|
604
|
+
} catch (e) {
|
|
605
|
+
console.warn(`[polymarket-proxy] Could not inject proxy token: ${e.message}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const socksAgent = await getSocksAgent();
|
|
609
|
+
if (socksAgent) {
|
|
610
|
+
try {
|
|
611
|
+
const axiosInstance = client.http || client.client || client.axios;
|
|
612
|
+
if (axiosInstance?.defaults) {
|
|
613
|
+
axiosInstance.defaults.httpAgent = socksAgent;
|
|
614
|
+
axiosInstance.defaults.httpsAgent = socksAgent;
|
|
615
|
+
console.log(`[polymarket-proxy] SOCKS agent injected into ClobClient for agent ${agentId}`);
|
|
616
|
+
} else {
|
|
617
|
+
client._socksAgent = socksAgent;
|
|
618
|
+
console.log(`[polymarket-proxy] SOCKS agent stored on ClobClient for agent ${agentId} (will be applied at request time)`);
|
|
619
|
+
}
|
|
620
|
+
} catch (e) {
|
|
621
|
+
console.log(`[polymarket-proxy] Could not inject SOCKS agent: ${e.message}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const instance = {
|
|
625
|
+
client,
|
|
626
|
+
address: signer.address,
|
|
627
|
+
funderAddress: funder,
|
|
628
|
+
signatureType: creds.signatureType || 0,
|
|
629
|
+
createdAt: Date.now()
|
|
630
|
+
};
|
|
631
|
+
clientInstances.set(agentId, instance);
|
|
632
|
+
return instance;
|
|
633
|
+
} catch (err) {
|
|
634
|
+
console.error(`[polymarket] Failed to create CLOB client for ${agentId}:`, err.message);
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
var dbInitialized = false;
|
|
639
|
+
var _dbInitPromise = null;
|
|
640
|
+
var _isPostgres = false;
|
|
641
|
+
function isPostgresDB() {
|
|
642
|
+
return _isPostgres;
|
|
643
|
+
}
|
|
644
|
+
async function initPolymarketDB(db) {
|
|
645
|
+
if (dbInitialized || !db) return;
|
|
646
|
+
if (_dbInitPromise) return _dbInitPromise;
|
|
647
|
+
_dbInitPromise = _doInitPolymarketDB(db);
|
|
648
|
+
return _dbInitPromise;
|
|
649
|
+
}
|
|
650
|
+
async function _doInitPolymarketDB(db) {
|
|
651
|
+
if (dbInitialized) return;
|
|
652
|
+
try {
|
|
653
|
+
const { detectDialect, setDialect, autoId: getAutoId } = await import("./polymarket-shared-M5ZU3HKM.js");
|
|
654
|
+
const dialect = await detectDialect(db);
|
|
655
|
+
setDialect(dialect);
|
|
656
|
+
_isPostgres = dialect === "postgres";
|
|
657
|
+
const autoId = getAutoId();
|
|
658
|
+
await db.execute(`
|
|
659
|
+
CREATE TABLE IF NOT EXISTS poly_wallet_credentials (
|
|
660
|
+
agent_id TEXT PRIMARY KEY,
|
|
661
|
+
private_key_encrypted TEXT NOT NULL,
|
|
662
|
+
funder_address TEXT,
|
|
663
|
+
signature_type INTEGER DEFAULT 0,
|
|
664
|
+
api_key TEXT,
|
|
665
|
+
api_secret TEXT,
|
|
666
|
+
api_passphrase TEXT,
|
|
667
|
+
rpc_url TEXT,
|
|
668
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
669
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
670
|
+
)
|
|
671
|
+
`);
|
|
672
|
+
await db.execute(`
|
|
673
|
+
CREATE TABLE IF NOT EXISTS poly_trading_config (
|
|
674
|
+
agent_id TEXT PRIMARY KEY,
|
|
675
|
+
mode TEXT DEFAULT 'approval',
|
|
676
|
+
max_position_size REAL DEFAULT 100,
|
|
677
|
+
max_order_size REAL DEFAULT 50,
|
|
678
|
+
max_total_exposure REAL DEFAULT 500,
|
|
679
|
+
max_daily_trades INTEGER DEFAULT 10,
|
|
680
|
+
max_daily_loss REAL DEFAULT 50,
|
|
681
|
+
max_drawdown_pct REAL DEFAULT 20,
|
|
682
|
+
allowed_categories TEXT DEFAULT '[]',
|
|
683
|
+
blocked_categories TEXT DEFAULT '[]',
|
|
684
|
+
blocked_markets TEXT DEFAULT '[]',
|
|
685
|
+
min_liquidity REAL DEFAULT 0,
|
|
686
|
+
min_volume REAL DEFAULT 0,
|
|
687
|
+
max_spread_pct REAL DEFAULT 100,
|
|
688
|
+
stop_loss_pct REAL DEFAULT 0,
|
|
689
|
+
take_profit_pct REAL DEFAULT 0,
|
|
690
|
+
trailing_stop_pct REAL DEFAULT 0,
|
|
691
|
+
rebalance_interval TEXT DEFAULT 'never',
|
|
692
|
+
notification_channel TEXT DEFAULT '',
|
|
693
|
+
notify_on TEXT DEFAULT '["trade_filled","stop_loss","circuit_breaker","market_resolved"]',
|
|
694
|
+
cash_reserve_pct REAL DEFAULT 20,
|
|
695
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
696
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
697
|
+
)
|
|
698
|
+
`);
|
|
699
|
+
await db.execute(`
|
|
700
|
+
CREATE TABLE IF NOT EXISTS poly_pending_trades (
|
|
701
|
+
id TEXT PRIMARY KEY,
|
|
702
|
+
agent_id TEXT NOT NULL,
|
|
703
|
+
token_id TEXT NOT NULL,
|
|
704
|
+
side TEXT NOT NULL,
|
|
705
|
+
price REAL,
|
|
706
|
+
size REAL NOT NULL,
|
|
707
|
+
order_type TEXT DEFAULT 'GTC',
|
|
708
|
+
tick_size TEXT DEFAULT '0.01',
|
|
709
|
+
neg_risk INTEGER DEFAULT 0,
|
|
710
|
+
market_question TEXT,
|
|
711
|
+
outcome TEXT,
|
|
712
|
+
rationale TEXT,
|
|
713
|
+
urgency TEXT DEFAULT 'normal',
|
|
714
|
+
status TEXT DEFAULT 'pending',
|
|
715
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
716
|
+
resolved_at TIMESTAMP,
|
|
717
|
+
resolved_by TEXT
|
|
718
|
+
)
|
|
719
|
+
`);
|
|
720
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_pending_agent ON poly_pending_trades(agent_id, status)`);
|
|
721
|
+
await db.execute(`
|
|
722
|
+
CREATE TABLE IF NOT EXISTS poly_trade_log (
|
|
723
|
+
id TEXT PRIMARY KEY,
|
|
724
|
+
agent_id TEXT NOT NULL,
|
|
725
|
+
token_id TEXT NOT NULL,
|
|
726
|
+
market_id TEXT,
|
|
727
|
+
market_question TEXT,
|
|
728
|
+
outcome TEXT,
|
|
729
|
+
side TEXT NOT NULL,
|
|
730
|
+
price REAL,
|
|
731
|
+
size REAL NOT NULL,
|
|
732
|
+
fill_price REAL,
|
|
733
|
+
fill_size REAL,
|
|
734
|
+
fee REAL DEFAULT 0,
|
|
735
|
+
order_type TEXT,
|
|
736
|
+
status TEXT DEFAULT 'placed',
|
|
737
|
+
rationale TEXT,
|
|
738
|
+
pnl REAL,
|
|
739
|
+
clob_order_id TEXT,
|
|
740
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
741
|
+
)
|
|
742
|
+
`);
|
|
743
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_trades_agent ON poly_trade_log(agent_id, created_at DESC)`);
|
|
744
|
+
await db.execute(`
|
|
745
|
+
CREATE TABLE IF NOT EXISTS poly_price_alerts (
|
|
746
|
+
id TEXT PRIMARY KEY,
|
|
747
|
+
agent_id TEXT NOT NULL,
|
|
748
|
+
token_id TEXT NOT NULL,
|
|
749
|
+
market_question TEXT,
|
|
750
|
+
condition TEXT NOT NULL,
|
|
751
|
+
target_price REAL,
|
|
752
|
+
pct_change REAL,
|
|
753
|
+
base_price REAL,
|
|
754
|
+
repeat_alert INTEGER DEFAULT 0,
|
|
755
|
+
auto_trade_config TEXT,
|
|
756
|
+
bracket_group TEXT,
|
|
757
|
+
bracket_role TEXT,
|
|
758
|
+
triggered INTEGER DEFAULT 0,
|
|
759
|
+
triggered_at TIMESTAMP,
|
|
760
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
761
|
+
)
|
|
762
|
+
`);
|
|
763
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_alerts_agent ON poly_price_alerts(agent_id, triggered)`);
|
|
764
|
+
try {
|
|
765
|
+
await db.execute(`ALTER TABLE poly_price_alerts ADD COLUMN bracket_group TEXT`);
|
|
766
|
+
} catch {
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
await db.execute(`ALTER TABLE poly_price_alerts ADD COLUMN bracket_role TEXT`);
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
await db.execute(`
|
|
773
|
+
CREATE TABLE IF NOT EXISTS poly_paper_positions (
|
|
774
|
+
id ${autoId},
|
|
775
|
+
agent_id TEXT NOT NULL,
|
|
776
|
+
token_id TEXT NOT NULL,
|
|
777
|
+
side TEXT NOT NULL,
|
|
778
|
+
entry_price REAL NOT NULL,
|
|
779
|
+
size REAL NOT NULL,
|
|
780
|
+
market_question TEXT,
|
|
781
|
+
rationale TEXT,
|
|
782
|
+
closed INTEGER DEFAULT 0,
|
|
783
|
+
exit_price REAL,
|
|
784
|
+
pnl REAL,
|
|
785
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
786
|
+
closed_at TIMESTAMP
|
|
787
|
+
)
|
|
788
|
+
`);
|
|
789
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_paper_agent ON poly_paper_positions(agent_id, closed)`);
|
|
790
|
+
await db.execute(`
|
|
791
|
+
CREATE TABLE IF NOT EXISTS poly_daily_counters (
|
|
792
|
+
agent_id TEXT NOT NULL,
|
|
793
|
+
date TEXT NOT NULL,
|
|
794
|
+
trade_count INTEGER DEFAULT 0,
|
|
795
|
+
daily_loss REAL DEFAULT 0,
|
|
796
|
+
paused INTEGER DEFAULT 0,
|
|
797
|
+
pause_reason TEXT,
|
|
798
|
+
PRIMARY KEY (agent_id, date)
|
|
799
|
+
)
|
|
800
|
+
`);
|
|
801
|
+
await db.execute(`
|
|
802
|
+
CREATE TABLE IF NOT EXISTS poly_auto_approve_rules (
|
|
803
|
+
id TEXT PRIMARY KEY,
|
|
804
|
+
agent_id TEXT NOT NULL,
|
|
805
|
+
max_size REAL DEFAULT 10,
|
|
806
|
+
categories TEXT DEFAULT '[]',
|
|
807
|
+
sides TEXT DEFAULT '["BUY","SELL"]',
|
|
808
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
809
|
+
)
|
|
810
|
+
`);
|
|
811
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_auto_rules_agent ON poly_auto_approve_rules(agent_id)`);
|
|
812
|
+
await db.execute(`
|
|
813
|
+
CREATE TABLE IF NOT EXISTS poly_whitelisted_addresses (
|
|
814
|
+
id TEXT PRIMARY KEY,
|
|
815
|
+
agent_id TEXT NOT NULL,
|
|
816
|
+
label TEXT NOT NULL,
|
|
817
|
+
address TEXT NOT NULL,
|
|
818
|
+
added_by TEXT NOT NULL,
|
|
819
|
+
per_tx_limit REAL DEFAULT 100,
|
|
820
|
+
daily_limit REAL DEFAULT 500,
|
|
821
|
+
cooling_until TEXT NOT NULL,
|
|
822
|
+
is_active INTEGER DEFAULT 1,
|
|
823
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
824
|
+
)
|
|
825
|
+
`);
|
|
826
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_whitelist_agent ON poly_whitelisted_addresses(agent_id, is_active)`);
|
|
827
|
+
await db.execute(`
|
|
828
|
+
CREATE TABLE IF NOT EXISTS poly_transfer_requests (
|
|
829
|
+
id TEXT PRIMARY KEY,
|
|
830
|
+
agent_id TEXT NOT NULL,
|
|
831
|
+
whitelist_id TEXT NOT NULL,
|
|
832
|
+
to_address TEXT NOT NULL,
|
|
833
|
+
to_label TEXT NOT NULL,
|
|
834
|
+
amount REAL NOT NULL,
|
|
835
|
+
token TEXT DEFAULT 'USDC',
|
|
836
|
+
reason TEXT,
|
|
837
|
+
status TEXT DEFAULT 'pending',
|
|
838
|
+
requested_by TEXT DEFAULT 'agent',
|
|
839
|
+
approved_by TEXT,
|
|
840
|
+
tx_hash TEXT,
|
|
841
|
+
error TEXT,
|
|
842
|
+
expires_at TEXT NOT NULL,
|
|
843
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
844
|
+
resolved_at TIMESTAMP
|
|
845
|
+
)
|
|
846
|
+
`);
|
|
847
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_transfers_agent ON poly_transfer_requests(agent_id, status)`);
|
|
848
|
+
await db.execute(`
|
|
849
|
+
CREATE TABLE IF NOT EXISTS poly_transfer_daily (
|
|
850
|
+
agent_id TEXT NOT NULL,
|
|
851
|
+
address TEXT NOT NULL,
|
|
852
|
+
date TEXT NOT NULL,
|
|
853
|
+
total_transferred REAL DEFAULT 0,
|
|
854
|
+
tx_count INTEGER DEFAULT 0,
|
|
855
|
+
PRIMARY KEY (agent_id, address, date)
|
|
856
|
+
)
|
|
857
|
+
`);
|
|
858
|
+
await db.execute(`
|
|
859
|
+
CREATE TABLE IF NOT EXISTS poly_proxy_config (
|
|
860
|
+
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
861
|
+
enabled INTEGER DEFAULT 0,
|
|
862
|
+
proxy_mode TEXT DEFAULT 'http',
|
|
863
|
+
proxy_url TEXT,
|
|
864
|
+
encrypted_proxy_token TEXT,
|
|
865
|
+
vps_host TEXT,
|
|
866
|
+
vps_user TEXT DEFAULT 'root',
|
|
867
|
+
vps_port INTEGER DEFAULT 22,
|
|
868
|
+
socks_port INTEGER DEFAULT 1080,
|
|
869
|
+
auth_method TEXT DEFAULT 'password',
|
|
870
|
+
ssh_key_path TEXT,
|
|
871
|
+
encrypted_ssh_key TEXT,
|
|
872
|
+
encrypted_password TEXT,
|
|
873
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
874
|
+
CHECK (id = 1)
|
|
875
|
+
)
|
|
876
|
+
`);
|
|
877
|
+
const migrateCols = [
|
|
878
|
+
["proxy_mode", "TEXT DEFAULT 'http'"],
|
|
879
|
+
["proxy_url", "TEXT"],
|
|
880
|
+
["encrypted_proxy_token", "TEXT"],
|
|
881
|
+
["encrypted_ssh_key", "TEXT"]
|
|
882
|
+
];
|
|
883
|
+
for (const [col, def] of migrateCols) {
|
|
884
|
+
try {
|
|
885
|
+
await db.execute(`ALTER TABLE poly_proxy_config ADD COLUMN ${col} ${def}`);
|
|
886
|
+
} catch {
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const { initWatcherTables } = await import("./polymarket-watcher-2JCPZUKA.js");
|
|
890
|
+
await initWatcherTables(db.getEngineDB?.() || db);
|
|
891
|
+
dbInitialized = true;
|
|
892
|
+
} catch (err) {
|
|
893
|
+
console.error("[polymarket] DB init failed:", err.message);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async function loadWalletCredentials(agentId, db) {
|
|
897
|
+
if (!db) return null;
|
|
898
|
+
await initPolymarketDB(db);
|
|
899
|
+
try {
|
|
900
|
+
const qFn = db.query || db.execute;
|
|
901
|
+
const rows = await qFn.call(db, `SELECT * FROM poly_wallet_credentials WHERE agent_id = $1`, [agentId]);
|
|
902
|
+
const row = rows?.rows?.[0] || (Array.isArray(rows) ? rows[0] : null);
|
|
903
|
+
if (!row) return null;
|
|
904
|
+
const vault = getVaultInstance();
|
|
905
|
+
const decryptField = (val) => {
|
|
906
|
+
if (!val) return val;
|
|
907
|
+
try {
|
|
908
|
+
return vault ? vault.decrypt(val) : val;
|
|
909
|
+
} catch {
|
|
910
|
+
return val;
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
return {
|
|
914
|
+
privateKey: decryptField(row.private_key_encrypted),
|
|
915
|
+
funderAddress: row.funder_address,
|
|
916
|
+
signatureType: row.signature_type || 0,
|
|
917
|
+
rpcUrl: row.rpc_url,
|
|
918
|
+
apiCreds: row.api_key ? {
|
|
919
|
+
apiKey: decryptField(row.api_key),
|
|
920
|
+
secret: decryptField(row.api_secret),
|
|
921
|
+
passphrase: decryptField(row.api_passphrase)
|
|
922
|
+
} : void 0
|
|
923
|
+
};
|
|
924
|
+
} catch {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
async function saveWalletCredentials(agentId, db, creds) {
|
|
929
|
+
if (!db) return;
|
|
930
|
+
await initPolymarketDB(db);
|
|
931
|
+
const vault = getVaultInstance();
|
|
932
|
+
const encryptField = (val) => {
|
|
933
|
+
if (!val) return val || null;
|
|
934
|
+
try {
|
|
935
|
+
return vault ? vault.encrypt(val) : val;
|
|
936
|
+
} catch {
|
|
937
|
+
return val;
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
await db.execute(`
|
|
941
|
+
INSERT INTO poly_wallet_credentials (agent_id, private_key_encrypted, funder_address, signature_type, api_key, api_secret, api_passphrase, rpc_url, updated_at)
|
|
942
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP)
|
|
943
|
+
ON CONFLICT (agent_id) DO UPDATE SET
|
|
944
|
+
private_key_encrypted = $2, funder_address = $3, signature_type = $4,
|
|
945
|
+
api_key = $5, api_secret = $6, api_passphrase = $7, rpc_url = $8,
|
|
946
|
+
updated_at = CURRENT_TIMESTAMP
|
|
947
|
+
`, [
|
|
948
|
+
agentId,
|
|
949
|
+
encryptField(creds.privateKey),
|
|
950
|
+
creds.funderAddress || null,
|
|
951
|
+
creds.signatureType || 0,
|
|
952
|
+
encryptField(creds.apiCreds?.apiKey),
|
|
953
|
+
encryptField(creds.apiCreds?.secret),
|
|
954
|
+
encryptField(creds.apiCreds?.passphrase),
|
|
955
|
+
creds.rpcUrl || null
|
|
956
|
+
]);
|
|
957
|
+
}
|
|
958
|
+
var DEFAULT_CONFIG = {
|
|
959
|
+
mode: "approval",
|
|
960
|
+
maxPositionSize: 100,
|
|
961
|
+
maxOrderSize: 50,
|
|
962
|
+
maxTotalExposure: 500,
|
|
963
|
+
maxDailyTrades: 10,
|
|
964
|
+
maxDailyLoss: 50,
|
|
965
|
+
maxDrawdownPct: 20,
|
|
966
|
+
allowedCategories: [],
|
|
967
|
+
blockedCategories: [],
|
|
968
|
+
blockedMarkets: [],
|
|
969
|
+
minLiquidity: 0,
|
|
970
|
+
minVolume: 0,
|
|
971
|
+
maxSpreadPct: 100,
|
|
972
|
+
stopLossPct: 0,
|
|
973
|
+
takeProfitPct: 0,
|
|
974
|
+
trailingStopPct: 0,
|
|
975
|
+
rebalanceInterval: "never",
|
|
976
|
+
notificationChannel: "",
|
|
977
|
+
notifyOn: ["trade_filled", "stop_loss", "circuit_breaker", "market_resolved"],
|
|
978
|
+
cashReservePct: 20
|
|
979
|
+
};
|
|
980
|
+
async function loadConfig(agentId, db) {
|
|
981
|
+
if (!db) return { ...DEFAULT_CONFIG };
|
|
982
|
+
try {
|
|
983
|
+
const qFn = db.query || db.execute;
|
|
984
|
+
let rows = await qFn.call(db, `SELECT * FROM poly_trading_config WHERE agent_id = $1`, [agentId]);
|
|
985
|
+
const row = rows?.rows?.[0] || rows?.[0];
|
|
986
|
+
if (!row) {
|
|
987
|
+
console.log("[polymarket] loadConfig: no row for", agentId, "method:", db.query ? "query" : "execute");
|
|
988
|
+
return { ...DEFAULT_CONFIG };
|
|
989
|
+
}
|
|
990
|
+
return {
|
|
991
|
+
mode: row.mode || "approval",
|
|
992
|
+
maxPositionSize: row.max_position_size ?? 100,
|
|
993
|
+
maxOrderSize: row.max_order_size ?? 50,
|
|
994
|
+
maxTotalExposure: row.max_total_exposure ?? 500,
|
|
995
|
+
maxDailyTrades: row.max_daily_trades ?? 10,
|
|
996
|
+
maxDailyLoss: row.max_daily_loss ?? 50,
|
|
997
|
+
maxDrawdownPct: row.max_drawdown_pct ?? 20,
|
|
998
|
+
allowedCategories: JSON.parse(row.allowed_categories || "[]"),
|
|
999
|
+
blockedCategories: JSON.parse(row.blocked_categories || "[]"),
|
|
1000
|
+
blockedMarkets: JSON.parse(row.blocked_markets || "[]"),
|
|
1001
|
+
minLiquidity: row.min_liquidity ?? 0,
|
|
1002
|
+
minVolume: row.min_volume ?? 0,
|
|
1003
|
+
maxSpreadPct: row.max_spread_pct ?? 100,
|
|
1004
|
+
stopLossPct: row.stop_loss_pct ?? 0,
|
|
1005
|
+
takeProfitPct: row.take_profit_pct ?? 0,
|
|
1006
|
+
trailingStopPct: row.trailing_stop_pct ?? 0,
|
|
1007
|
+
rebalanceInterval: row.rebalance_interval || "never",
|
|
1008
|
+
notificationChannel: row.notification_channel || "",
|
|
1009
|
+
notifyOn: JSON.parse(row.notify_on || "[]"),
|
|
1010
|
+
cashReservePct: row.cash_reserve_pct ?? 20
|
|
1011
|
+
};
|
|
1012
|
+
} catch {
|
|
1013
|
+
return { ...DEFAULT_CONFIG };
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
async function saveConfig(agentId, db, config) {
|
|
1017
|
+
if (!db) return;
|
|
1018
|
+
await db.execute(`
|
|
1019
|
+
INSERT INTO poly_trading_config (agent_id, mode, max_position_size, max_order_size, max_total_exposure,
|
|
1020
|
+
max_daily_trades, max_daily_loss, max_drawdown_pct, allowed_categories, blocked_categories, blocked_markets,
|
|
1021
|
+
min_liquidity, min_volume, max_spread_pct, stop_loss_pct, take_profit_pct, trailing_stop_pct,
|
|
1022
|
+
rebalance_interval, notification_channel, notify_on, cash_reserve_pct, updated_at)
|
|
1023
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,CURRENT_TIMESTAMP)
|
|
1024
|
+
ON CONFLICT (agent_id) DO UPDATE SET
|
|
1025
|
+
mode=$2, max_position_size=$3, max_order_size=$4, max_total_exposure=$5,
|
|
1026
|
+
max_daily_trades=$6, max_daily_loss=$7, max_drawdown_pct=$8,
|
|
1027
|
+
allowed_categories=$9, blocked_categories=$10, blocked_markets=$11,
|
|
1028
|
+
min_liquidity=$12, min_volume=$13, max_spread_pct=$14,
|
|
1029
|
+
stop_loss_pct=$15, take_profit_pct=$16, trailing_stop_pct=$17,
|
|
1030
|
+
rebalance_interval=$18, notification_channel=$19, notify_on=$20, cash_reserve_pct=$21,
|
|
1031
|
+
updated_at=CURRENT_TIMESTAMP
|
|
1032
|
+
`, [
|
|
1033
|
+
agentId,
|
|
1034
|
+
config.mode,
|
|
1035
|
+
config.maxPositionSize,
|
|
1036
|
+
config.maxOrderSize,
|
|
1037
|
+
config.maxTotalExposure,
|
|
1038
|
+
config.maxDailyTrades,
|
|
1039
|
+
config.maxDailyLoss,
|
|
1040
|
+
config.maxDrawdownPct,
|
|
1041
|
+
JSON.stringify(config.allowedCategories),
|
|
1042
|
+
JSON.stringify(config.blockedCategories),
|
|
1043
|
+
JSON.stringify(config.blockedMarkets),
|
|
1044
|
+
config.minLiquidity,
|
|
1045
|
+
config.minVolume,
|
|
1046
|
+
config.maxSpreadPct,
|
|
1047
|
+
config.stopLossPct,
|
|
1048
|
+
config.takeProfitPct,
|
|
1049
|
+
config.trailingStopPct,
|
|
1050
|
+
config.rebalanceInterval,
|
|
1051
|
+
config.notificationChannel,
|
|
1052
|
+
JSON.stringify(config.notifyOn),
|
|
1053
|
+
config.cashReservePct
|
|
1054
|
+
]);
|
|
1055
|
+
}
|
|
1056
|
+
async function getDailyCounter(agentId, db) {
|
|
1057
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1058
|
+
if (!db) return { count: 0, loss: 0, paused: false, reason: "" };
|
|
1059
|
+
try {
|
|
1060
|
+
const rows = await (db.query || db.execute).call(db, `SELECT * FROM poly_daily_counters WHERE agent_id = $1 AND date = $2`, [agentId, today]);
|
|
1061
|
+
const row = rows?.rows?.[0] || rows?.[0];
|
|
1062
|
+
if (!row) return { count: 0, loss: 0, paused: false, reason: "" };
|
|
1063
|
+
return { count: row.trade_count || 0, loss: row.daily_loss || 0, paused: !!row.paused, reason: row.pause_reason || "" };
|
|
1064
|
+
} catch {
|
|
1065
|
+
return { count: 0, loss: 0, paused: false, reason: "" };
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
async function incrementDailyCounter(agentId, db, loss = 0) {
|
|
1069
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1070
|
+
if (!db) return;
|
|
1071
|
+
await db.execute(`
|
|
1072
|
+
INSERT INTO poly_daily_counters (agent_id, date, trade_count, daily_loss) VALUES ($1, $2, 1, $3)
|
|
1073
|
+
ON CONFLICT (agent_id, date) DO UPDATE SET trade_count = poly_daily_counters.trade_count + 1, daily_loss = poly_daily_counters.daily_loss + $3
|
|
1074
|
+
`, [agentId, today, loss]);
|
|
1075
|
+
}
|
|
1076
|
+
async function pauseTrading(agentId, db, reason) {
|
|
1077
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1078
|
+
if (!db) return;
|
|
1079
|
+
await db.execute(`
|
|
1080
|
+
INSERT INTO poly_daily_counters (agent_id, date, paused, pause_reason) VALUES ($1, $2, 1, $3)
|
|
1081
|
+
ON CONFLICT (agent_id, date) DO UPDATE SET paused = 1, pause_reason = $3
|
|
1082
|
+
`, [agentId, today, reason]);
|
|
1083
|
+
}
|
|
1084
|
+
async function resumeTrading(agentId, db) {
|
|
1085
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1086
|
+
if (!db) return;
|
|
1087
|
+
await db.execute(`
|
|
1088
|
+
UPDATE poly_daily_counters SET paused = 0, pause_reason = '' WHERE agent_id = $1 AND date = $2
|
|
1089
|
+
`, [agentId, today]);
|
|
1090
|
+
}
|
|
1091
|
+
async function savePendingTrade(db, trade) {
|
|
1092
|
+
if (!db) return;
|
|
1093
|
+
await db.execute(`
|
|
1094
|
+
INSERT INTO poly_pending_trades (id, agent_id, token_id, side, price, size, order_type, tick_size, neg_risk, market_question, outcome, rationale, urgency)
|
|
1095
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
|
|
1096
|
+
`, [
|
|
1097
|
+
trade.id,
|
|
1098
|
+
trade.agentId,
|
|
1099
|
+
trade.tokenId,
|
|
1100
|
+
trade.side,
|
|
1101
|
+
trade.price,
|
|
1102
|
+
trade.size,
|
|
1103
|
+
trade.orderType,
|
|
1104
|
+
trade.tickSize,
|
|
1105
|
+
trade.negRisk ? 1 : 0,
|
|
1106
|
+
trade.marketQuestion,
|
|
1107
|
+
trade.outcome,
|
|
1108
|
+
trade.rationale,
|
|
1109
|
+
trade.urgency
|
|
1110
|
+
]);
|
|
1111
|
+
}
|
|
1112
|
+
async function getPendingTrades(agentId, db) {
|
|
1113
|
+
if (!db) return [];
|
|
1114
|
+
try {
|
|
1115
|
+
const rows = await (db.query || db.execute).call(db, `SELECT * FROM poly_pending_trades WHERE agent_id = $1 AND status = 'pending' ORDER BY created_at DESC`, [agentId]);
|
|
1116
|
+
return rows?.rows || rows || [];
|
|
1117
|
+
} catch {
|
|
1118
|
+
return [];
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
async function resolvePendingTrade(db, tradeId, status, resolvedBy) {
|
|
1122
|
+
if (!db) return;
|
|
1123
|
+
await db.execute(`UPDATE poly_pending_trades SET status = $1, resolved_at = CURRENT_TIMESTAMP, resolved_by = $2 WHERE id = $3`, [status, resolvedBy, tradeId]);
|
|
1124
|
+
}
|
|
1125
|
+
async function logTrade(db, trade) {
|
|
1126
|
+
if (!db) {
|
|
1127
|
+
console.warn("[logTrade] No db provided, skipping");
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
try {
|
|
1131
|
+
await db.execute(`
|
|
1132
|
+
INSERT INTO poly_trade_log (id, agent_id, token_id, market_id, market_question, outcome, side, price, size, fill_price, fill_size, fee, order_type, status, rationale, pnl, clob_order_id)
|
|
1133
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)
|
|
1134
|
+
`, [
|
|
1135
|
+
trade.id,
|
|
1136
|
+
trade.agentId,
|
|
1137
|
+
trade.tokenId,
|
|
1138
|
+
trade.marketId || null,
|
|
1139
|
+
trade.marketQuestion || null,
|
|
1140
|
+
trade.outcome || null,
|
|
1141
|
+
trade.side,
|
|
1142
|
+
trade.price || null,
|
|
1143
|
+
trade.size,
|
|
1144
|
+
trade.fillPrice || null,
|
|
1145
|
+
trade.fillSize || null,
|
|
1146
|
+
trade.fee || 0,
|
|
1147
|
+
trade.orderType || null,
|
|
1148
|
+
trade.status,
|
|
1149
|
+
trade.rationale || null,
|
|
1150
|
+
trade.pnl || null,
|
|
1151
|
+
trade.clobOrderId || null
|
|
1152
|
+
]);
|
|
1153
|
+
} catch (err) {
|
|
1154
|
+
console.error(`[logTrade] FAILED to log trade ${trade.id}: ${err.message}`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
async function saveAlert(db, alert) {
|
|
1158
|
+
if (!db) return;
|
|
1159
|
+
await db.execute(`
|
|
1160
|
+
INSERT INTO poly_price_alerts (id, agent_id, token_id, market_question, condition, target_price, pct_change, base_price, repeat_alert, auto_trade_config)
|
|
1161
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
|
|
1162
|
+
`, [
|
|
1163
|
+
alert.id,
|
|
1164
|
+
alert.agentId,
|
|
1165
|
+
alert.tokenId,
|
|
1166
|
+
alert.marketQuestion,
|
|
1167
|
+
alert.condition,
|
|
1168
|
+
alert.targetPrice || null,
|
|
1169
|
+
alert.pctChange || null,
|
|
1170
|
+
alert.basePrice,
|
|
1171
|
+
alert.repeat ? 1 : 0,
|
|
1172
|
+
alert.autoTrade ? JSON.stringify(alert.autoTrade) : null
|
|
1173
|
+
]);
|
|
1174
|
+
}
|
|
1175
|
+
async function getAlerts(agentId, db) {
|
|
1176
|
+
if (!db) return [];
|
|
1177
|
+
try {
|
|
1178
|
+
const rows = await (db.query || db.execute).call(db, `SELECT * FROM poly_price_alerts WHERE agent_id = $1 AND triggered = 0 ORDER BY created_at DESC`, [agentId]);
|
|
1179
|
+
return rows?.rows || rows || [];
|
|
1180
|
+
} catch {
|
|
1181
|
+
return [];
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async function deleteAlert(db, alertId) {
|
|
1185
|
+
if (!db) return;
|
|
1186
|
+
await db.execute(`DELETE FROM poly_price_alerts WHERE id = $1`, [alertId]);
|
|
1187
|
+
}
|
|
1188
|
+
async function deleteAllAlerts(agentId, db) {
|
|
1189
|
+
if (!db) return;
|
|
1190
|
+
await db.execute(`DELETE FROM poly_price_alerts WHERE agent_id = $1`, [agentId]);
|
|
1191
|
+
}
|
|
1192
|
+
var DEFAULT_TAKE_PROFIT_PCT = 15;
|
|
1193
|
+
var DEFAULT_STOP_LOSS_PCT = 10;
|
|
1194
|
+
async function createBracketAlerts(db, opts) {
|
|
1195
|
+
const tpPct = opts.takeProfitPct ?? DEFAULT_TAKE_PROFIT_PCT;
|
|
1196
|
+
const slPct = opts.stopLossPct ?? DEFAULT_STOP_LOSS_PCT;
|
|
1197
|
+
const bracketGroup = `bracket_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1198
|
+
const tpAlertId = `tp_${bracketGroup}`;
|
|
1199
|
+
const slAlertId = `sl_${bracketGroup}`;
|
|
1200
|
+
const takeProfitPrice = Math.round(opts.buyPrice * (1 + tpPct / 100) * 100) / 100;
|
|
1201
|
+
const stopLossPrice = Math.round(opts.buyPrice * (1 - slPct / 100) * 100) / 100;
|
|
1202
|
+
await db.execute(`
|
|
1203
|
+
INSERT INTO poly_price_alerts (id, agent_id, token_id, market_question, condition, target_price, base_price, repeat_alert, auto_trade_config, bracket_group, bracket_role)
|
|
1204
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
|
|
1205
|
+
`, [
|
|
1206
|
+
tpAlertId,
|
|
1207
|
+
opts.agentId,
|
|
1208
|
+
opts.tokenId,
|
|
1209
|
+
opts.marketQuestion,
|
|
1210
|
+
"above",
|
|
1211
|
+
takeProfitPrice,
|
|
1212
|
+
opts.buyPrice,
|
|
1213
|
+
0,
|
|
1214
|
+
JSON.stringify({ side: "SELL", size: opts.size, token_id: opts.tokenId, source_trade: opts.sourceTradeId }),
|
|
1215
|
+
bracketGroup,
|
|
1216
|
+
"take_profit"
|
|
1217
|
+
]);
|
|
1218
|
+
await db.execute(`
|
|
1219
|
+
INSERT INTO poly_price_alerts (id, agent_id, token_id, market_question, condition, target_price, base_price, repeat_alert, auto_trade_config, bracket_group, bracket_role)
|
|
1220
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
|
|
1221
|
+
`, [
|
|
1222
|
+
slAlertId,
|
|
1223
|
+
opts.agentId,
|
|
1224
|
+
opts.tokenId,
|
|
1225
|
+
opts.marketQuestion,
|
|
1226
|
+
"below",
|
|
1227
|
+
stopLossPrice,
|
|
1228
|
+
opts.buyPrice,
|
|
1229
|
+
0,
|
|
1230
|
+
JSON.stringify({ side: "SELL", size: opts.size, token_id: opts.tokenId, source_trade: opts.sourceTradeId }),
|
|
1231
|
+
bracketGroup,
|
|
1232
|
+
"stop_loss"
|
|
1233
|
+
]);
|
|
1234
|
+
console.log(`[bracket] Created bracket for ${opts.marketQuestion}: TP@${takeProfitPrice} (+${tpPct}%), SL@${stopLossPrice} (-${slPct}%) | group=${bracketGroup}`);
|
|
1235
|
+
return { bracketGroup, takeProfitAlertId: tpAlertId, stopLossAlertId: slAlertId, takeProfitPrice, stopLossPrice };
|
|
1236
|
+
}
|
|
1237
|
+
async function cancelBracketSibling(db, firedAlertId, bracketGroup) {
|
|
1238
|
+
if (!db || !bracketGroup) return null;
|
|
1239
|
+
try {
|
|
1240
|
+
const result = await db.execute(
|
|
1241
|
+
`UPDATE poly_price_alerts SET triggered = 1, triggered_at = CURRENT_TIMESTAMP WHERE bracket_group = $1 AND id != $2 AND triggered = 0`,
|
|
1242
|
+
[bracketGroup, firedAlertId]
|
|
1243
|
+
);
|
|
1244
|
+
const affected = result?.rowCount || result?.changes || 0;
|
|
1245
|
+
if (affected > 0) {
|
|
1246
|
+
console.log(`[bracket] Cancelled sibling of ${firedAlertId} in bracket group ${bracketGroup}`);
|
|
1247
|
+
try {
|
|
1248
|
+
const sibling = await (db.query || db.execute).call(
|
|
1249
|
+
db,
|
|
1250
|
+
`SELECT id, bracket_role FROM poly_price_alerts WHERE bracket_group = $1 AND id != $2`,
|
|
1251
|
+
[bracketGroup, firedAlertId]
|
|
1252
|
+
);
|
|
1253
|
+
const row = sibling?.rows?.[0] || sibling?.[0];
|
|
1254
|
+
return row?.id || null;
|
|
1255
|
+
} catch {
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return null;
|
|
1260
|
+
} catch (e) {
|
|
1261
|
+
console.error(`[bracket] Failed to cancel sibling: ${e.message}`);
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
async function getBracketConfig(agentId, db) {
|
|
1266
|
+
try {
|
|
1267
|
+
const config = await loadConfig(agentId, db);
|
|
1268
|
+
const bracket = config?.bracket || {};
|
|
1269
|
+
return {
|
|
1270
|
+
enabled: bracket.enabled !== false,
|
|
1271
|
+
// enabled by default
|
|
1272
|
+
takeProfitPct: bracket.take_profit_pct ?? config.takeProfitPct ?? DEFAULT_TAKE_PROFIT_PCT,
|
|
1273
|
+
stopLossPct: bracket.stop_loss_pct ?? config.stopLossPct ?? DEFAULT_STOP_LOSS_PCT,
|
|
1274
|
+
trailingStopPct: config.trailingStopPct ?? 12
|
|
1275
|
+
};
|
|
1276
|
+
} catch {
|
|
1277
|
+
return { enabled: true, takeProfitPct: DEFAULT_TAKE_PROFIT_PCT, stopLossPct: DEFAULT_STOP_LOSS_PCT, trailingStopPct: 12 };
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
async function checkAlerts(agentId, db) {
|
|
1281
|
+
const alerts = await getAlerts(agentId, db);
|
|
1282
|
+
if (alerts.length === 0) return [];
|
|
1283
|
+
const triggered = [];
|
|
1284
|
+
const CLOB = "https://clob.polymarket.com";
|
|
1285
|
+
for (const alert of alerts) {
|
|
1286
|
+
try {
|
|
1287
|
+
const mid = await (await fetch(`${CLOB}/midpoint?token_id=${alert.token_id}`)).json();
|
|
1288
|
+
const currentPrice = parseFloat(mid?.mid || "0");
|
|
1289
|
+
if (!currentPrice) continue;
|
|
1290
|
+
let fire = false;
|
|
1291
|
+
let reason = "";
|
|
1292
|
+
if (alert.condition === "above" && alert.target_price && currentPrice >= alert.target_price) {
|
|
1293
|
+
fire = true;
|
|
1294
|
+
reason = `Price ${(currentPrice * 100).toFixed(1)}% crossed above target ${(alert.target_price * 100).toFixed(1)}%`;
|
|
1295
|
+
} else if (alert.condition === "below" && alert.target_price && currentPrice <= alert.target_price) {
|
|
1296
|
+
fire = true;
|
|
1297
|
+
reason = `Price ${(currentPrice * 100).toFixed(1)}% dropped below target ${(alert.target_price * 100).toFixed(1)}%`;
|
|
1298
|
+
} else if (alert.condition === "pct_change" && alert.pct_change && alert.base_price) {
|
|
1299
|
+
const change = Math.abs(currentPrice - alert.base_price) / alert.base_price * 100;
|
|
1300
|
+
if (change >= alert.pct_change) {
|
|
1301
|
+
fire = true;
|
|
1302
|
+
const dir = currentPrice > alert.base_price ? "up" : "down";
|
|
1303
|
+
reason = `Price moved ${dir} ${change.toFixed(1)}% (threshold: ${alert.pct_change}%). Was ${(alert.base_price * 100).toFixed(1)}%, now ${(currentPrice * 100).toFixed(1)}%`;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (fire) {
|
|
1307
|
+
triggered.push({
|
|
1308
|
+
alert_id: alert.id,
|
|
1309
|
+
token_id: alert.token_id,
|
|
1310
|
+
market: alert.market_question,
|
|
1311
|
+
condition: alert.condition,
|
|
1312
|
+
current_price: currentPrice,
|
|
1313
|
+
reason,
|
|
1314
|
+
auto_trade: alert.auto_trade_config ? JSON.parse(alert.auto_trade_config) : null
|
|
1315
|
+
});
|
|
1316
|
+
if (alert.repeat_alert) {
|
|
1317
|
+
await db.execute(`UPDATE poly_price_alerts SET base_price = $1 WHERE id = $2`, [currentPrice, alert.id]);
|
|
1318
|
+
} else {
|
|
1319
|
+
await db.execute(`UPDATE poly_price_alerts SET triggered = 1 WHERE id = $1`, [alert.id]);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
} catch {
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return triggered;
|
|
1326
|
+
}
|
|
1327
|
+
async function savePaperPosition(db, pos) {
|
|
1328
|
+
if (!db) return;
|
|
1329
|
+
await db.execute(`
|
|
1330
|
+
INSERT INTO poly_paper_positions (agent_id, token_id, side, entry_price, size, market_question, rationale)
|
|
1331
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7)
|
|
1332
|
+
`, [pos.agentId, pos.tokenId, pos.side, pos.entryPrice, pos.size, pos.marketQuestion, pos.rationale]);
|
|
1333
|
+
}
|
|
1334
|
+
async function getPaperPositions(agentId, db) {
|
|
1335
|
+
if (!db) return [];
|
|
1336
|
+
try {
|
|
1337
|
+
const rows = await (db.query || db.execute).call(db, `SELECT * FROM poly_paper_positions WHERE agent_id = $1 AND closed = 0 ORDER BY created_at DESC`, [agentId]);
|
|
1338
|
+
return rows?.rows || rows || [];
|
|
1339
|
+
} catch {
|
|
1340
|
+
return [];
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
async function getAutoApproveRules(agentId, db) {
|
|
1344
|
+
if (!db) return [];
|
|
1345
|
+
try {
|
|
1346
|
+
const rows = await (db.query || db.execute).call(db, `SELECT * FROM poly_auto_approve_rules WHERE agent_id = $1`, [agentId]);
|
|
1347
|
+
return (rows?.rows || rows || []).map((r) => ({
|
|
1348
|
+
id: r.id,
|
|
1349
|
+
maxSize: r.max_size,
|
|
1350
|
+
categories: JSON.parse(r.categories || "[]"),
|
|
1351
|
+
sides: JSON.parse(r.sides || "[]")
|
|
1352
|
+
}));
|
|
1353
|
+
} catch {
|
|
1354
|
+
return [];
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
async function saveAutoApproveRule(db, rule) {
|
|
1358
|
+
if (!db) return;
|
|
1359
|
+
await db.execute(
|
|
1360
|
+
`INSERT INTO poly_auto_approve_rules (id, agent_id, max_size, categories, sides) VALUES ($1,$2,$3,$4,$5)`,
|
|
1361
|
+
[rule.id, rule.agentId, rule.maxSize, JSON.stringify(rule.categories), JSON.stringify(rule.sides)]
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
async function deleteAutoApproveRule(db, ruleId) {
|
|
1365
|
+
if (!db) return;
|
|
1366
|
+
await db.execute(`DELETE FROM poly_auto_approve_rules WHERE id = $1`, [ruleId]);
|
|
1367
|
+
}
|
|
1368
|
+
async function generateWallet() {
|
|
1369
|
+
const sdk = await ensureSDK();
|
|
1370
|
+
if (!sdk.ready) {
|
|
1371
|
+
const crypto = await import("crypto");
|
|
1372
|
+
const privateKey = "0x" + crypto.randomBytes(32).toString("hex");
|
|
1373
|
+
return { address: "(install ethers to derive address)", privateKey };
|
|
1374
|
+
}
|
|
1375
|
+
try {
|
|
1376
|
+
const ethWallet = await importSDK("@ethersproject/wallet");
|
|
1377
|
+
if (!ethWallet) throw new Error("importSDK returned null for @ethersproject/wallet");
|
|
1378
|
+
const Wallet = ethWallet.Wallet || ethWallet.default?.Wallet;
|
|
1379
|
+
if (!Wallet) throw new Error("Wallet class not found in module. Keys: " + Object.keys(ethWallet).join(","));
|
|
1380
|
+
const wallet = Wallet.createRandom();
|
|
1381
|
+
return { address: wallet.address, privateKey: wallet.privateKey };
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
console.error("[polymarket] generateWallet failed:", err.message);
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async function initLearningDB(db) {
|
|
1388
|
+
if (!db) return;
|
|
1389
|
+
try {
|
|
1390
|
+
await db.execute(`
|
|
1391
|
+
CREATE TABLE IF NOT EXISTS poly_predictions (
|
|
1392
|
+
id TEXT PRIMARY KEY,
|
|
1393
|
+
agent_id TEXT NOT NULL,
|
|
1394
|
+
market_id TEXT,
|
|
1395
|
+
token_id TEXT NOT NULL,
|
|
1396
|
+
market_question TEXT,
|
|
1397
|
+
predicted_outcome TEXT NOT NULL,
|
|
1398
|
+
predicted_probability REAL NOT NULL,
|
|
1399
|
+
market_price_at_prediction REAL NOT NULL,
|
|
1400
|
+
confidence REAL NOT NULL,
|
|
1401
|
+
reasoning TEXT,
|
|
1402
|
+
signals_used TEXT,
|
|
1403
|
+
category TEXT,
|
|
1404
|
+
resolved INTEGER DEFAULT 0,
|
|
1405
|
+
actual_outcome TEXT,
|
|
1406
|
+
was_correct INTEGER,
|
|
1407
|
+
pnl REAL,
|
|
1408
|
+
lesson_extracted INTEGER DEFAULT 0,
|
|
1409
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
1410
|
+
resolved_at TIMESTAMP
|
|
1411
|
+
)
|
|
1412
|
+
`);
|
|
1413
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_pred_agent ON poly_predictions(agent_id, resolved)`);
|
|
1414
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_pred_category ON poly_predictions(agent_id, category)`);
|
|
1415
|
+
await db.execute(`
|
|
1416
|
+
CREATE TABLE IF NOT EXISTS poly_strategy_stats (
|
|
1417
|
+
id TEXT PRIMARY KEY,
|
|
1418
|
+
agent_id TEXT NOT NULL,
|
|
1419
|
+
strategy_name TEXT NOT NULL,
|
|
1420
|
+
total_predictions INTEGER DEFAULT 0,
|
|
1421
|
+
correct_predictions INTEGER DEFAULT 0,
|
|
1422
|
+
total_pnl REAL DEFAULT 0,
|
|
1423
|
+
avg_confidence REAL DEFAULT 0,
|
|
1424
|
+
brier_score REAL DEFAULT 0,
|
|
1425
|
+
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
1426
|
+
UNIQUE(agent_id, strategy_name)
|
|
1427
|
+
)
|
|
1428
|
+
`);
|
|
1429
|
+
await db.execute(`
|
|
1430
|
+
CREATE TABLE IF NOT EXISTS poly_lessons (
|
|
1431
|
+
id TEXT PRIMARY KEY,
|
|
1432
|
+
agent_id TEXT NOT NULL,
|
|
1433
|
+
lesson TEXT NOT NULL,
|
|
1434
|
+
category TEXT NOT NULL,
|
|
1435
|
+
source_prediction_ids TEXT,
|
|
1436
|
+
importance TEXT DEFAULT 'normal',
|
|
1437
|
+
times_applied INTEGER DEFAULT 0,
|
|
1438
|
+
last_applied TIMESTAMP,
|
|
1439
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
1440
|
+
)
|
|
1441
|
+
`);
|
|
1442
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS idx_poly_lessons_agent ON poly_lessons(agent_id, category)`);
|
|
1443
|
+
await db.execute(`
|
|
1444
|
+
CREATE TABLE IF NOT EXISTS poly_calibration (
|
|
1445
|
+
agent_id TEXT NOT NULL,
|
|
1446
|
+
bucket TEXT NOT NULL,
|
|
1447
|
+
predictions INTEGER DEFAULT 0,
|
|
1448
|
+
correct INTEGER DEFAULT 0,
|
|
1449
|
+
PRIMARY KEY (agent_id, bucket)
|
|
1450
|
+
)
|
|
1451
|
+
`);
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
console.error("[polymarket] Learning DB init failed:", err.message);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
async function recordPrediction(db, pred) {
|
|
1457
|
+
if (!db) return;
|
|
1458
|
+
await db.execute(`
|
|
1459
|
+
INSERT INTO poly_predictions (id, agent_id, market_id, token_id, market_question, predicted_outcome,
|
|
1460
|
+
predicted_probability, market_price_at_prediction, confidence, reasoning, signals_used, category)
|
|
1461
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
|
|
1462
|
+
`, [
|
|
1463
|
+
pred.id,
|
|
1464
|
+
pred.agentId,
|
|
1465
|
+
pred.marketId || null,
|
|
1466
|
+
pred.tokenId,
|
|
1467
|
+
pred.marketQuestion || null,
|
|
1468
|
+
pred.predictedOutcome,
|
|
1469
|
+
pred.predictedProbability,
|
|
1470
|
+
pred.marketPrice,
|
|
1471
|
+
pred.confidence,
|
|
1472
|
+
pred.reasoning || null,
|
|
1473
|
+
pred.signalsUsed ? JSON.stringify(pred.signalsUsed) : null,
|
|
1474
|
+
pred.category || null
|
|
1475
|
+
]);
|
|
1476
|
+
}
|
|
1477
|
+
async function resolvePrediction(db, predId, actualOutcome, pnl) {
|
|
1478
|
+
if (!db) return;
|
|
1479
|
+
const rows = await (db.query || db.execute).call(db, `SELECT * FROM poly_predictions WHERE id = $1`, [predId]);
|
|
1480
|
+
const pred = rows?.rows?.[0] || rows?.[0];
|
|
1481
|
+
if (!pred) return;
|
|
1482
|
+
const wasCorrect = pred.predicted_outcome.toLowerCase() === actualOutcome.toLowerCase() ? 1 : 0;
|
|
1483
|
+
await db.execute(`
|
|
1484
|
+
UPDATE poly_predictions SET resolved = 1, actual_outcome = $1, was_correct = $2, pnl = $3, resolved_at = CURRENT_TIMESTAMP
|
|
1485
|
+
WHERE id = $4
|
|
1486
|
+
`, [actualOutcome, wasCorrect, pnl, predId]);
|
|
1487
|
+
const bucket = Math.floor(pred.confidence * 10) * 10 + "%";
|
|
1488
|
+
await db.execute(`
|
|
1489
|
+
INSERT INTO poly_calibration (agent_id, bucket, predictions, correct) VALUES ($1, $2, 1, $3)
|
|
1490
|
+
ON CONFLICT (agent_id, bucket) DO UPDATE SET predictions = predictions + 1, correct = correct + $3
|
|
1491
|
+
`, [pred.agent_id, bucket, wasCorrect]);
|
|
1492
|
+
if (pred.signals_used) {
|
|
1493
|
+
try {
|
|
1494
|
+
const signals = JSON.parse(pred.signals_used);
|
|
1495
|
+
for (const signal of signals) {
|
|
1496
|
+
await db.execute(`
|
|
1497
|
+
INSERT INTO poly_strategy_stats (id, agent_id, strategy_name, total_predictions, correct_predictions, total_pnl, avg_confidence, last_updated)
|
|
1498
|
+
VALUES ($1, $2, $3, 1, $4, $5, $6, CURRENT_TIMESTAMP)
|
|
1499
|
+
ON CONFLICT (agent_id, strategy_name) DO UPDATE SET
|
|
1500
|
+
total_predictions = poly_strategy_stats.total_predictions + 1,
|
|
1501
|
+
correct_predictions = poly_strategy_stats.correct_predictions + $4,
|
|
1502
|
+
total_pnl = poly_strategy_stats.total_pnl + $5,
|
|
1503
|
+
avg_confidence = (poly_strategy_stats.avg_confidence * poly_strategy_stats.total_predictions + $6) / (poly_strategy_stats.total_predictions + 1),
|
|
1504
|
+
last_updated = CURRENT_TIMESTAMP
|
|
1505
|
+
`, [
|
|
1506
|
+
`stat_${pred.agent_id}_${signal}`,
|
|
1507
|
+
pred.agent_id,
|
|
1508
|
+
signal,
|
|
1509
|
+
wasCorrect,
|
|
1510
|
+
pnl,
|
|
1511
|
+
pred.confidence
|
|
1512
|
+
]);
|
|
1513
|
+
}
|
|
1514
|
+
} catch {
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
async function storeLesson(db, lesson) {
|
|
1519
|
+
if (!db) return;
|
|
1520
|
+
await db.execute(`
|
|
1521
|
+
INSERT INTO poly_lessons (id, agent_id, lesson, category, source_prediction_ids, importance)
|
|
1522
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
1523
|
+
`, [
|
|
1524
|
+
lesson.id,
|
|
1525
|
+
lesson.agentId,
|
|
1526
|
+
lesson.lesson,
|
|
1527
|
+
lesson.category,
|
|
1528
|
+
lesson.sourcePredictionIds ? JSON.stringify(lesson.sourcePredictionIds) : null,
|
|
1529
|
+
lesson.importance || "normal"
|
|
1530
|
+
]);
|
|
1531
|
+
}
|
|
1532
|
+
async function recallLessons(agentId, db, category) {
|
|
1533
|
+
if (!db) return [];
|
|
1534
|
+
try {
|
|
1535
|
+
const query = category ? `SELECT * FROM poly_lessons WHERE agent_id = $1 AND category = $2 ORDER BY importance DESC, created_at DESC LIMIT 20` : `SELECT * FROM poly_lessons WHERE agent_id = $1 ORDER BY importance DESC, created_at DESC LIMIT 20`;
|
|
1536
|
+
const params = category ? [agentId, category] : [agentId];
|
|
1537
|
+
const rows = await (db.query || db.execute).call(db, query, params);
|
|
1538
|
+
return rows?.rows || rows || [];
|
|
1539
|
+
} catch {
|
|
1540
|
+
return [];
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
async function getCalibration(agentId, db) {
|
|
1544
|
+
if (!db) return [];
|
|
1545
|
+
try {
|
|
1546
|
+
const rows = await (db.query || db.execute).call(db, `SELECT * FROM poly_calibration WHERE agent_id = $1 ORDER BY bucket`, [agentId]);
|
|
1547
|
+
return rows?.rows || rows || [];
|
|
1548
|
+
} catch {
|
|
1549
|
+
return [];
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
async function getStrategyPerformance(agentId, db) {
|
|
1553
|
+
if (!db) return [];
|
|
1554
|
+
try {
|
|
1555
|
+
const rows = await (db.query || db.execute).call(db, `
|
|
1556
|
+
SELECT *, CASE WHEN total_predictions > 0 THEN ROUND(CAST(correct_predictions AS REAL) / total_predictions * 100, 1) ELSE 0 END as win_rate
|
|
1557
|
+
FROM poly_strategy_stats WHERE agent_id = $1 ORDER BY total_pnl DESC
|
|
1558
|
+
`, [agentId]);
|
|
1559
|
+
return rows?.rows || rows || [];
|
|
1560
|
+
} catch {
|
|
1561
|
+
return [];
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
async function getUnresolvedPredictions(agentId, db, marketId) {
|
|
1565
|
+
if (!db) return [];
|
|
1566
|
+
try {
|
|
1567
|
+
const query = marketId ? `SELECT * FROM poly_predictions WHERE agent_id = $1 AND resolved = 0 AND market_id = $2 ORDER BY created_at DESC` : `SELECT * FROM poly_predictions WHERE agent_id = $1 AND resolved = 0 ORDER BY created_at DESC LIMIT 50`;
|
|
1568
|
+
const params = marketId ? [agentId, marketId] : [agentId];
|
|
1569
|
+
const rows = await (db.query || db.execute).call(db, query, params);
|
|
1570
|
+
return rows?.rows || rows || [];
|
|
1571
|
+
} catch {
|
|
1572
|
+
return [];
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
async function getResolvedPredictions(agentId, db, limit = 20) {
|
|
1576
|
+
if (!db) return [];
|
|
1577
|
+
try {
|
|
1578
|
+
const rows = await (db.query || db.execute).call(db, `
|
|
1579
|
+
SELECT * FROM poly_predictions WHERE agent_id = $1 AND resolved = 1 AND lesson_extracted = 0
|
|
1580
|
+
ORDER BY resolved_at DESC LIMIT $2
|
|
1581
|
+
`, [agentId, limit]);
|
|
1582
|
+
return rows?.rows || rows || [];
|
|
1583
|
+
} catch {
|
|
1584
|
+
return [];
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
async function markLessonsExtracted(db, predictionIds) {
|
|
1588
|
+
if (!db || predictionIds.length === 0) return;
|
|
1589
|
+
const placeholders = predictionIds.map((_, i) => `$${i + 1}`).join(",");
|
|
1590
|
+
await db.execute(`UPDATE poly_predictions SET lesson_extracted = 1 WHERE id IN (${placeholders})`, predictionIds);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
export {
|
|
1594
|
+
loadProxyConfig,
|
|
1595
|
+
saveProxyConfig,
|
|
1596
|
+
startProxy,
|
|
1597
|
+
stopProxy,
|
|
1598
|
+
getProxyState,
|
|
1599
|
+
isProxyEnabled,
|
|
1600
|
+
autoConnectProxy,
|
|
1601
|
+
deployProxyToVPS,
|
|
1602
|
+
getClobUrl,
|
|
1603
|
+
getSocksAgent,
|
|
1604
|
+
ensureSDK,
|
|
1605
|
+
importSDK,
|
|
1606
|
+
getClobClient,
|
|
1607
|
+
isPostgresDB,
|
|
1608
|
+
initPolymarketDB,
|
|
1609
|
+
loadWalletCredentials,
|
|
1610
|
+
saveWalletCredentials,
|
|
1611
|
+
loadConfig,
|
|
1612
|
+
saveConfig,
|
|
1613
|
+
getDailyCounter,
|
|
1614
|
+
incrementDailyCounter,
|
|
1615
|
+
pauseTrading,
|
|
1616
|
+
resumeTrading,
|
|
1617
|
+
savePendingTrade,
|
|
1618
|
+
getPendingTrades,
|
|
1619
|
+
resolvePendingTrade,
|
|
1620
|
+
logTrade,
|
|
1621
|
+
saveAlert,
|
|
1622
|
+
getAlerts,
|
|
1623
|
+
deleteAlert,
|
|
1624
|
+
deleteAllAlerts,
|
|
1625
|
+
createBracketAlerts,
|
|
1626
|
+
cancelBracketSibling,
|
|
1627
|
+
getBracketConfig,
|
|
1628
|
+
checkAlerts,
|
|
1629
|
+
savePaperPosition,
|
|
1630
|
+
getPaperPositions,
|
|
1631
|
+
getAutoApproveRules,
|
|
1632
|
+
saveAutoApproveRule,
|
|
1633
|
+
deleteAutoApproveRule,
|
|
1634
|
+
generateWallet,
|
|
1635
|
+
initLearningDB,
|
|
1636
|
+
recordPrediction,
|
|
1637
|
+
resolvePrediction,
|
|
1638
|
+
storeLesson,
|
|
1639
|
+
recallLessons,
|
|
1640
|
+
getCalibration,
|
|
1641
|
+
getStrategyPerformance,
|
|
1642
|
+
getUnresolvedPredictions,
|
|
1643
|
+
getResolvedPredictions,
|
|
1644
|
+
markLessonsExtracted
|
|
1645
|
+
};
|