@elizaos/plugin-vincent 2.0.3-beta.5 → 2.0.3-beta.7
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/dist/TradingProfileCard.d.ts +8 -0
- package/dist/TradingProfileCard.d.ts.map +1 -0
- package/dist/TradingProfileCard.js +70 -0
- package/dist/TradingProfileCard.js.map +1 -0
- package/dist/TradingStrategyPanel.d.ts +10 -0
- package/dist/TradingStrategyPanel.d.ts.map +1 -0
- package/dist/TradingStrategyPanel.js +133 -0
- package/dist/TradingStrategyPanel.js.map +1 -0
- package/dist/VincentAppView.d.ts +4 -0
- package/dist/VincentAppView.d.ts.map +1 -0
- package/dist/VincentAppView.helpers.d.ts +10 -0
- package/dist/VincentAppView.helpers.d.ts.map +1 -0
- package/dist/VincentAppView.helpers.js +21 -0
- package/dist/VincentAppView.helpers.js.map +1 -0
- package/dist/VincentAppView.interact.d.ts +2 -0
- package/dist/VincentAppView.interact.d.ts.map +1 -0
- package/dist/VincentAppView.interact.js +45 -0
- package/dist/VincentAppView.interact.js.map +1 -0
- package/dist/VincentAppView.js +145 -0
- package/dist/VincentAppView.js.map +1 -0
- package/dist/VincentConnectionCard.d.ts +9 -0
- package/dist/VincentConnectionCard.d.ts.map +1 -0
- package/dist/VincentConnectionCard.js +121 -0
- package/dist/VincentConnectionCard.js.map +1 -0
- package/dist/VincentView.d.ts +13 -0
- package/dist/VincentView.d.ts.map +1 -0
- package/dist/VincentView.js +86 -0
- package/dist/VincentView.js.map +1 -0
- package/dist/WalletStatusCard.d.ts +12 -0
- package/dist/WalletStatusCard.d.ts.map +1 -0
- package/dist/WalletStatusCard.js +203 -0
- package/dist/WalletStatusCard.js.map +1 -0
- package/dist/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +31 -0
- package/dist/client.js.map +1 -0
- package/dist/components/VincentSpatialView.d.ts +32 -0
- package/dist/components/VincentSpatialView.d.ts.map +1 -0
- package/dist/components/VincentSpatialView.js +191 -0
- package/dist/components/VincentSpatialView.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +14 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +98 -0
- package/dist/plugin.js.map +1 -0
- package/dist/register-routes.d.ts +2 -0
- package/dist/register-routes.d.ts.map +1 -0
- package/dist/register-routes.js +6 -0
- package/dist/register-routes.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +28 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +6 -0
- package/dist/register.js.map +1 -0
- package/dist/routes.d.ts +24 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +429 -0
- package/dist/routes.js.map +1 -0
- package/dist/ui.d.ts +11 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +16 -0
- package/dist/ui.js.map +1 -0
- package/dist/useVincentDashboard.d.ts +21 -0
- package/dist/useVincentDashboard.d.ts.map +1 -0
- package/dist/useVincentDashboard.js +105 -0
- package/dist/useVincentDashboard.js.map +1 -0
- package/dist/useVincentState.d.ts +15 -0
- package/dist/useVincentState.d.ts.map +1 -0
- package/dist/useVincentState.js +116 -0
- package/dist/useVincentState.js.map +1 -0
- package/dist/views/bundle.js +500 -0
- package/dist/views/bundle.js.map +1 -0
- package/dist/vincent-app.d.ts +10 -0
- package/dist/vincent-app.d.ts.map +1 -0
- package/dist/vincent-app.js +16 -0
- package/dist/vincent-app.js.map +1 -0
- package/dist/vincent-contracts.d.ts +52 -0
- package/dist/vincent-contracts.d.ts.map +1 -0
- package/dist/vincent-contracts.js +8 -0
- package/dist/vincent-contracts.js.map +1 -0
- package/dist/vincent-view-bundle.d.ts +3 -0
- package/dist/vincent-view-bundle.d.ts.map +1 -0
- package/dist/vincent-view-bundle.js +7 -0
- package/dist/vincent-view-bundle.js.map +1 -0
- package/package.json +7 -7
package/dist/routes.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { saveElizaConfig } from "@elizaos/agent/config/config";
|
|
3
|
+
import { sendJson, sendJsonError } from "@elizaos/app-core/api/response";
|
|
4
|
+
import { logger } from "@elizaos/core";
|
|
5
|
+
import { VINCENT_TRADING_VENUES } from "./vincent-contracts.js";
|
|
6
|
+
const VINCENT_API_BASE = "https://heyvincent.ai";
|
|
7
|
+
const PENDING_LOGIN_TTL_MS = 10 * 60 * 1e3;
|
|
8
|
+
const pendingLogins = /* @__PURE__ */ new Map();
|
|
9
|
+
function sweepExpiredLogins() {
|
|
10
|
+
const cutoff = Date.now() - PENDING_LOGIN_TTL_MS;
|
|
11
|
+
for (const [state, entry] of pendingLogins) {
|
|
12
|
+
if (entry.createdAt < cutoff) pendingLogins.delete(state);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function base64UrlEncode(buf) {
|
|
16
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
17
|
+
}
|
|
18
|
+
function generateCodeVerifier() {
|
|
19
|
+
return base64UrlEncode(crypto.randomBytes(32));
|
|
20
|
+
}
|
|
21
|
+
function generateCodeChallenge(verifier) {
|
|
22
|
+
return base64UrlEncode(crypto.createHash("sha256").update(verifier).digest());
|
|
23
|
+
}
|
|
24
|
+
function resolveServerOrigin(req) {
|
|
25
|
+
const host = req.headers.host;
|
|
26
|
+
if (!host) return null;
|
|
27
|
+
return `http://${host}`;
|
|
28
|
+
}
|
|
29
|
+
function isVincentTradingConfig(value) {
|
|
30
|
+
return isPlainRecord(value) && (value.strategy === void 0 || readVincentStrategyName(value.strategy) !== null) && (value.params === void 0 || isPlainRecord(value.params)) && (value.intervalSeconds === void 0 || typeof value.intervalSeconds === "number") && (value.dryRun === void 0 || typeof value.dryRun === "boolean");
|
|
31
|
+
}
|
|
32
|
+
function isPlainRecord(value) {
|
|
33
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
function parseJsonRecord(body) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(body);
|
|
38
|
+
return isPlainRecord(parsed) ? parsed : null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function readStringField(value, key) {
|
|
44
|
+
const field = value?.[key];
|
|
45
|
+
return typeof field === "string" && field.trim() ? field.trim() : null;
|
|
46
|
+
}
|
|
47
|
+
function readVincentStrategyName(value) {
|
|
48
|
+
return value === "dca" || value === "rebalance" || value === "threshold" || value === "manual" ? value : null;
|
|
49
|
+
}
|
|
50
|
+
function getVincentTokens(config) {
|
|
51
|
+
const vincent = Reflect.get(config, "vincent");
|
|
52
|
+
return isPlainRecord(vincent) && typeof vincent.accessToken === "string" && typeof vincent.clientId === "string" && typeof vincent.connectedAt === "number" ? {
|
|
53
|
+
accessToken: vincent.accessToken,
|
|
54
|
+
refreshToken: typeof vincent.refreshToken === "string" ? vincent.refreshToken : null,
|
|
55
|
+
clientId: vincent.clientId,
|
|
56
|
+
connectedAt: vincent.connectedAt
|
|
57
|
+
} : void 0;
|
|
58
|
+
}
|
|
59
|
+
function setVincentTokens(config, tokens) {
|
|
60
|
+
Reflect.set(config, "vincent", tokens);
|
|
61
|
+
}
|
|
62
|
+
function getVincentTradingConfig(config) {
|
|
63
|
+
const trading = Reflect.get(config, "trading");
|
|
64
|
+
return isVincentTradingConfig(trading) ? trading : void 0;
|
|
65
|
+
}
|
|
66
|
+
function setVincentTradingConfig(config, trading) {
|
|
67
|
+
Reflect.set(config, "trading", trading);
|
|
68
|
+
}
|
|
69
|
+
async function handleVincentRoute(req, res, pathname, method, state) {
|
|
70
|
+
if (method === "POST" && pathname === "/api/vincent/start-login") {
|
|
71
|
+
try {
|
|
72
|
+
sweepExpiredLogins();
|
|
73
|
+
const origin = resolveServerOrigin(req);
|
|
74
|
+
if (!origin) {
|
|
75
|
+
sendJsonError(res, 400, "Missing Host header");
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const redirectUri = `${origin}/callback/vincent`;
|
|
79
|
+
const body = await readBody(req).catch(() => "");
|
|
80
|
+
const parsed = body ? parseJsonRecord(body) : null;
|
|
81
|
+
const appName = readStringField(parsed, "appName") ?? "Eliza";
|
|
82
|
+
const registerRes = await fetch(
|
|
83
|
+
`${VINCENT_API_BASE}/api/oauth/public/register`,
|
|
84
|
+
{
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
client_name: appName,
|
|
89
|
+
redirect_uris: [redirectUri]
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
if (!registerRes.ok) {
|
|
94
|
+
const text = await registerRes.text().catch(() => "");
|
|
95
|
+
sendJsonError(
|
|
96
|
+
res,
|
|
97
|
+
registerRes.status,
|
|
98
|
+
`Vincent register failed: ${text}`
|
|
99
|
+
);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
const registerJson = parseJsonRecord(
|
|
103
|
+
await registerRes.text().catch(() => "")
|
|
104
|
+
);
|
|
105
|
+
const clientId = readStringField(registerJson, "client_id");
|
|
106
|
+
if (!clientId) {
|
|
107
|
+
sendJsonError(res, 502, "Vincent register returned an invalid payload");
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const codeVerifier = generateCodeVerifier();
|
|
111
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
112
|
+
const stateParam = crypto.randomUUID();
|
|
113
|
+
pendingLogins.set(stateParam, {
|
|
114
|
+
clientId,
|
|
115
|
+
codeVerifier,
|
|
116
|
+
redirectUri,
|
|
117
|
+
createdAt: Date.now()
|
|
118
|
+
});
|
|
119
|
+
const params = new URLSearchParams({
|
|
120
|
+
client_id: clientId,
|
|
121
|
+
response_type: "code",
|
|
122
|
+
redirect_uri: redirectUri,
|
|
123
|
+
scope: "all",
|
|
124
|
+
resource: VINCENT_API_BASE,
|
|
125
|
+
code_challenge: codeChallenge,
|
|
126
|
+
code_challenge_method: "S256",
|
|
127
|
+
state: stateParam
|
|
128
|
+
});
|
|
129
|
+
const authUrl = `${VINCENT_API_BASE}/api/oauth/public/authorize?${params.toString()}`;
|
|
130
|
+
const payload = {
|
|
131
|
+
authUrl,
|
|
132
|
+
state: stateParam,
|
|
133
|
+
redirectUri
|
|
134
|
+
};
|
|
135
|
+
sendJson(res, 200, payload);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
logger.error(
|
|
138
|
+
`[vincent/start-login] ${err instanceof Error ? err.message : String(err)}`
|
|
139
|
+
);
|
|
140
|
+
sendJsonError(res, 500, "Vincent start-login failed");
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (method === "GET" && pathname === "/callback/vincent") {
|
|
145
|
+
try {
|
|
146
|
+
sweepExpiredLogins();
|
|
147
|
+
const url = new URL(req.url ?? "/callback/vincent", "http://localhost");
|
|
148
|
+
const code = url.searchParams.get("code");
|
|
149
|
+
const stateParam = url.searchParams.get("state");
|
|
150
|
+
const oauthError = url.searchParams.get("error");
|
|
151
|
+
if (oauthError) {
|
|
152
|
+
sendCallbackHtml(
|
|
153
|
+
res,
|
|
154
|
+
400,
|
|
155
|
+
"Vincent login failed",
|
|
156
|
+
`Vincent returned an error: ${oauthError}. You may close this window.`
|
|
157
|
+
);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
if (!code) {
|
|
161
|
+
sendCallbackHtml(
|
|
162
|
+
res,
|
|
163
|
+
400,
|
|
164
|
+
"Vincent login failed",
|
|
165
|
+
"The Vincent redirect did not include an authorization code. You may close this window."
|
|
166
|
+
);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (!stateParam) {
|
|
170
|
+
sendCallbackHtml(
|
|
171
|
+
res,
|
|
172
|
+
400,
|
|
173
|
+
"Vincent login failed",
|
|
174
|
+
"The Vincent redirect did not include a state parameter. Please return to the app and try again."
|
|
175
|
+
);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
const pending = pendingLogins.get(stateParam);
|
|
179
|
+
if (!pending) {
|
|
180
|
+
sendCallbackHtml(
|
|
181
|
+
res,
|
|
182
|
+
400,
|
|
183
|
+
"Vincent login expired",
|
|
184
|
+
"No pending login was found for this callback. Please return to the app and try again."
|
|
185
|
+
);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
pendingLogins.delete(stateParam);
|
|
189
|
+
const tokenRes = await fetch(
|
|
190
|
+
`${VINCENT_API_BASE}/api/oauth/public/token`,
|
|
191
|
+
{
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: { "Content-Type": "application/json" },
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
grant_type: "authorization_code",
|
|
196
|
+
code,
|
|
197
|
+
client_id: pending.clientId,
|
|
198
|
+
code_verifier: pending.codeVerifier,
|
|
199
|
+
// RFC 6749 §4.1.3: redirect_uri MUST match the one used in the
|
|
200
|
+
// authorize request. Vincent rejects the exchange with
|
|
201
|
+
// "invalid_request: expected string, received undefined" without
|
|
202
|
+
// this field.
|
|
203
|
+
redirect_uri: pending.redirectUri
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
if (!tokenRes.ok) {
|
|
208
|
+
const text = await tokenRes.text().catch(() => "");
|
|
209
|
+
logger.error(
|
|
210
|
+
`[vincent/callback] token exchange failed: ${tokenRes.status} ${text}`
|
|
211
|
+
);
|
|
212
|
+
sendCallbackHtml(
|
|
213
|
+
res,
|
|
214
|
+
502,
|
|
215
|
+
"Vincent login failed",
|
|
216
|
+
"Token exchange with Vincent failed. Please return to the app and try again."
|
|
217
|
+
);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
const tokenJson = parseJsonRecord(await tokenRes.text().catch(() => ""));
|
|
221
|
+
const accessToken = readStringField(tokenJson, "access_token");
|
|
222
|
+
if (!accessToken) {
|
|
223
|
+
logger.error(
|
|
224
|
+
"[vincent/callback] token response was missing access_token"
|
|
225
|
+
);
|
|
226
|
+
sendCallbackHtml(
|
|
227
|
+
res,
|
|
228
|
+
502,
|
|
229
|
+
"Vincent login failed",
|
|
230
|
+
"Token exchange with Vincent returned an invalid payload. Please return to the app and try again."
|
|
231
|
+
);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
const refreshToken = readStringField(tokenJson, "refresh_token");
|
|
235
|
+
const config = state.config;
|
|
236
|
+
setVincentTokens(config, {
|
|
237
|
+
accessToken,
|
|
238
|
+
refreshToken,
|
|
239
|
+
clientId: pending.clientId,
|
|
240
|
+
connectedAt: Math.floor(Date.now() / 1e3)
|
|
241
|
+
});
|
|
242
|
+
await saveElizaConfig(config);
|
|
243
|
+
logger.info("[vincent/callback] Vincent connected successfully");
|
|
244
|
+
sendCallbackHtml(
|
|
245
|
+
res,
|
|
246
|
+
200,
|
|
247
|
+
"Vincent connected",
|
|
248
|
+
"You're signed in. You can close this window and return to the app."
|
|
249
|
+
);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
logger.error(
|
|
252
|
+
`[vincent/callback] ${err instanceof Error ? err.message : String(err)}`
|
|
253
|
+
);
|
|
254
|
+
sendCallbackHtml(
|
|
255
|
+
res,
|
|
256
|
+
500,
|
|
257
|
+
"Vincent login failed",
|
|
258
|
+
"An unexpected error occurred completing the Vincent login. You may close this window and try again."
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
if (method === "GET" && pathname === "/api/vincent/status") {
|
|
264
|
+
const vincent = getVincentTokens(state.config);
|
|
265
|
+
const payload = {
|
|
266
|
+
connected: Boolean(vincent?.accessToken),
|
|
267
|
+
connectedAt: vincent?.connectedAt ?? null,
|
|
268
|
+
tradingVenues: VINCENT_TRADING_VENUES
|
|
269
|
+
};
|
|
270
|
+
sendJson(res, 200, payload);
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
if (method === "POST" && pathname === "/api/vincent/disconnect") {
|
|
274
|
+
try {
|
|
275
|
+
const config = state.config;
|
|
276
|
+
setVincentTokens(config, void 0);
|
|
277
|
+
await saveElizaConfig(config);
|
|
278
|
+
logger.info("[vincent/disconnect] Vincent disconnected");
|
|
279
|
+
sendJson(res, 200, { ok: true });
|
|
280
|
+
} catch (err) {
|
|
281
|
+
logger.error(
|
|
282
|
+
`[vincent/disconnect] ${err instanceof Error ? err.message : String(err)}`
|
|
283
|
+
);
|
|
284
|
+
sendJsonError(res, 500, "Vincent disconnect failed");
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (method === "GET" && pathname === "/api/vincent/trading-profile") {
|
|
289
|
+
const vincent = getVincentTokens(state.config);
|
|
290
|
+
if (!vincent?.accessToken) {
|
|
291
|
+
sendJson(res, 200, { connected: false, profile: null });
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
const payload = {
|
|
295
|
+
connected: true,
|
|
296
|
+
profile: null
|
|
297
|
+
};
|
|
298
|
+
sendJson(res, 200, payload);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
if (method === "GET" && pathname === "/api/vincent/strategy") {
|
|
302
|
+
const vincent = getVincentTokens(state.config);
|
|
303
|
+
if (!vincent?.accessToken) {
|
|
304
|
+
sendJson(res, 200, { connected: false, strategy: null });
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
const tradingConfig = getVincentTradingConfig(state.config);
|
|
308
|
+
if (!tradingConfig) {
|
|
309
|
+
sendJson(res, 200, { connected: true, strategy: null });
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
const payload = {
|
|
313
|
+
connected: true,
|
|
314
|
+
strategy: {
|
|
315
|
+
name: tradingConfig?.strategy ?? "manual",
|
|
316
|
+
venues: VINCENT_TRADING_VENUES,
|
|
317
|
+
params: tradingConfig?.params ?? {},
|
|
318
|
+
intervalSeconds: tradingConfig?.intervalSeconds ?? 60,
|
|
319
|
+
dryRun: tradingConfig?.dryRun ?? false,
|
|
320
|
+
running: false
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
sendJson(res, 200, payload);
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
if (method === "POST" && pathname === "/api/vincent/strategy") {
|
|
327
|
+
try {
|
|
328
|
+
const vincent = getVincentTokens(state.config);
|
|
329
|
+
if (!vincent?.accessToken) {
|
|
330
|
+
sendJsonError(res, 401, "Vincent not connected");
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
const body = await readBody(req);
|
|
334
|
+
const parsed = parseJsonRecord(body);
|
|
335
|
+
const updates = {
|
|
336
|
+
strategy: readVincentStrategyName(parsed?.strategy) ?? void 0,
|
|
337
|
+
params: isPlainRecord(parsed?.params) ? parsed.params : void 0,
|
|
338
|
+
intervalSeconds: typeof parsed?.intervalSeconds === "number" ? parsed.intervalSeconds : void 0,
|
|
339
|
+
dryRun: typeof parsed?.dryRun === "boolean" ? parsed.dryRun : void 0
|
|
340
|
+
};
|
|
341
|
+
const config = getVincentTradingConfig(state.config);
|
|
342
|
+
const nextTrading = {
|
|
343
|
+
...config ?? {},
|
|
344
|
+
...updates.strategy !== void 0 && { strategy: updates.strategy },
|
|
345
|
+
...updates.params !== void 0 && { params: updates.params },
|
|
346
|
+
...updates.intervalSeconds !== void 0 && {
|
|
347
|
+
intervalSeconds: updates.intervalSeconds
|
|
348
|
+
},
|
|
349
|
+
...updates.dryRun !== void 0 && { dryRun: updates.dryRun }
|
|
350
|
+
};
|
|
351
|
+
setVincentTradingConfig(state.config, nextTrading);
|
|
352
|
+
await saveElizaConfig(state.config);
|
|
353
|
+
const payload = {
|
|
354
|
+
ok: true,
|
|
355
|
+
strategy: {
|
|
356
|
+
name: nextTrading.strategy ?? "manual",
|
|
357
|
+
venues: VINCENT_TRADING_VENUES,
|
|
358
|
+
params: nextTrading.params ?? {},
|
|
359
|
+
intervalSeconds: nextTrading.intervalSeconds ?? 60,
|
|
360
|
+
dryRun: nextTrading.dryRun ?? false,
|
|
361
|
+
running: false
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
sendJson(res, 200, payload);
|
|
365
|
+
} catch (err) {
|
|
366
|
+
logger.error(
|
|
367
|
+
`[vincent/strategy] ${err instanceof Error ? err.message : String(err)}`
|
|
368
|
+
);
|
|
369
|
+
sendJsonError(res, 500, "Strategy update failed");
|
|
370
|
+
}
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
function readBody(req) {
|
|
376
|
+
return new Promise((resolve, reject) => {
|
|
377
|
+
const chunks = [];
|
|
378
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
379
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
380
|
+
req.on("error", reject);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function escapeHtml(input) {
|
|
384
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
385
|
+
}
|
|
386
|
+
function sendCallbackHtml(res, status, title, message) {
|
|
387
|
+
if (res.headersSent) return;
|
|
388
|
+
const safeTitle = escapeHtml(title);
|
|
389
|
+
const safeMessage = escapeHtml(message);
|
|
390
|
+
const html = `<!doctype html>
|
|
391
|
+
<html lang="en">
|
|
392
|
+
<head>
|
|
393
|
+
<meta charset="utf-8" />
|
|
394
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
395
|
+
<title>${safeTitle} \xB7 Eliza</title>
|
|
396
|
+
<style>
|
|
397
|
+
:root { color-scheme: light dark; }
|
|
398
|
+
html, body { height: 100%; margin: 0; }
|
|
399
|
+
body {
|
|
400
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
401
|
+
display: flex; align-items: center; justify-content: center;
|
|
402
|
+
background: #0b0b0f; color: #f4f4f5;
|
|
403
|
+
}
|
|
404
|
+
.card {
|
|
405
|
+
max-width: 420px; padding: 32px 36px; border-radius: 16px;
|
|
406
|
+
background: #17171c; border: 1px solid #2a2a33;
|
|
407
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
|
|
408
|
+
text-align: center;
|
|
409
|
+
}
|
|
410
|
+
h1 { margin: 0 0 12px; font-size: 20px; font-weight: 600; }
|
|
411
|
+
p { margin: 0; font-size: 14px; line-height: 1.5; color: #b4b4bd; }
|
|
412
|
+
</style>
|
|
413
|
+
</head>
|
|
414
|
+
<body>
|
|
415
|
+
<main class="card">
|
|
416
|
+
<h1>${safeTitle}</h1>
|
|
417
|
+
<p>${safeMessage}</p>
|
|
418
|
+
</main>
|
|
419
|
+
<script>setTimeout(() => { try { window.close(); } catch (_) {} }, 1500);</script>
|
|
420
|
+
</body>
|
|
421
|
+
</html>`;
|
|
422
|
+
res.statusCode = status;
|
|
423
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
424
|
+
res.end(html);
|
|
425
|
+
}
|
|
426
|
+
export {
|
|
427
|
+
handleVincentRoute
|
|
428
|
+
};
|
|
429
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routes.ts"],"sourcesContent":["/**\n * Vincent OAuth backend routes for Hyperliquid and Polymarket access.\n *\n * POST /api/vincent/start-login — Begin OAuth: register app, generate PKCE, return authUrl\n * GET /callback/vincent — OAuth redirect target: exchange code, persist tokens\n * GET /api/vincent/status — Check if Vincent is connected\n * POST /api/vincent/disconnect — Clear stored Vincent tokens\n *\n * The start-login + /callback/vincent pair keeps the PKCE code_verifier on the\n * server so the OAuth redirect can land in the user's external system browser\n * (where sessionStorage is not shared with the desktop webview) and still\n * complete the token exchange.\n */\n\nimport crypto from \"node:crypto\";\nimport type http from \"node:http\";\nimport { saveElizaConfig } from \"@elizaos/agent/config/config\";\nimport { sendJson, sendJsonError } from \"@elizaos/app-core/api/response\";\nimport { logger } from \"@elizaos/core\";\nimport type { ElizaConfig } from \"@elizaos/shared\";\nimport type {\n VincentStartLoginResponse,\n VincentStatusResponse,\n VincentStrategyName,\n VincentStrategyResponse,\n VincentStrategyUpdateRequest,\n VincentStrategyUpdateResponse,\n VincentTradingProfileResponse,\n} from \"./vincent-contracts.js\";\nimport { VINCENT_TRADING_VENUES } from \"./vincent-contracts.js\";\n\nconst VINCENT_API_BASE = \"https://heyvincent.ai\";\n\n/** Maximum time a pending PKCE login can sit before it is evicted. */\nconst PENDING_LOGIN_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\ninterface PendingLogin {\n clientId: string;\n codeVerifier: string;\n /**\n * Must be echoed back to the Vincent token endpoint verbatim. RFC 6749\n * §4.1.3 requires redirect_uri in the token exchange to exactly match the\n * one in the authorize request when it was present there — Vincent enforces\n * this and rejects with \"invalid_request: expected string, received\n * undefined\" if the field is missing.\n */\n redirectUri: string;\n createdAt: number;\n}\n\n/**\n * In-memory store for in-flight OAuth logins, keyed by OAuth `state` param.\n * Process-local on purpose — Vincent tokens only matter for this runtime.\n */\nconst pendingLogins = new Map<string, PendingLogin>();\n\nfunction sweepExpiredLogins(): void {\n const cutoff = Date.now() - PENDING_LOGIN_TTL_MS;\n for (const [state, entry] of pendingLogins) {\n if (entry.createdAt < cutoff) pendingLogins.delete(state);\n }\n}\n\nfunction base64UrlEncode(buf: Buffer): string {\n return buf\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction generateCodeVerifier(): string {\n return base64UrlEncode(crypto.randomBytes(32));\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return base64UrlEncode(crypto.createHash(\"sha256\").update(verifier).digest());\n}\n\nfunction resolveServerOrigin(req: http.IncomingMessage): string | null {\n const host = req.headers.host;\n if (!host) return null;\n // All Vincent login traffic is loopback — http is fine and matches the\n // redirect_uri the external browser will actually hit.\n return `http://${host}`;\n}\n\ninterface VincentTokens {\n accessToken: string;\n refreshToken: string | null;\n clientId: string;\n connectedAt: number;\n}\n\ninterface VincentConfigState {\n vincent?: VincentTokens;\n trading?: {\n strategy?: VincentStrategyName;\n params?: Record<string, unknown>;\n intervalSeconds?: number;\n dryRun?: boolean;\n };\n}\n\nfunction isVincentTradingConfig(\n value: unknown,\n): value is NonNullable<VincentConfigState[\"trading\"]> {\n return (\n isPlainRecord(value) &&\n (value.strategy === undefined ||\n readVincentStrategyName(value.strategy) !== null) &&\n (value.params === undefined || isPlainRecord(value.params)) &&\n (value.intervalSeconds === undefined ||\n typeof value.intervalSeconds === \"number\") &&\n (value.dryRun === undefined || typeof value.dryRun === \"boolean\")\n );\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction parseJsonRecord(body: string): Record<string, unknown> | null {\n try {\n const parsed: unknown = JSON.parse(body);\n return isPlainRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction readStringField(\n value: Record<string, unknown> | null,\n key: string,\n): string | null {\n const field = value?.[key];\n return typeof field === \"string\" && field.trim() ? field.trim() : null;\n}\n\nfunction readVincentStrategyName(value: unknown): VincentStrategyName | null {\n return value === \"dca\" ||\n value === \"rebalance\" ||\n value === \"threshold\" ||\n value === \"manual\"\n ? value\n : null;\n}\n\nfunction getVincentTokens(config: ElizaConfig): VincentTokens | undefined {\n const vincent: unknown = Reflect.get(config, \"vincent\");\n return isPlainRecord(vincent) &&\n typeof vincent.accessToken === \"string\" &&\n typeof vincent.clientId === \"string\" &&\n typeof vincent.connectedAt === \"number\"\n ? {\n accessToken: vincent.accessToken,\n refreshToken:\n typeof vincent.refreshToken === \"string\"\n ? vincent.refreshToken\n : null,\n clientId: vincent.clientId,\n connectedAt: vincent.connectedAt,\n }\n : undefined;\n}\n\nfunction setVincentTokens(\n config: ElizaConfig,\n tokens: VincentTokens | undefined,\n): void {\n Reflect.set(config, \"vincent\", tokens);\n}\n\nfunction getVincentTradingConfig(\n config: ElizaConfig,\n): VincentConfigState[\"trading\"] | undefined {\n const trading: unknown = Reflect.get(config, \"trading\");\n return isVincentTradingConfig(trading) ? trading : undefined;\n}\n\nfunction setVincentTradingConfig(\n config: ElizaConfig,\n trading: VincentConfigState[\"trading\"],\n): void {\n Reflect.set(config, \"trading\", trading);\n}\n\nexport interface VincentRouteState {\n config: ElizaConfig;\n}\n\n/**\n * Handle all /api/vincent/* routes.\n * Returns true if the route was handled, false otherwise.\n */\nexport async function handleVincentRoute(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n method: string,\n state: VincentRouteState,\n): Promise<boolean> {\n // ── POST /api/vincent/start-login ───────────────────────────────\n // Server-side PKCE: register with Vincent, generate verifier/challenge,\n // store the verifier keyed by a fresh `state` param, and return the\n // authorization URL. The browser visits this URL, authenticates, and is\n // redirected back to GET /callback/vincent on this same origin.\n if (method === \"POST\" && pathname === \"/api/vincent/start-login\") {\n try {\n sweepExpiredLogins();\n\n const origin = resolveServerOrigin(req);\n if (!origin) {\n sendJsonError(res, 400, \"Missing Host header\");\n return true;\n }\n const redirectUri = `${origin}/callback/vincent`;\n\n const body = await readBody(req).catch(() => \"\");\n const parsed = body ? parseJsonRecord(body) : null;\n const appName = readStringField(parsed, \"appName\") ?? \"Eliza\";\n\n const registerRes = await fetch(\n `${VINCENT_API_BASE}/api/oauth/public/register`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_name: appName,\n redirect_uris: [redirectUri],\n }),\n },\n );\n if (!registerRes.ok) {\n const text = await registerRes.text().catch(() => \"\");\n sendJsonError(\n res,\n registerRes.status,\n `Vincent register failed: ${text}`,\n );\n return true;\n }\n const registerJson = parseJsonRecord(\n await registerRes.text().catch(() => \"\"),\n );\n const clientId = readStringField(registerJson, \"client_id\");\n if (!clientId) {\n sendJsonError(res, 502, \"Vincent register returned an invalid payload\");\n return true;\n }\n\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n const stateParam = crypto.randomUUID();\n\n pendingLogins.set(stateParam, {\n clientId,\n codeVerifier,\n redirectUri,\n createdAt: Date.now(),\n });\n\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: \"code\",\n redirect_uri: redirectUri,\n scope: \"all\",\n resource: VINCENT_API_BASE,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state: stateParam,\n });\n const authUrl = `${VINCENT_API_BASE}/api/oauth/public/authorize?${params.toString()}`;\n const payload: VincentStartLoginResponse = {\n authUrl,\n state: stateParam,\n redirectUri,\n };\n\n sendJson(res, 200, payload);\n } catch (err) {\n logger.error(\n `[vincent/start-login] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, \"Vincent start-login failed\");\n }\n return true;\n }\n\n // ── GET /callback/vincent ───────────────────────────────────────\n // OAuth redirect target for the Vincent authorization flow. Loads in the\n // user's external browser (not the desktop webview), so it returns a small\n // HTML page that tells the user to close the tab — the desktop app is\n // polling /api/vincent/status and will flip to connected on its own.\n if (method === \"GET\" && pathname === \"/callback/vincent\") {\n try {\n sweepExpiredLogins();\n\n const url = new URL(req.url ?? \"/callback/vincent\", \"http://localhost\");\n const code = url.searchParams.get(\"code\");\n const stateParam = url.searchParams.get(\"state\");\n const oauthError = url.searchParams.get(\"error\");\n\n if (oauthError) {\n // `sendCallbackHtml` escapes `message` wholesale — don't escape the\n // `error` param here or it ends up double-encoded (&lt; instead\n // of <).\n sendCallbackHtml(\n res,\n 400,\n \"Vincent login failed\",\n `Vincent returned an error: ${oauthError}. You may close this window.`,\n );\n return true;\n }\n if (!code) {\n sendCallbackHtml(\n res,\n 400,\n \"Vincent login failed\",\n \"The Vincent redirect did not include an authorization code. You may close this window.\",\n );\n return true;\n }\n\n // Require a state param that matches a pending login exactly. This\n // prevents any local process that can reach loopback from completing a\n // login flow it did not initiate — only start-login can seed an entry\n // in pendingLogins, and only the state value we returned from it can\n // unlock the associated code_verifier. (PKCE already prevents cross-\n // session token theft via Vincent's challenge/verifier check, but\n // rejecting unknown state is the cheaper, clearer gate.)\n if (!stateParam) {\n sendCallbackHtml(\n res,\n 400,\n \"Vincent login failed\",\n \"The Vincent redirect did not include a state parameter. Please return to the app and try again.\",\n );\n return true;\n }\n const pending = pendingLogins.get(stateParam);\n if (!pending) {\n sendCallbackHtml(\n res,\n 400,\n \"Vincent login expired\",\n \"No pending login was found for this callback. Please return to the app and try again.\",\n );\n return true;\n }\n pendingLogins.delete(stateParam);\n\n const tokenRes = await fetch(\n `${VINCENT_API_BASE}/api/oauth/public/token`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code,\n client_id: pending.clientId,\n code_verifier: pending.codeVerifier,\n // RFC 6749 §4.1.3: redirect_uri MUST match the one used in the\n // authorize request. Vincent rejects the exchange with\n // \"invalid_request: expected string, received undefined\" without\n // this field.\n redirect_uri: pending.redirectUri,\n }),\n },\n );\n if (!tokenRes.ok) {\n const text = await tokenRes.text().catch(() => \"\");\n logger.error(\n `[vincent/callback] token exchange failed: ${tokenRes.status} ${text}`,\n );\n sendCallbackHtml(\n res,\n 502,\n \"Vincent login failed\",\n \"Token exchange with Vincent failed. Please return to the app and try again.\",\n );\n return true;\n }\n\n const tokenJson = parseJsonRecord(await tokenRes.text().catch(() => \"\"));\n const accessToken = readStringField(tokenJson, \"access_token\");\n if (!accessToken) {\n logger.error(\n \"[vincent/callback] token response was missing access_token\",\n );\n sendCallbackHtml(\n res,\n 502,\n \"Vincent login failed\",\n \"Token exchange with Vincent returned an invalid payload. Please return to the app and try again.\",\n );\n return true;\n }\n const refreshToken = readStringField(tokenJson, \"refresh_token\");\n\n const config = state.config;\n setVincentTokens(config, {\n accessToken,\n refreshToken,\n clientId: pending.clientId,\n connectedAt: Math.floor(Date.now() / 1000),\n });\n await saveElizaConfig(config);\n\n logger.info(\"[vincent/callback] Vincent connected successfully\");\n sendCallbackHtml(\n res,\n 200,\n \"Vincent connected\",\n \"You're signed in. You can close this window and return to the app.\",\n );\n } catch (err) {\n logger.error(\n `[vincent/callback] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendCallbackHtml(\n res,\n 500,\n \"Vincent login failed\",\n \"An unexpected error occurred completing the Vincent login. You may close this window and try again.\",\n );\n }\n return true;\n }\n\n // ── GET /api/vincent/status ─────────────────────────────────────\n if (method === \"GET\" && pathname === \"/api/vincent/status\") {\n const vincent = getVincentTokens(state.config);\n const payload: VincentStatusResponse = {\n connected: Boolean(vincent?.accessToken),\n connectedAt: vincent?.connectedAt ?? null,\n tradingVenues: VINCENT_TRADING_VENUES,\n };\n sendJson(res, 200, payload);\n return true;\n }\n\n // ── POST /api/vincent/disconnect ────────────────────────────────\n if (method === \"POST\" && pathname === \"/api/vincent/disconnect\") {\n try {\n const config = state.config;\n setVincentTokens(config, undefined);\n await saveElizaConfig(config);\n logger.info(\"[vincent/disconnect] Vincent disconnected\");\n sendJson(res, 200, { ok: true });\n } catch (err) {\n logger.error(\n `[vincent/disconnect] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, \"Vincent disconnect failed\");\n }\n return true;\n }\n\n // ── GET /api/vincent/trading-profile ───────────────────────────\n if (method === \"GET\" && pathname === \"/api/vincent/trading-profile\") {\n const vincent = getVincentTokens(state.config);\n if (!vincent?.accessToken) {\n sendJson(res, 200, { connected: false, profile: null });\n return true;\n }\n const payload: VincentTradingProfileResponse = {\n connected: true,\n profile: null,\n };\n sendJson(res, 200, payload);\n return true;\n }\n\n // ── GET /api/vincent/strategy ──────────────────────────────────\n // Current trading strategy configuration.\n if (method === \"GET\" && pathname === \"/api/vincent/strategy\") {\n const vincent = getVincentTokens(state.config);\n if (!vincent?.accessToken) {\n sendJson(res, 200, { connected: false, strategy: null });\n return true;\n }\n const tradingConfig = getVincentTradingConfig(state.config);\n if (!tradingConfig) {\n sendJson(res, 200, { connected: true, strategy: null });\n return true;\n }\n const payload: VincentStrategyResponse = {\n connected: true,\n strategy: {\n name: tradingConfig?.strategy ?? \"manual\",\n venues: VINCENT_TRADING_VENUES,\n params: tradingConfig?.params ?? {},\n intervalSeconds: tradingConfig?.intervalSeconds ?? 60,\n dryRun: tradingConfig?.dryRun ?? false,\n running: false,\n },\n };\n sendJson(res, 200, payload);\n return true;\n }\n\n // ── POST /api/vincent/strategy ─────────────────────────────────\n // Update trading strategy configuration.\n if (method === \"POST\" && pathname === \"/api/vincent/strategy\") {\n try {\n const vincent = getVincentTokens(state.config);\n if (!vincent?.accessToken) {\n sendJsonError(res, 401, \"Vincent not connected\");\n return true;\n }\n const body = await readBody(req);\n const parsed = parseJsonRecord(body);\n const updates: VincentStrategyUpdateRequest = {\n strategy: readVincentStrategyName(parsed?.strategy) ?? undefined,\n params: isPlainRecord(parsed?.params) ? parsed.params : undefined,\n intervalSeconds:\n typeof parsed?.intervalSeconds === \"number\"\n ? parsed.intervalSeconds\n : undefined,\n dryRun: typeof parsed?.dryRun === \"boolean\" ? parsed.dryRun : undefined,\n };\n const config = getVincentTradingConfig(state.config);\n const nextTrading = {\n ...(config ?? {}),\n ...(updates.strategy !== undefined && { strategy: updates.strategy }),\n ...(updates.params !== undefined && { params: updates.params }),\n ...(updates.intervalSeconds !== undefined && {\n intervalSeconds: updates.intervalSeconds,\n }),\n ...(updates.dryRun !== undefined && { dryRun: updates.dryRun }),\n };\n setVincentTradingConfig(state.config, nextTrading);\n await saveElizaConfig(state.config);\n const payload: VincentStrategyUpdateResponse = {\n ok: true,\n strategy: {\n name: nextTrading.strategy ?? \"manual\",\n venues: VINCENT_TRADING_VENUES,\n params: nextTrading.params ?? {},\n intervalSeconds: nextTrading.intervalSeconds ?? 60,\n dryRun: nextTrading.dryRun ?? false,\n running: false,\n },\n };\n sendJson(res, 200, payload);\n } catch (err) {\n logger.error(\n `[vincent/strategy] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, \"Strategy update failed\");\n }\n return true;\n }\n\n return false;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────\n\nfunction readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString()));\n req.on(\"error\", reject);\n });\n}\n\nfunction escapeHtml(input: string): string {\n return input\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction sendCallbackHtml(\n res: http.ServerResponse,\n status: number,\n title: string,\n message: string,\n): void {\n if (res.headersSent) return;\n const safeTitle = escapeHtml(title);\n const safeMessage = escapeHtml(message);\n const html = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<title>${safeTitle} · Eliza</title>\n<style>\n :root { color-scheme: light dark; }\n html, body { height: 100%; margin: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n display: flex; align-items: center; justify-content: center;\n background: #0b0b0f; color: #f4f4f5;\n }\n .card {\n max-width: 420px; padding: 32px 36px; border-radius: 16px;\n background: #17171c; border: 1px solid #2a2a33;\n box-shadow: 0 20px 60px rgba(0,0,0,0.4);\n text-align: center;\n }\n h1 { margin: 0 0 12px; font-size: 20px; font-weight: 600; }\n p { margin: 0; font-size: 14px; line-height: 1.5; color: #b4b4bd; }\n</style>\n</head>\n<body>\n <main class=\"card\">\n <h1>${safeTitle}</h1>\n <p>${safeMessage}</p>\n </main>\n <script>setTimeout(() => { try { window.close(); } catch (_) {} }, 1500);</script>\n</body>\n</html>`;\n res.statusCode = status;\n res.setHeader(\"content-type\", \"text/html; charset=utf-8\");\n res.end(html);\n}\n"],"mappings":"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","names":[]}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./TradingProfileCard.tsx";
|
|
2
|
+
export * from "./TradingStrategyPanel.tsx";
|
|
3
|
+
export * from "./useVincentDashboard.ts";
|
|
4
|
+
export * from "./useVincentState.ts";
|
|
5
|
+
export * from "./VincentAppView.helpers.ts";
|
|
6
|
+
export * from "./VincentAppView.tsx";
|
|
7
|
+
export * from "./VincentConnectionCard.tsx";
|
|
8
|
+
export { VincentView } from "./VincentView.tsx";
|
|
9
|
+
export { VINCENT_APP_NAME, vincentApp } from "./vincent-app.ts";
|
|
10
|
+
export * from "./WalletStatusCard.tsx";
|
|
11
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChE,cAAc,wBAAwB,CAAC"}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./TradingProfileCard.js";
|
|
2
|
+
export * from "./TradingStrategyPanel.js";
|
|
3
|
+
export * from "./useVincentDashboard.js";
|
|
4
|
+
export * from "./useVincentState.js";
|
|
5
|
+
export * from "./VincentAppView.helpers.js";
|
|
6
|
+
export * from "./VincentAppView.js";
|
|
7
|
+
export * from "./VincentConnectionCard.js";
|
|
8
|
+
import { VincentView } from "./VincentView.js";
|
|
9
|
+
import { VINCENT_APP_NAME, vincentApp } from "./vincent-app.js";
|
|
10
|
+
export * from "./WalletStatusCard.js";
|
|
11
|
+
export {
|
|
12
|
+
VINCENT_APP_NAME,
|
|
13
|
+
VincentView,
|
|
14
|
+
vincentApp
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=ui.js.map
|
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui.ts"],"sourcesContent":["export * from \"./TradingProfileCard.js\";\nexport * from \"./TradingStrategyPanel.js\";\nexport * from \"./useVincentDashboard.js\";\nexport * from \"./useVincentState.js\";\nexport * from \"./VincentAppView.helpers.js\";\nexport * from \"./VincentAppView.js\";\nexport * from \"./VincentConnectionCard.js\";\nexport { VincentView } from \"./VincentView.js\";\nexport { VINCENT_APP_NAME, vincentApp } from \"./vincent-app.js\";\nexport * from \"./WalletStatusCard.js\";\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB,kBAAkB;AAC7C,cAAc;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useVincentDashboard — aggregated data hook for the Vincent overlay app.
|
|
3
|
+
*
|
|
4
|
+
* Polls Vincent-specific endpoints every 15 s when connected, and fetches
|
|
5
|
+
* the agent wallet addresses and balances for dashboard context.
|
|
6
|
+
*/
|
|
7
|
+
import type { WalletAddresses, WalletBalancesResponse } from "@elizaos/shared";
|
|
8
|
+
import type { VincentStrategy, VincentTradingProfile } from "./vincent-contracts";
|
|
9
|
+
export interface VincentDashboardState {
|
|
10
|
+
vincentConnected: boolean;
|
|
11
|
+
vincentConnectedAt: number | null;
|
|
12
|
+
walletAddresses: WalletAddresses | null;
|
|
13
|
+
walletBalances: WalletBalancesResponse | null;
|
|
14
|
+
strategy: VincentStrategy | null;
|
|
15
|
+
tradingProfile: VincentTradingProfile | null;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
error: string | null;
|
|
18
|
+
refresh: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function useVincentDashboard(): VincentDashboardState;
|
|
21
|
+
//# sourceMappingURL=useVincentDashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVincentDashboard.d.ts","sourceRoot":"","sources":["../src/useVincentDashboard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAI/E,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,cAAc,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAE9C,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAEjC,cAAc,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAE7C,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAErB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,wBAAgB,mBAAmB,IAAI,qBAAqB,CAqH3D"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ApiError } from "@elizaos/ui";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { vincentClient } from "./client.js";
|
|
4
|
+
const POLL_INTERVAL_MS = 15e3;
|
|
5
|
+
function useVincentDashboard() {
|
|
6
|
+
const [vincentConnected, setVincentConnected] = useState(false);
|
|
7
|
+
const [vincentConnectedAt, setVincentConnectedAt] = useState(
|
|
8
|
+
null
|
|
9
|
+
);
|
|
10
|
+
const [walletAddresses, setWalletAddresses] = useState(null);
|
|
11
|
+
const [walletBalances, setWalletBalances] = useState(null);
|
|
12
|
+
const [strategy, setStrategy] = useState(null);
|
|
13
|
+
const [tradingProfile, setTradingProfile] = useState(null);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [error, setError] = useState(null);
|
|
16
|
+
const intervalRef = useRef(null);
|
|
17
|
+
const mountedRef = useRef(true);
|
|
18
|
+
const fetchAll = useCallback(async () => {
|
|
19
|
+
try {
|
|
20
|
+
const vincentStatusResult = await vincentClient.vincentStatus();
|
|
21
|
+
if (!mountedRef.current) return;
|
|
22
|
+
setVincentConnected(vincentStatusResult.connected);
|
|
23
|
+
setVincentConnectedAt(vincentStatusResult.connectedAt);
|
|
24
|
+
const [
|
|
25
|
+
addressResult,
|
|
26
|
+
balanceResult,
|
|
27
|
+
strategyResult,
|
|
28
|
+
tradingProfileResult
|
|
29
|
+
] = await Promise.allSettled([
|
|
30
|
+
vincentClient.getWalletAddresses(),
|
|
31
|
+
vincentClient.getWalletBalances(),
|
|
32
|
+
vincentClient.vincentStrategy(),
|
|
33
|
+
vincentClient.vincentTradingProfile()
|
|
34
|
+
]);
|
|
35
|
+
if (!mountedRef.current) return;
|
|
36
|
+
if (addressResult.status === "fulfilled") {
|
|
37
|
+
setWalletAddresses(addressResult.value);
|
|
38
|
+
}
|
|
39
|
+
if (balanceResult.status === "fulfilled") {
|
|
40
|
+
setWalletBalances(balanceResult.value);
|
|
41
|
+
}
|
|
42
|
+
if (strategyResult.status === "fulfilled") {
|
|
43
|
+
setStrategy(strategyResult.value.strategy);
|
|
44
|
+
}
|
|
45
|
+
if (tradingProfileResult.status === "fulfilled") {
|
|
46
|
+
setTradingProfile(tradingProfileResult.value.profile);
|
|
47
|
+
}
|
|
48
|
+
setError(null);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (!mountedRef.current) return;
|
|
51
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
52
|
+
setVincentConnected(false);
|
|
53
|
+
setError(null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
57
|
+
} finally {
|
|
58
|
+
if (mountedRef.current) setLoading(false);
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
mountedRef.current = true;
|
|
63
|
+
void fetchAll();
|
|
64
|
+
return () => {
|
|
65
|
+
mountedRef.current = false;
|
|
66
|
+
};
|
|
67
|
+
}, [fetchAll]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (intervalRef.current) {
|
|
70
|
+
clearInterval(intervalRef.current);
|
|
71
|
+
intervalRef.current = null;
|
|
72
|
+
}
|
|
73
|
+
if (vincentConnected) {
|
|
74
|
+
intervalRef.current = setInterval(
|
|
75
|
+
() => void fetchAll(),
|
|
76
|
+
POLL_INTERVAL_MS
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return () => {
|
|
80
|
+
if (intervalRef.current) {
|
|
81
|
+
clearInterval(intervalRef.current);
|
|
82
|
+
intervalRef.current = null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}, [vincentConnected, fetchAll]);
|
|
86
|
+
const refresh = useCallback(() => {
|
|
87
|
+
setLoading(true);
|
|
88
|
+
void fetchAll();
|
|
89
|
+
}, [fetchAll]);
|
|
90
|
+
return {
|
|
91
|
+
vincentConnected,
|
|
92
|
+
vincentConnectedAt,
|
|
93
|
+
walletAddresses,
|
|
94
|
+
walletBalances,
|
|
95
|
+
strategy,
|
|
96
|
+
tradingProfile,
|
|
97
|
+
loading,
|
|
98
|
+
error,
|
|
99
|
+
refresh
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
useVincentDashboard
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=useVincentDashboard.js.map
|