@exagent/agent 0.1.38 → 0.1.40
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/chunk-3AJR5TQF.mjs +6499 -0
- package/dist/chunk-47FQBTKE.mjs +9394 -0
- package/dist/chunk-6VSAJQU7.mjs +9394 -0
- package/dist/chunk-6Y4PBQ2U.mjs +6806 -0
- package/dist/chunk-EPXZ6MEW.mjs +6959 -0
- package/dist/chunk-KQUNPLRE.mjs +6959 -0
- package/dist/chunk-LDTWZMEI.mjs +6818 -0
- package/dist/chunk-MVUE6EPJ.mjs +8634 -0
- package/dist/chunk-NNIBSMJQ.mjs +9333 -0
- package/dist/chunk-OBESLXRL.mjs +8634 -0
- package/dist/chunk-TE3P727T.mjs +8634 -0
- package/dist/cli.js +7992 -3533
- package/dist/cli.mjs +1086 -21
- package/dist/index.d.mts +1088 -8
- package/dist/index.d.ts +1088 -8
- package/dist/index.js +3472 -236
- package/dist/index.mjs +27 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -45,6 +45,8 @@ __export(index_exports, {
|
|
|
45
45
|
HyperliquidWebSocket: () => HyperliquidWebSocket,
|
|
46
46
|
LLMConfigSchema: () => LLMConfigSchema,
|
|
47
47
|
LLMProviderSchema: () => LLMProviderSchema,
|
|
48
|
+
MARKET_CATEGORIES: () => MARKET_CATEGORIES,
|
|
49
|
+
MarketBrowser: () => MarketBrowser,
|
|
48
50
|
MarketDataService: () => MarketDataService,
|
|
49
51
|
MistralAdapter: () => MistralAdapter,
|
|
50
52
|
OllamaAdapter: () => OllamaAdapter,
|
|
@@ -53,8 +55,14 @@ __export(index_exports, {
|
|
|
53
55
|
PerpConfigSchema: () => PerpConfigSchema,
|
|
54
56
|
PerpOnboarding: () => PerpOnboarding,
|
|
55
57
|
PerpTradeRecorder: () => PerpTradeRecorder,
|
|
58
|
+
PolymarketClient: () => PolymarketClient,
|
|
56
59
|
PositionManager: () => PositionManager,
|
|
57
60
|
PositionTracker: () => PositionTracker,
|
|
61
|
+
PredictionConfigSchema: () => PredictionConfigSchema,
|
|
62
|
+
PredictionFunding: () => PredictionFunding,
|
|
63
|
+
PredictionOrderManager: () => PredictionOrderManager,
|
|
64
|
+
PredictionPositionManager: () => PredictionPositionManager,
|
|
65
|
+
PredictionTradeRecorder: () => PredictionTradeRecorder,
|
|
58
66
|
RelayClient: () => RelayClient,
|
|
59
67
|
RelayConfigSchema: () => RelayConfigSchema,
|
|
60
68
|
RiskManager: () => RiskManager,
|
|
@@ -66,9 +74,12 @@ __export(index_exports, {
|
|
|
66
74
|
VaultConfigSchema: () => VaultConfigSchema,
|
|
67
75
|
VaultManager: () => VaultManager,
|
|
68
76
|
VaultPolicySchema: () => VaultPolicySchema,
|
|
77
|
+
calculatePredictionFee: () => calculatePredictionFee,
|
|
69
78
|
createLLMAdapter: () => createLLMAdapter,
|
|
70
79
|
createSampleConfig: () => createSampleConfig,
|
|
80
|
+
decodePredictionInstrument: () => decodePredictionInstrument,
|
|
71
81
|
decryptEnvFile: () => decryptEnvFile,
|
|
82
|
+
encodePredictionInstrument: () => encodePredictionInstrument,
|
|
72
83
|
encryptEnvFile: () => encryptEnvFile,
|
|
73
84
|
fillHashToBytes32: () => fillHashToBytes32,
|
|
74
85
|
fillOidToBytes32: () => fillOidToBytes32,
|
|
@@ -78,6 +89,8 @@ __export(index_exports, {
|
|
|
78
89
|
loadConfig: () => loadConfig,
|
|
79
90
|
loadSecureEnv: () => loadSecureEnv,
|
|
80
91
|
loadStrategy: () => loadStrategy,
|
|
92
|
+
orderIdToBytes32: () => orderIdToBytes32,
|
|
93
|
+
tradeIdToBytes32: () => tradeIdToBytes32,
|
|
81
94
|
validateConfig: () => validateConfig,
|
|
82
95
|
validateStrategy: () => validateStrategy
|
|
83
96
|
});
|
|
@@ -85,9 +98,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
85
98
|
|
|
86
99
|
// src/runtime.ts
|
|
87
100
|
var import_sdk = require("@exagent/sdk");
|
|
88
|
-
var
|
|
89
|
-
var
|
|
90
|
-
var
|
|
101
|
+
var import_viem11 = require("viem");
|
|
102
|
+
var import_chains8 = require("viem/chains");
|
|
103
|
+
var import_accounts8 = require("viem/accounts");
|
|
91
104
|
|
|
92
105
|
// src/trading/market.ts
|
|
93
106
|
var import_viem2 = require("viem");
|
|
@@ -1635,7 +1648,7 @@ async function loadStrategy(strategyPath) {
|
|
|
1635
1648
|
console.log("No custom strategy found, using default (hold) strategy");
|
|
1636
1649
|
return defaultStrategy;
|
|
1637
1650
|
}
|
|
1638
|
-
async function loadTypeScriptModule(
|
|
1651
|
+
async function loadTypeScriptModule(path4) {
|
|
1639
1652
|
try {
|
|
1640
1653
|
const tsxPath = require.resolve("tsx");
|
|
1641
1654
|
const { pathToFileURL } = await import("url");
|
|
@@ -1646,7 +1659,7 @@ async function loadTypeScriptModule(path2) {
|
|
|
1646
1659
|
"--import",
|
|
1647
1660
|
"tsx/esm",
|
|
1648
1661
|
"-e",
|
|
1649
|
-
`import('${pathToFileURL(
|
|
1662
|
+
`import('${pathToFileURL(path4).href}').then(m => console.log(JSON.stringify({ exports: Object.keys(m) }))).catch(e => console.error('ERROR:', e.message))`
|
|
1650
1663
|
],
|
|
1651
1664
|
{
|
|
1652
1665
|
cwd: process.cwd(),
|
|
@@ -1669,7 +1682,7 @@ async function loadTypeScriptModule(path2) {
|
|
|
1669
1682
|
const tsx = await import("tsx/esm/api");
|
|
1670
1683
|
const unregister = tsx.register();
|
|
1671
1684
|
try {
|
|
1672
|
-
const module2 = await import(
|
|
1685
|
+
const module2 = await import(path4);
|
|
1673
1686
|
return module2;
|
|
1674
1687
|
} finally {
|
|
1675
1688
|
unregister();
|
|
@@ -2001,6 +2014,49 @@ var PerpConfigSchema = import_zod.z.object({
|
|
|
2001
2014
|
/** Allowed perp instruments (e.g. ["ETH", "BTC", "SOL"]). If empty, all instruments allowed. */
|
|
2002
2015
|
allowedInstruments: import_zod.z.array(import_zod.z.string()).optional()
|
|
2003
2016
|
}).optional();
|
|
2017
|
+
var PredictionConfigSchema = import_zod.z.object({
|
|
2018
|
+
/** Enable prediction market trading */
|
|
2019
|
+
enabled: import_zod.z.boolean().default(false),
|
|
2020
|
+
/** Polymarket CLOB API URL */
|
|
2021
|
+
clobApiUrl: import_zod.z.string().url().default("https://clob.polymarket.com"),
|
|
2022
|
+
/** Gamma API URL for market discovery */
|
|
2023
|
+
gammaApiUrl: import_zod.z.string().url().default("https://gamma-api.polymarket.com"),
|
|
2024
|
+
/** Polygon RPC URL */
|
|
2025
|
+
polygonRpcUrl: import_zod.z.string().url().optional(),
|
|
2026
|
+
/** CLOB API key (L2 auth) — derived from wallet signature if not provided */
|
|
2027
|
+
clobApiKey: import_zod.z.string().optional(),
|
|
2028
|
+
/** CLOB API secret (L2 auth) */
|
|
2029
|
+
clobApiSecret: import_zod.z.string().optional(),
|
|
2030
|
+
/** CLOB API passphrase (L2 auth) */
|
|
2031
|
+
clobApiPassphrase: import_zod.z.string().optional(),
|
|
2032
|
+
/** Private key for the prediction relayer (calls recordPerpTrade on Base). Falls back to agent wallet. */
|
|
2033
|
+
predictionRelayerKey: import_zod.z.string().optional(),
|
|
2034
|
+
/** Maximum notional per trade in USD (default: 500) */
|
|
2035
|
+
maxNotionalUSD: import_zod.z.number().min(1).default(500),
|
|
2036
|
+
/** Maximum total exposure across all markets in USD (default: 5000) */
|
|
2037
|
+
maxTotalExposureUSD: import_zod.z.number().min(1).default(5e3),
|
|
2038
|
+
/** Target USDC allocation on Polygon in USD (default: 100) */
|
|
2039
|
+
polygonAllocationUSD: import_zod.z.number().min(1).default(100),
|
|
2040
|
+
/** Allowed market categories (empty = all categories) */
|
|
2041
|
+
allowedCategories: import_zod.z.array(import_zod.z.string()).optional(),
|
|
2042
|
+
/** Fee bridge threshold in USD — accumulated fees are bridged to Base when exceeded (default: 10) */
|
|
2043
|
+
feeBridgeThresholdUSD: import_zod.z.number().min(1).default(10),
|
|
2044
|
+
/** Prediction vault configuration (ERC-4626 + ERC-1271 on Polygon) */
|
|
2045
|
+
vault: import_zod.z.object({
|
|
2046
|
+
/** Enable vault mode for prediction trading */
|
|
2047
|
+
enabled: import_zod.z.boolean().default(false),
|
|
2048
|
+
/** PredictionVaultFactory address on Polygon */
|
|
2049
|
+
factoryAddress: import_zod.z.string(),
|
|
2050
|
+
/** PredictionFeeCollector address on Polygon */
|
|
2051
|
+
feeCollectorAddress: import_zod.z.string().optional(),
|
|
2052
|
+
/** Existing vault address (if already created) */
|
|
2053
|
+
vaultAddress: import_zod.z.string().optional(),
|
|
2054
|
+
/** Agent's fee recipient address */
|
|
2055
|
+
feeRecipient: import_zod.z.string().optional(),
|
|
2056
|
+
/** Whether agent is verified (higher deposit cap) */
|
|
2057
|
+
isVerified: import_zod.z.boolean().default(false)
|
|
2058
|
+
}).optional()
|
|
2059
|
+
}).optional();
|
|
2004
2060
|
var AgentConfigSchema = import_zod.z.object({
|
|
2005
2061
|
// Identity (from on-chain registration)
|
|
2006
2062
|
agentId: import_zod.z.union([import_zod.z.number().positive(), import_zod.z.string()]),
|
|
@@ -2020,6 +2076,8 @@ var AgentConfigSchema = import_zod.z.object({
|
|
|
2020
2076
|
relay: RelayConfigSchema,
|
|
2021
2077
|
// Perp trading configuration (Hyperliquid)
|
|
2022
2078
|
perp: PerpConfigSchema,
|
|
2079
|
+
// Prediction market configuration (Polymarket)
|
|
2080
|
+
prediction: PredictionConfigSchema,
|
|
2023
2081
|
// Allowed tokens (addresses)
|
|
2024
2082
|
allowedTokens: import_zod.z.array(import_zod.z.string()).optional()
|
|
2025
2083
|
});
|
|
@@ -3144,6 +3202,609 @@ function openBrowser(url) {
|
|
|
3144
3202
|
}
|
|
3145
3203
|
}
|
|
3146
3204
|
|
|
3205
|
+
// src/paper/executor.ts
|
|
3206
|
+
var PaperExecutor = class {
|
|
3207
|
+
config;
|
|
3208
|
+
allowedTokens;
|
|
3209
|
+
prices;
|
|
3210
|
+
slippageBps;
|
|
3211
|
+
feeRateBps;
|
|
3212
|
+
estimatedGasUSD;
|
|
3213
|
+
/** Running totals for the paper session */
|
|
3214
|
+
totalFeesPaid = 0;
|
|
3215
|
+
totalGasPaid = 0;
|
|
3216
|
+
tradeCount = 0;
|
|
3217
|
+
constructor(config, options) {
|
|
3218
|
+
this.config = config;
|
|
3219
|
+
this.slippageBps = options?.slippageBps ?? 50;
|
|
3220
|
+
this.feeRateBps = options?.feeRateBps ?? 20;
|
|
3221
|
+
this.estimatedGasUSD = options?.estimatedGasUSD ?? 0.01;
|
|
3222
|
+
this.prices = {};
|
|
3223
|
+
this.allowedTokens = new Set(
|
|
3224
|
+
(config.allowedTokens || []).map((t) => t.toLowerCase())
|
|
3225
|
+
);
|
|
3226
|
+
}
|
|
3227
|
+
/**
|
|
3228
|
+
* Update live prices before executing trades.
|
|
3229
|
+
* Called by the runtime before each cycle with current market data.
|
|
3230
|
+
*/
|
|
3231
|
+
updatePrices(prices) {
|
|
3232
|
+
this.prices = prices;
|
|
3233
|
+
}
|
|
3234
|
+
/**
|
|
3235
|
+
* Simulate a single trade.
|
|
3236
|
+
* Returns the same shape as TradeExecutor.execute() for drop-in compatibility.
|
|
3237
|
+
*/
|
|
3238
|
+
async execute(signal) {
|
|
3239
|
+
if (signal.action === "hold") {
|
|
3240
|
+
return { success: true };
|
|
3241
|
+
}
|
|
3242
|
+
if (!this.validateSignal(signal)) {
|
|
3243
|
+
return { success: false, error: "Signal failed validation" };
|
|
3244
|
+
}
|
|
3245
|
+
const tokenInPrice = this.prices[signal.tokenIn.toLowerCase()];
|
|
3246
|
+
const tokenOutPrice = this.prices[signal.tokenOut.toLowerCase()];
|
|
3247
|
+
if (!tokenInPrice || !tokenOutPrice) {
|
|
3248
|
+
return {
|
|
3249
|
+
success: false,
|
|
3250
|
+
error: `Missing price data: ${!tokenInPrice ? signal.tokenIn : signal.tokenOut}`
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
const fill = this.simulateFill(signal, tokenInPrice, tokenOutPrice);
|
|
3254
|
+
this.tradeCount++;
|
|
3255
|
+
this.totalFeesPaid += fill.feeUSD;
|
|
3256
|
+
this.totalGasPaid += this.estimatedGasUSD;
|
|
3257
|
+
console.log(
|
|
3258
|
+
` [PAPER] ${signal.action.toUpperCase()}: ${signal.tokenIn.slice(0, 10)}... \u2192 ${signal.tokenOut.slice(0, 10)}...`
|
|
3259
|
+
);
|
|
3260
|
+
console.log(
|
|
3261
|
+
` [PAPER] Fill: $${fill.valueInUSD.toFixed(2)} \u2192 $${fill.valueOutUSD.toFixed(2)} (slippage: ${fill.appliedSlippageBps}bps, fee: $${fill.feeUSD.toFixed(4)})`
|
|
3262
|
+
);
|
|
3263
|
+
return {
|
|
3264
|
+
success: true,
|
|
3265
|
+
txHash: `paper-${Date.now()}-${this.tradeCount}`,
|
|
3266
|
+
paperFill: fill
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Simulate multiple trades (same interface as TradeExecutor.executeAll).
|
|
3271
|
+
*/
|
|
3272
|
+
async executeAll(signals) {
|
|
3273
|
+
const results = [];
|
|
3274
|
+
for (const signal of signals) {
|
|
3275
|
+
const result = await this.execute(signal);
|
|
3276
|
+
results.push({ signal, ...result });
|
|
3277
|
+
}
|
|
3278
|
+
return results;
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Get session totals for reporting.
|
|
3282
|
+
*/
|
|
3283
|
+
getSessionStats() {
|
|
3284
|
+
return {
|
|
3285
|
+
tradeCount: this.tradeCount,
|
|
3286
|
+
totalFeesPaid: this.totalFeesPaid,
|
|
3287
|
+
totalGasPaid: this.totalGasPaid,
|
|
3288
|
+
slippageBps: this.slippageBps,
|
|
3289
|
+
feeRateBps: this.feeRateBps
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
/**
|
|
3293
|
+
* Reset session stats (e.g., when starting a new paper session).
|
|
3294
|
+
*/
|
|
3295
|
+
resetStats() {
|
|
3296
|
+
this.tradeCount = 0;
|
|
3297
|
+
this.totalFeesPaid = 0;
|
|
3298
|
+
this.totalGasPaid = 0;
|
|
3299
|
+
}
|
|
3300
|
+
// --- Private methods ---
|
|
3301
|
+
simulateFill(signal, tokenInPrice, tokenOutPrice) {
|
|
3302
|
+
const decimalsIn = getTokenDecimals(signal.tokenIn);
|
|
3303
|
+
const valueInUSD = Number(signal.amountIn) / Math.pow(10, decimalsIn) * tokenInPrice;
|
|
3304
|
+
const afterFeeUSD = valueInUSD * (1 - this.feeRateBps / 1e4);
|
|
3305
|
+
const feeUSD = valueInUSD - afterFeeUSD;
|
|
3306
|
+
const afterSlippageUSD = afterFeeUSD * (1 - this.slippageBps / 1e4);
|
|
3307
|
+
const decimalsOut = getTokenDecimals(signal.tokenOut);
|
|
3308
|
+
const amountOut = BigInt(Math.floor(afterSlippageUSD / tokenOutPrice * Math.pow(10, decimalsOut)));
|
|
3309
|
+
return {
|
|
3310
|
+
signal,
|
|
3311
|
+
timestamp: Date.now(),
|
|
3312
|
+
valueInUSD,
|
|
3313
|
+
valueOutUSD: afterSlippageUSD,
|
|
3314
|
+
feeUSD,
|
|
3315
|
+
gasUSD: this.estimatedGasUSD,
|
|
3316
|
+
amountOut,
|
|
3317
|
+
fillPriceUSD: tokenOutPrice * (1 + this.slippageBps / 1e4),
|
|
3318
|
+
// worse price due to slippage
|
|
3319
|
+
appliedSlippageBps: this.slippageBps
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
validateSignal(signal) {
|
|
3323
|
+
if (signal.confidence < 0.5) {
|
|
3324
|
+
console.warn(` [PAPER] Signal confidence ${signal.confidence} below threshold (0.5)`);
|
|
3325
|
+
return false;
|
|
3326
|
+
}
|
|
3327
|
+
if (this.allowedTokens.size > 0) {
|
|
3328
|
+
const tokenInAllowed = this.allowedTokens.has(signal.tokenIn.toLowerCase());
|
|
3329
|
+
const tokenOutAllowed = this.allowedTokens.has(signal.tokenOut.toLowerCase());
|
|
3330
|
+
if (!tokenInAllowed || !tokenOutAllowed) {
|
|
3331
|
+
console.warn(` [PAPER] Token not in allowed list \u2014 skipping`);
|
|
3332
|
+
return false;
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
return true;
|
|
3336
|
+
}
|
|
3337
|
+
};
|
|
3338
|
+
|
|
3339
|
+
// src/paper/portfolio.ts
|
|
3340
|
+
var fs = __toESM(require("fs"));
|
|
3341
|
+
var path = __toESM(require("path"));
|
|
3342
|
+
var SimulatedPortfolio = class _SimulatedPortfolio {
|
|
3343
|
+
balances;
|
|
3344
|
+
initialBalances;
|
|
3345
|
+
equityCurve;
|
|
3346
|
+
trades;
|
|
3347
|
+
startedAt;
|
|
3348
|
+
dataDir;
|
|
3349
|
+
constructor(initialBalances, dataDir, options) {
|
|
3350
|
+
this.balances = {};
|
|
3351
|
+
this.initialBalances = {};
|
|
3352
|
+
for (const [addr, amount] of Object.entries(initialBalances)) {
|
|
3353
|
+
const key = addr.toLowerCase();
|
|
3354
|
+
this.balances[key] = amount;
|
|
3355
|
+
this.initialBalances[key] = amount;
|
|
3356
|
+
}
|
|
3357
|
+
this.equityCurve = [];
|
|
3358
|
+
this.trades = [];
|
|
3359
|
+
this.startedAt = options?.startedAt ?? Date.now();
|
|
3360
|
+
this.dataDir = dataDir;
|
|
3361
|
+
if (!fs.existsSync(dataDir)) {
|
|
3362
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
/**
|
|
3366
|
+
* Get the session start timestamp.
|
|
3367
|
+
*/
|
|
3368
|
+
getStartedAt() {
|
|
3369
|
+
return this.startedAt;
|
|
3370
|
+
}
|
|
3371
|
+
/**
|
|
3372
|
+
* Get current balances in the same format as MarketData.balances.
|
|
3373
|
+
* Used by the runtime to build MarketData for strategy calls.
|
|
3374
|
+
*/
|
|
3375
|
+
getBalances() {
|
|
3376
|
+
return { ...this.balances };
|
|
3377
|
+
}
|
|
3378
|
+
/**
|
|
3379
|
+
* Apply a paper trade fill to the portfolio.
|
|
3380
|
+
* Deducts tokenIn, adds tokenOut.
|
|
3381
|
+
*/
|
|
3382
|
+
applyFill(fill) {
|
|
3383
|
+
const tokenIn = fill.signal.tokenIn.toLowerCase();
|
|
3384
|
+
const tokenOut = fill.signal.tokenOut.toLowerCase();
|
|
3385
|
+
const currentIn = this.balances[tokenIn] || BigInt(0);
|
|
3386
|
+
const newIn = currentIn - fill.signal.amountIn;
|
|
3387
|
+
if (newIn < BigInt(0)) {
|
|
3388
|
+
console.warn(` [PAPER] Warning: balance went negative for ${tokenIn} \u2014 clamping to 0`);
|
|
3389
|
+
this.balances[tokenIn] = BigInt(0);
|
|
3390
|
+
} else {
|
|
3391
|
+
this.balances[tokenIn] = newIn;
|
|
3392
|
+
}
|
|
3393
|
+
const currentOut = this.balances[tokenOut] || BigInt(0);
|
|
3394
|
+
this.balances[tokenOut] = currentOut + fill.amountOut;
|
|
3395
|
+
this.trades.push({
|
|
3396
|
+
timestamp: fill.timestamp,
|
|
3397
|
+
action: fill.signal.action,
|
|
3398
|
+
tokenIn,
|
|
3399
|
+
tokenOut,
|
|
3400
|
+
amountIn: fill.signal.amountIn.toString(),
|
|
3401
|
+
amountOut: fill.amountOut.toString(),
|
|
3402
|
+
valueInUSD: fill.valueInUSD,
|
|
3403
|
+
valueOutUSD: fill.valueOutUSD,
|
|
3404
|
+
feeUSD: fill.feeUSD,
|
|
3405
|
+
gasUSD: fill.gasUSD,
|
|
3406
|
+
reasoning: fill.signal.reasoning
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
/**
|
|
3410
|
+
* Record a portfolio value snapshot (called after each cycle).
|
|
3411
|
+
* @param prices - Current token prices
|
|
3412
|
+
* @param timestamp - Optional timestamp override (for backtesting with historical timestamps)
|
|
3413
|
+
*/
|
|
3414
|
+
recordEquityPoint(prices, timestamp) {
|
|
3415
|
+
const value = this.calculateValue(prices);
|
|
3416
|
+
this.equityCurve.push({
|
|
3417
|
+
timestamp: timestamp ?? Date.now(),
|
|
3418
|
+
value
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
/**
|
|
3422
|
+
* Calculate current portfolio value in USD.
|
|
3423
|
+
*/
|
|
3424
|
+
calculateValue(prices) {
|
|
3425
|
+
let total = 0;
|
|
3426
|
+
for (const [token, balance] of Object.entries(this.balances)) {
|
|
3427
|
+
if (balance <= BigInt(0)) continue;
|
|
3428
|
+
const price = prices[token.toLowerCase()] || 0;
|
|
3429
|
+
const decimals = getTokenDecimals(token);
|
|
3430
|
+
total += Number(balance) / Math.pow(10, decimals) * price;
|
|
3431
|
+
}
|
|
3432
|
+
return total;
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Calculate initial portfolio value for return comparison.
|
|
3436
|
+
*/
|
|
3437
|
+
calculateInitialValue(prices) {
|
|
3438
|
+
let total = 0;
|
|
3439
|
+
for (const [token, balance] of Object.entries(this.initialBalances)) {
|
|
3440
|
+
if (balance <= BigInt(0)) continue;
|
|
3441
|
+
const price = prices[token.toLowerCase()] || 0;
|
|
3442
|
+
const decimals = getTokenDecimals(token);
|
|
3443
|
+
total += Number(balance) / Math.pow(10, decimals) * price;
|
|
3444
|
+
}
|
|
3445
|
+
return total;
|
|
3446
|
+
}
|
|
3447
|
+
/**
|
|
3448
|
+
* Get equity curve data.
|
|
3449
|
+
*/
|
|
3450
|
+
getEquityCurve() {
|
|
3451
|
+
return [...this.equityCurve];
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Get all paper trades.
|
|
3455
|
+
*/
|
|
3456
|
+
getTrades() {
|
|
3457
|
+
return [...this.trades];
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Get summary for display.
|
|
3461
|
+
*/
|
|
3462
|
+
getSummary(prices) {
|
|
3463
|
+
const currentValue = this.calculateValue(prices);
|
|
3464
|
+
const initialValue = this.calculateInitialValue(prices);
|
|
3465
|
+
const totalReturn = initialValue > 0 ? (currentValue - initialValue) / initialValue * 100 : 0;
|
|
3466
|
+
const totalFees = this.trades.reduce((sum, t) => sum + t.feeUSD, 0);
|
|
3467
|
+
const totalGas = this.trades.reduce((sum, t) => sum + t.gasUSD, 0);
|
|
3468
|
+
return {
|
|
3469
|
+
startedAt: this.startedAt,
|
|
3470
|
+
currentValue,
|
|
3471
|
+
initialValue,
|
|
3472
|
+
totalReturnPct: totalReturn,
|
|
3473
|
+
totalReturnUSD: currentValue - initialValue,
|
|
3474
|
+
tradeCount: this.trades.length,
|
|
3475
|
+
totalFees,
|
|
3476
|
+
totalGas,
|
|
3477
|
+
positions: this.getPositionBreakdown(prices)
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Persist portfolio state to disk.
|
|
3482
|
+
*/
|
|
3483
|
+
save() {
|
|
3484
|
+
const filePath = path.join(this.dataDir, "paper-portfolio.json");
|
|
3485
|
+
const data = {
|
|
3486
|
+
startedAt: this.startedAt,
|
|
3487
|
+
balances: Object.fromEntries(
|
|
3488
|
+
Object.entries(this.balances).map(([k, v]) => [k, v.toString()])
|
|
3489
|
+
),
|
|
3490
|
+
initialBalances: Object.fromEntries(
|
|
3491
|
+
Object.entries(this.initialBalances).map(([k, v]) => [k, v.toString()])
|
|
3492
|
+
),
|
|
3493
|
+
equityCurve: this.equityCurve,
|
|
3494
|
+
trades: this.trades
|
|
3495
|
+
};
|
|
3496
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
3497
|
+
}
|
|
3498
|
+
/**
|
|
3499
|
+
* Load portfolio state from disk (for resuming a paper session).
|
|
3500
|
+
* Returns null if no saved state exists.
|
|
3501
|
+
*/
|
|
3502
|
+
static load(dataDir) {
|
|
3503
|
+
const filePath = path.join(dataDir, "paper-portfolio.json");
|
|
3504
|
+
if (!fs.existsSync(filePath)) return null;
|
|
3505
|
+
try {
|
|
3506
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
3507
|
+
const initialBalances = {};
|
|
3508
|
+
for (const [k, v] of Object.entries(raw.initialBalances)) {
|
|
3509
|
+
initialBalances[k] = BigInt(v);
|
|
3510
|
+
}
|
|
3511
|
+
const portfolio = new _SimulatedPortfolio(initialBalances, dataDir);
|
|
3512
|
+
portfolio.startedAt = raw.startedAt;
|
|
3513
|
+
portfolio.equityCurve = raw.equityCurve || [];
|
|
3514
|
+
portfolio.trades = raw.trades || [];
|
|
3515
|
+
portfolio.balances = {};
|
|
3516
|
+
for (const [k, v] of Object.entries(raw.balances)) {
|
|
3517
|
+
portfolio.balances[k] = BigInt(v);
|
|
3518
|
+
}
|
|
3519
|
+
return portfolio;
|
|
3520
|
+
} catch {
|
|
3521
|
+
console.warn(" [PAPER] Failed to load saved portfolio state \u2014 starting fresh");
|
|
3522
|
+
return null;
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Delete saved state (for starting a new paper session).
|
|
3527
|
+
*/
|
|
3528
|
+
static clear(dataDir) {
|
|
3529
|
+
const filePath = path.join(dataDir, "paper-portfolio.json");
|
|
3530
|
+
if (fs.existsSync(filePath)) {
|
|
3531
|
+
fs.unlinkSync(filePath);
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
// --- Private ---
|
|
3535
|
+
getPositionBreakdown(prices) {
|
|
3536
|
+
const positions = [];
|
|
3537
|
+
for (const [token, balance] of Object.entries(this.balances)) {
|
|
3538
|
+
if (balance <= BigInt(0)) continue;
|
|
3539
|
+
const price = prices[token.toLowerCase()] || 0;
|
|
3540
|
+
const decimals = getTokenDecimals(token);
|
|
3541
|
+
const valueUSD = Number(balance) / Math.pow(10, decimals) * price;
|
|
3542
|
+
if (valueUSD >= 0.01) {
|
|
3543
|
+
positions.push({
|
|
3544
|
+
token,
|
|
3545
|
+
balance: (Number(balance) / Math.pow(10, decimals)).toFixed(6),
|
|
3546
|
+
valueUSD
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
return positions.sort((a, b) => b.valueUSD - a.valueUSD);
|
|
3551
|
+
}
|
|
3552
|
+
};
|
|
3553
|
+
|
|
3554
|
+
// src/paper/results.ts
|
|
3555
|
+
var fs2 = __toESM(require("fs"));
|
|
3556
|
+
var path2 = __toESM(require("path"));
|
|
3557
|
+
|
|
3558
|
+
// src/paper/metrics.ts
|
|
3559
|
+
function calculateMetrics(trades, equityCurve, initialValue, currentValue, startedAt, endedAt) {
|
|
3560
|
+
const durationMs = (endedAt ?? Date.now()) - startedAt;
|
|
3561
|
+
const durationDays = durationMs / (1e3 * 60 * 60 * 24);
|
|
3562
|
+
const totalReturnUSD = currentValue - initialValue;
|
|
3563
|
+
const totalReturnPct = initialValue > 0 ? totalReturnUSD / initialValue * 100 : 0;
|
|
3564
|
+
const actionTrades = trades.filter((t) => t.action !== "hold");
|
|
3565
|
+
let wins = 0;
|
|
3566
|
+
let losses = 0;
|
|
3567
|
+
let grossProfit = 0;
|
|
3568
|
+
let grossLoss = 0;
|
|
3569
|
+
for (const trade of actionTrades) {
|
|
3570
|
+
const pnl = trade.valueOutUSD - trade.valueInUSD;
|
|
3571
|
+
if (pnl >= 0) {
|
|
3572
|
+
wins++;
|
|
3573
|
+
grossProfit += pnl;
|
|
3574
|
+
} else {
|
|
3575
|
+
losses++;
|
|
3576
|
+
grossLoss += Math.abs(pnl);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
const winRate = actionTrades.length > 0 ? wins / actionTrades.length : 0;
|
|
3580
|
+
const profitFactor = grossLoss > 0 ? grossProfit / grossLoss : null;
|
|
3581
|
+
const { maxDrawdownPct, maxDrawdownUSD } = calculateMaxDrawdown(equityCurve);
|
|
3582
|
+
const { sharpe, sortino } = calculateRiskMetrics(equityCurve);
|
|
3583
|
+
const totalFees = trades.reduce((s, t) => s + t.feeUSD, 0);
|
|
3584
|
+
const totalGas = trades.reduce((s, t) => s + t.gasUSD, 0);
|
|
3585
|
+
const avgTradeValueUSD = actionTrades.length > 0 ? actionTrades.reduce((s, t) => s + t.valueInUSD, 0) / actionTrades.length : 0;
|
|
3586
|
+
const tradesPerDay = durationDays > 0 ? actionTrades.length / durationDays : 0;
|
|
3587
|
+
return {
|
|
3588
|
+
totalReturnPct,
|
|
3589
|
+
totalReturnUSD,
|
|
3590
|
+
winRate,
|
|
3591
|
+
wins,
|
|
3592
|
+
losses,
|
|
3593
|
+
maxDrawdownPct,
|
|
3594
|
+
maxDrawdownUSD,
|
|
3595
|
+
sharpeRatio: sharpe,
|
|
3596
|
+
sortinoRatio: sortino,
|
|
3597
|
+
profitFactor,
|
|
3598
|
+
avgTradeValueUSD,
|
|
3599
|
+
totalFees,
|
|
3600
|
+
totalGas,
|
|
3601
|
+
durationMs,
|
|
3602
|
+
tradesPerDay
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
function calculateMaxDrawdown(curve) {
|
|
3606
|
+
if (curve.length < 2) return { maxDrawdownPct: 0, maxDrawdownUSD: 0 };
|
|
3607
|
+
let peak = curve[0].value;
|
|
3608
|
+
let maxDrawdownPct = 0;
|
|
3609
|
+
let maxDrawdownUSD = 0;
|
|
3610
|
+
for (const point of curve) {
|
|
3611
|
+
if (point.value > peak) {
|
|
3612
|
+
peak = point.value;
|
|
3613
|
+
}
|
|
3614
|
+
if (peak > 0) {
|
|
3615
|
+
const drawdownPct = (point.value - peak) / peak * 100;
|
|
3616
|
+
const drawdownUSD = point.value - peak;
|
|
3617
|
+
if (drawdownPct < maxDrawdownPct) {
|
|
3618
|
+
maxDrawdownPct = drawdownPct;
|
|
3619
|
+
maxDrawdownUSD = drawdownUSD;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
return { maxDrawdownPct, maxDrawdownUSD };
|
|
3624
|
+
}
|
|
3625
|
+
function calculateRiskMetrics(curve) {
|
|
3626
|
+
if (curve.length < 3) return { sharpe: null, sortino: null };
|
|
3627
|
+
const returns = [];
|
|
3628
|
+
for (let i = 1; i < curve.length; i++) {
|
|
3629
|
+
if (curve[i - 1].value > 0) {
|
|
3630
|
+
returns.push((curve[i].value - curve[i - 1].value) / curve[i - 1].value);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
if (returns.length < 2) return { sharpe: null, sortino: null };
|
|
3634
|
+
const meanReturn = returns.reduce((s, r) => s + r, 0) / returns.length;
|
|
3635
|
+
const variance = returns.reduce((s, r) => s + (r - meanReturn) ** 2, 0) / (returns.length - 1);
|
|
3636
|
+
const stdDev = Math.sqrt(variance);
|
|
3637
|
+
const downsideVariance = returns.reduce((s, r) => {
|
|
3638
|
+
const downside = Math.min(0, r);
|
|
3639
|
+
return s + downside ** 2;
|
|
3640
|
+
}, 0) / (returns.length - 1);
|
|
3641
|
+
const downsideDev = Math.sqrt(downsideVariance);
|
|
3642
|
+
const totalTimeMs = curve[curve.length - 1].timestamp - curve[0].timestamp;
|
|
3643
|
+
const avgPeriodMs = totalTimeMs / (curve.length - 1);
|
|
3644
|
+
const periodsPerYear = avgPeriodMs > 0 ? 365 * 24 * 60 * 60 * 1e3 / avgPeriodMs : 365;
|
|
3645
|
+
const sharpe = stdDev > 0 ? meanReturn / stdDev * Math.sqrt(periodsPerYear) : null;
|
|
3646
|
+
const sortino = downsideDev > 0 ? meanReturn / downsideDev * Math.sqrt(periodsPerYear) : null;
|
|
3647
|
+
return { sharpe, sortino };
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
// src/paper/results.ts
|
|
3651
|
+
function saveSessionResult(dataDir, agentName, llm, portfolio, equityCurve, trades, startedAt, options) {
|
|
3652
|
+
const endedAt = options?.endedAt ?? Date.now();
|
|
3653
|
+
const durationMs = endedAt - startedAt;
|
|
3654
|
+
const prefix = options?.idPrefix ?? "paper";
|
|
3655
|
+
const id = `${prefix}-${startedAt}`;
|
|
3656
|
+
const metrics = calculateMetrics(
|
|
3657
|
+
trades,
|
|
3658
|
+
equityCurve,
|
|
3659
|
+
portfolio.initialValue,
|
|
3660
|
+
portfolio.currentValue,
|
|
3661
|
+
startedAt,
|
|
3662
|
+
endedAt
|
|
3663
|
+
);
|
|
3664
|
+
const result = {
|
|
3665
|
+
id,
|
|
3666
|
+
agentName,
|
|
3667
|
+
startedAt,
|
|
3668
|
+
endedAt,
|
|
3669
|
+
durationMs,
|
|
3670
|
+
llm,
|
|
3671
|
+
initialValue: portfolio.initialValue,
|
|
3672
|
+
finalValue: portfolio.currentValue,
|
|
3673
|
+
metrics,
|
|
3674
|
+
portfolio,
|
|
3675
|
+
equityCurve,
|
|
3676
|
+
trades
|
|
3677
|
+
};
|
|
3678
|
+
const sessionsDir = path2.join(dataDir, "sessions");
|
|
3679
|
+
if (!fs2.existsSync(sessionsDir)) {
|
|
3680
|
+
fs2.mkdirSync(sessionsDir, { recursive: true });
|
|
3681
|
+
}
|
|
3682
|
+
const filePath = path2.join(sessionsDir, `${id}.json`);
|
|
3683
|
+
fs2.writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
3684
|
+
console.log(` [PAPER] Session saved: ${filePath}`);
|
|
3685
|
+
return result;
|
|
3686
|
+
}
|
|
3687
|
+
function formatSessionReport(result) {
|
|
3688
|
+
const m = result.metrics;
|
|
3689
|
+
const lines = [];
|
|
3690
|
+
const divider = "\u2550".repeat(56);
|
|
3691
|
+
const thinDivider = "\u2500".repeat(56);
|
|
3692
|
+
lines.push("");
|
|
3693
|
+
lines.push(` ${divider}`);
|
|
3694
|
+
lines.push(` PAPER TRADING RESULTS \u2014 ${result.agentName}`);
|
|
3695
|
+
lines.push(` ${divider}`);
|
|
3696
|
+
lines.push("");
|
|
3697
|
+
lines.push(` Session: ${result.id}`);
|
|
3698
|
+
lines.push(` Started: ${new Date(result.startedAt).toLocaleString()}`);
|
|
3699
|
+
lines.push(` Ended: ${new Date(result.endedAt).toLocaleString()}`);
|
|
3700
|
+
lines.push(` Duration: ${formatDuration(result.durationMs)}`);
|
|
3701
|
+
lines.push(` LLM: ${result.llm.provider}/${result.llm.model}`);
|
|
3702
|
+
lines.push("");
|
|
3703
|
+
lines.push(` ${thinDivider}`);
|
|
3704
|
+
lines.push(" PERFORMANCE");
|
|
3705
|
+
lines.push(` ${thinDivider}`);
|
|
3706
|
+
lines.push("");
|
|
3707
|
+
const returnSign = m.totalReturnPct >= 0 ? "+" : "";
|
|
3708
|
+
lines.push(` Total Return: ${returnSign}${m.totalReturnPct.toFixed(2)}% ($${m.totalReturnUSD.toFixed(2)})`);
|
|
3709
|
+
lines.push(` Initial Value: $${result.initialValue.toFixed(2)}`);
|
|
3710
|
+
lines.push(` Final Value: $${result.finalValue.toFixed(2)}`);
|
|
3711
|
+
lines.push("");
|
|
3712
|
+
lines.push(` Max Drawdown: ${m.maxDrawdownPct.toFixed(2)}% ($${m.maxDrawdownUSD.toFixed(2)})`);
|
|
3713
|
+
lines.push(` Sharpe Ratio: ${m.sharpeRatio !== null ? m.sharpeRatio.toFixed(2) : "N/A"}`);
|
|
3714
|
+
lines.push(` Sortino Ratio: ${m.sortinoRatio !== null ? m.sortinoRatio.toFixed(2) : "N/A"}`);
|
|
3715
|
+
lines.push("");
|
|
3716
|
+
lines.push(` ${thinDivider}`);
|
|
3717
|
+
lines.push(" TRADES");
|
|
3718
|
+
lines.push(` ${thinDivider}`);
|
|
3719
|
+
lines.push("");
|
|
3720
|
+
lines.push(` Total Trades: ${result.trades.length}`);
|
|
3721
|
+
lines.push(` Win Rate: ${(m.winRate * 100).toFixed(1)}% (${m.wins}W / ${m.losses}L)`);
|
|
3722
|
+
lines.push(` Profit Factor: ${m.profitFactor !== null ? m.profitFactor.toFixed(2) : "N/A"}`);
|
|
3723
|
+
lines.push(` Avg Trade Size: $${m.avgTradeValueUSD.toFixed(2)}`);
|
|
3724
|
+
lines.push(` Trades/Day: ${m.tradesPerDay.toFixed(1)}`);
|
|
3725
|
+
lines.push("");
|
|
3726
|
+
lines.push(` ${thinDivider}`);
|
|
3727
|
+
lines.push(" COSTS (SIMULATED)");
|
|
3728
|
+
lines.push(` ${thinDivider}`);
|
|
3729
|
+
lines.push("");
|
|
3730
|
+
lines.push(` Total Fees: $${m.totalFees.toFixed(4)}`);
|
|
3731
|
+
lines.push(` Total Gas: $${m.totalGas.toFixed(4)}`);
|
|
3732
|
+
lines.push(` Total Costs: $${(m.totalFees + m.totalGas).toFixed(4)}`);
|
|
3733
|
+
lines.push("");
|
|
3734
|
+
if (result.portfolio.positions.length > 0) {
|
|
3735
|
+
lines.push(` ${thinDivider}`);
|
|
3736
|
+
lines.push(" FINAL POSITIONS");
|
|
3737
|
+
lines.push(` ${thinDivider}`);
|
|
3738
|
+
lines.push("");
|
|
3739
|
+
for (const pos of result.portfolio.positions) {
|
|
3740
|
+
const tokenShort = pos.token.slice(0, 10) + "...";
|
|
3741
|
+
lines.push(` ${tokenShort} ${pos.balance} $${pos.valueUSD.toFixed(2)}`);
|
|
3742
|
+
}
|
|
3743
|
+
lines.push("");
|
|
3744
|
+
}
|
|
3745
|
+
if (result.equityCurve.length >= 3) {
|
|
3746
|
+
lines.push(` ${thinDivider}`);
|
|
3747
|
+
lines.push(" EQUITY CURVE");
|
|
3748
|
+
lines.push(` ${thinDivider}`);
|
|
3749
|
+
lines.push("");
|
|
3750
|
+
lines.push(renderAsciiChart(result.equityCurve));
|
|
3751
|
+
lines.push("");
|
|
3752
|
+
}
|
|
3753
|
+
lines.push(` ${divider}`);
|
|
3754
|
+
lines.push(" DISCLAIMER: Simulated results do not represent actual");
|
|
3755
|
+
lines.push(" trading. Past performance does not guarantee future");
|
|
3756
|
+
lines.push(" results. LLM strategies are non-deterministic. Real");
|
|
3757
|
+
lines.push(" trading may differ due to slippage, liquidity, MEV,");
|
|
3758
|
+
lines.push(" and execution timing.");
|
|
3759
|
+
lines.push(` ${divider}`);
|
|
3760
|
+
lines.push("");
|
|
3761
|
+
return lines.join("\n");
|
|
3762
|
+
}
|
|
3763
|
+
function formatDuration(ms) {
|
|
3764
|
+
const seconds = Math.floor(ms / 1e3);
|
|
3765
|
+
if (seconds < 60) return `${seconds}s`;
|
|
3766
|
+
const minutes = Math.floor(seconds / 60);
|
|
3767
|
+
if (minutes < 60) return `${minutes}m`;
|
|
3768
|
+
const hours = Math.floor(minutes / 60);
|
|
3769
|
+
const mins = minutes % 60;
|
|
3770
|
+
if (hours < 24) return `${hours}h ${mins}m`;
|
|
3771
|
+
const days = Math.floor(hours / 24);
|
|
3772
|
+
return `${days}d ${hours % 24}h`;
|
|
3773
|
+
}
|
|
3774
|
+
function renderAsciiChart(curve) {
|
|
3775
|
+
const width = 48;
|
|
3776
|
+
const height = 8;
|
|
3777
|
+
const step = Math.max(1, Math.floor(curve.length / width));
|
|
3778
|
+
const sampled = curve.filter((_, i) => i % step === 0).slice(0, width);
|
|
3779
|
+
const values = sampled.map((p) => p.value);
|
|
3780
|
+
const min = Math.min(...values);
|
|
3781
|
+
const max = Math.max(...values);
|
|
3782
|
+
const range = max - min || 1;
|
|
3783
|
+
const lines = [];
|
|
3784
|
+
for (let row = height - 1; row >= 0; row--) {
|
|
3785
|
+
const threshold = min + range * row / (height - 1);
|
|
3786
|
+
let line = " \u2502";
|
|
3787
|
+
for (const val of values) {
|
|
3788
|
+
const normalized = (val - min) / range * (height - 1);
|
|
3789
|
+
if (Math.round(normalized) === row) {
|
|
3790
|
+
line += "\u2588";
|
|
3791
|
+
} else if (Math.round(normalized) > row) {
|
|
3792
|
+
line += "\u2591";
|
|
3793
|
+
} else {
|
|
3794
|
+
line += " ";
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
if (row === height - 1) {
|
|
3798
|
+
line += ` $${max.toFixed(0)}`;
|
|
3799
|
+
} else if (row === 0) {
|
|
3800
|
+
line += ` $${min.toFixed(0)}`;
|
|
3801
|
+
}
|
|
3802
|
+
lines.push(line);
|
|
3803
|
+
}
|
|
3804
|
+
lines.push(" \u2514" + "\u2500".repeat(values.length));
|
|
3805
|
+
return lines.join("\n");
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3147
3808
|
// src/perp/client.ts
|
|
3148
3809
|
var HyperliquidClient = class {
|
|
3149
3810
|
apiUrl;
|
|
@@ -4384,91 +5045,1955 @@ var CORE_DEPOSIT_WALLET_ABI = (0, import_viem6.parseAbi)([
|
|
|
4384
5045
|
"function deposit(uint256 amount, uint32 destinationDex) external"
|
|
4385
5046
|
]);
|
|
4386
5047
|
|
|
4387
|
-
// src/
|
|
4388
|
-
var
|
|
4389
|
-
|
|
4390
|
-
|
|
5048
|
+
// src/prediction/types.ts
|
|
5049
|
+
var DEFAULT_PREDICTION_CONFIG = {
|
|
5050
|
+
clobApiUrl: "https://clob.polymarket.com",
|
|
5051
|
+
gammaApiUrl: "https://gamma-api.polymarket.com",
|
|
5052
|
+
polygonRpcUrl: "https://polygon-rpc.com",
|
|
5053
|
+
maxNotionalUSD: 1e3,
|
|
5054
|
+
maxTotalExposureUSD: 5e3,
|
|
5055
|
+
polygonAllocationUSD: 100,
|
|
5056
|
+
feeBridgeThresholdUSD: 10
|
|
5057
|
+
};
|
|
5058
|
+
var POLYGON_CHAIN_ID = 137;
|
|
5059
|
+
var POLYMARKET_SIGNATURE_TYPE_EOA = 0;
|
|
5060
|
+
var POLYMARKET_SIGNATURE_TYPE_GNOSIS_SAFE = 1;
|
|
5061
|
+
var POLYGON_USDC_ADDRESS = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
|
|
5062
|
+
var POLYGON_TOKEN_MESSENGER_V2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
|
|
5063
|
+
var POLYGON_MESSAGE_TRANSMITTER_V2 = "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64";
|
|
5064
|
+
var CCTP_DOMAIN_BASE = 6;
|
|
5065
|
+
var CCTP_DOMAIN_POLYGON = 7;
|
|
5066
|
+
var PREDICTION_INSTRUMENT_PREFIX = "POLY:";
|
|
5067
|
+
|
|
5068
|
+
// src/prediction/client.ts
|
|
5069
|
+
var import_clob_client = require("@polymarket/clob-client");
|
|
5070
|
+
var import_ethers = require("ethers");
|
|
5071
|
+
var PolymarketClient = class {
|
|
4391
5072
|
config;
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
lastPrices = {};
|
|
4411
|
-
processAlive = true;
|
|
4412
|
-
riskUniverse = 0;
|
|
4413
|
-
allowedTokens = /* @__PURE__ */ new Set();
|
|
4414
|
-
strategyContext;
|
|
4415
|
-
positionTracker;
|
|
4416
|
-
// Perp trading components (null if perp not enabled)
|
|
4417
|
-
perpClient = null;
|
|
4418
|
-
perpSigner = null;
|
|
4419
|
-
perpOrders = null;
|
|
4420
|
-
perpPositions = null;
|
|
4421
|
-
perpWebSocket = null;
|
|
4422
|
-
perpRecorder = null;
|
|
4423
|
-
perpOnboarding = null;
|
|
4424
|
-
perpStrategy = null;
|
|
4425
|
-
// Two-layer perp control:
|
|
4426
|
-
// perpConnected = Hyperliquid infrastructure is initialized (WS, signer, recorder ready)
|
|
4427
|
-
// perpTradingActive = Dedicated perp trading cycle is mandated to run
|
|
4428
|
-
// When perpConnected && !perpTradingActive: agent's strategy can optionally return perp signals
|
|
4429
|
-
// When perpConnected && perpTradingActive: dedicated runPerpCycle() runs every interval
|
|
4430
|
-
perpConnected = false;
|
|
4431
|
-
perpTradingActive = false;
|
|
4432
|
-
// Cached perp account data for synchronous heartbeat inclusion (refreshed async)
|
|
4433
|
-
cachedPerpEquity = 0;
|
|
4434
|
-
cachedPerpUnrealizedPnl = 0;
|
|
4435
|
-
cachedPerpMarginUsed = 0;
|
|
4436
|
-
cachedPerpLeverage = 0;
|
|
4437
|
-
cachedPerpOpenPositions = 0;
|
|
4438
|
-
constructor(config) {
|
|
4439
|
-
this.config = config;
|
|
5073
|
+
clobClient = null;
|
|
5074
|
+
apiCreds = null;
|
|
5075
|
+
signer;
|
|
5076
|
+
walletAddress;
|
|
5077
|
+
/** Vault mode: when true, orders use maker=vaultAddress with POLY_GNOSIS_SAFE signature type */
|
|
5078
|
+
vaultMode;
|
|
5079
|
+
/** Vault contract address (only set in vault mode) */
|
|
5080
|
+
vaultAddress;
|
|
5081
|
+
/** Cache for Gamma API market data (conditionId -> market) */
|
|
5082
|
+
marketCache = /* @__PURE__ */ new Map();
|
|
5083
|
+
CACHE_TTL_MS = 6e4;
|
|
5084
|
+
// 60 seconds
|
|
5085
|
+
constructor(privateKey, config, vaultOpts) {
|
|
5086
|
+
this.config = { ...DEFAULT_PREDICTION_CONFIG, ...config };
|
|
5087
|
+
this.signer = new import_ethers.Wallet(privateKey);
|
|
5088
|
+
this.walletAddress = this.signer.address;
|
|
5089
|
+
this.vaultMode = vaultOpts?.vaultMode ?? false;
|
|
5090
|
+
this.vaultAddress = vaultOpts?.vaultAddress;
|
|
4440
5091
|
}
|
|
5092
|
+
// ============================================================
|
|
5093
|
+
// INITIALIZATION
|
|
5094
|
+
// ============================================================
|
|
4441
5095
|
/**
|
|
4442
|
-
* Initialize the
|
|
5096
|
+
* Initialize the CLOB client with L2 API credentials.
|
|
5097
|
+
* Must be called once before placing orders.
|
|
4443
5098
|
*/
|
|
4444
5099
|
async initialize() {
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
const
|
|
4452
|
-
|
|
4453
|
-
|
|
5100
|
+
const initClient = new import_clob_client.ClobClient(
|
|
5101
|
+
this.config.clobApiUrl,
|
|
5102
|
+
POLYGON_CHAIN_ID,
|
|
5103
|
+
this.signer
|
|
5104
|
+
);
|
|
5105
|
+
this.apiCreds = await initClient.createOrDeriveApiKey();
|
|
5106
|
+
const signatureType = this.vaultMode ? POLYMARKET_SIGNATURE_TYPE_GNOSIS_SAFE : POLYMARKET_SIGNATURE_TYPE_EOA;
|
|
5107
|
+
this.clobClient = new import_clob_client.ClobClient(
|
|
5108
|
+
this.config.clobApiUrl,
|
|
5109
|
+
POLYGON_CHAIN_ID,
|
|
5110
|
+
this.signer,
|
|
5111
|
+
this.apiCreds,
|
|
5112
|
+
signatureType
|
|
5113
|
+
);
|
|
5114
|
+
if (this.vaultMode) {
|
|
5115
|
+
console.log(`Polymarket CLOB initialized in VAULT MODE \u2014 maker: ${this.vaultAddress}, signer: ${this.walletAddress}`);
|
|
5116
|
+
} else {
|
|
5117
|
+
console.log(`Polymarket CLOB initialized for ${this.walletAddress}`);
|
|
4454
5118
|
}
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
5119
|
+
}
|
|
5120
|
+
/**
|
|
5121
|
+
* Check if the client is initialized with CLOB credentials.
|
|
5122
|
+
*/
|
|
5123
|
+
get isInitialized() {
|
|
5124
|
+
return this.clobClient !== null && this.apiCreds !== null;
|
|
5125
|
+
}
|
|
5126
|
+
// ============================================================
|
|
5127
|
+
// CLOB API — ORDER BOOK & PRICES
|
|
5128
|
+
// ============================================================
|
|
5129
|
+
/**
|
|
5130
|
+
* Get the order book for a specific outcome token.
|
|
5131
|
+
*/
|
|
5132
|
+
async getOrderBook(tokenId) {
|
|
5133
|
+
this.ensureInitialized();
|
|
5134
|
+
const book = await this.clobClient.getOrderBook(tokenId);
|
|
5135
|
+
return {
|
|
5136
|
+
bids: (book.bids || []).map((b) => ({ price: parseFloat(b.price), size: parseFloat(b.size) })),
|
|
5137
|
+
asks: (book.asks || []).map((a) => ({ price: parseFloat(a.price), size: parseFloat(a.size) }))
|
|
4469
5138
|
};
|
|
4470
|
-
|
|
4471
|
-
|
|
5139
|
+
}
|
|
5140
|
+
/**
|
|
5141
|
+
* Get the midpoint price for an outcome token.
|
|
5142
|
+
*/
|
|
5143
|
+
async getMidpointPrice(tokenId) {
|
|
5144
|
+
const book = await this.getOrderBook(tokenId);
|
|
5145
|
+
if (book.bids.length === 0 || book.asks.length === 0) return 0;
|
|
5146
|
+
return (book.bids[0].price + book.asks[0].price) / 2;
|
|
5147
|
+
}
|
|
5148
|
+
/**
|
|
5149
|
+
* Get the last trade price for an outcome token.
|
|
5150
|
+
*/
|
|
5151
|
+
async getLastTradePrice(tokenId) {
|
|
5152
|
+
this.ensureInitialized();
|
|
5153
|
+
const resp = await this.clobClient.getLastTradePrice(tokenId);
|
|
5154
|
+
return parseFloat(resp?.price || "0");
|
|
5155
|
+
}
|
|
5156
|
+
// ============================================================
|
|
5157
|
+
// CLOB API — ORDERS
|
|
5158
|
+
// ============================================================
|
|
5159
|
+
/**
|
|
5160
|
+
* Place a limit order on the CLOB.
|
|
5161
|
+
*/
|
|
5162
|
+
async placeLimitOrder(params) {
|
|
5163
|
+
this.ensureInitialized();
|
|
5164
|
+
const order = await this.clobClient.createAndPostOrder({
|
|
5165
|
+
tokenID: params.tokenId,
|
|
5166
|
+
price: params.price,
|
|
5167
|
+
side: params.side,
|
|
5168
|
+
size: params.size
|
|
5169
|
+
});
|
|
5170
|
+
return {
|
|
5171
|
+
orderId: order?.orderID || "",
|
|
5172
|
+
success: !!order?.orderID
|
|
5173
|
+
};
|
|
5174
|
+
}
|
|
5175
|
+
/**
|
|
5176
|
+
* Place a market order (aggressive limit at best available price).
|
|
5177
|
+
*/
|
|
5178
|
+
async placeMarketOrder(params) {
|
|
5179
|
+
this.ensureInitialized();
|
|
5180
|
+
const order = await this.clobClient.createMarketOrder({
|
|
5181
|
+
tokenID: params.tokenId,
|
|
5182
|
+
amount: params.amount,
|
|
5183
|
+
side: params.side
|
|
5184
|
+
});
|
|
5185
|
+
const result = await this.clobClient.postOrder(order);
|
|
5186
|
+
return {
|
|
5187
|
+
orderId: result?.orderID || "",
|
|
5188
|
+
success: !!result?.orderID
|
|
5189
|
+
};
|
|
5190
|
+
}
|
|
5191
|
+
/**
|
|
5192
|
+
* Cancel an open order by ID.
|
|
5193
|
+
*/
|
|
5194
|
+
async cancelOrder(orderId) {
|
|
5195
|
+
this.ensureInitialized();
|
|
5196
|
+
try {
|
|
5197
|
+
await this.clobClient.cancelOrder({ orderID: orderId });
|
|
5198
|
+
return true;
|
|
5199
|
+
} catch {
|
|
5200
|
+
return false;
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5203
|
+
/**
|
|
5204
|
+
* Cancel all open orders.
|
|
5205
|
+
*/
|
|
5206
|
+
async cancelAllOrders() {
|
|
5207
|
+
this.ensureInitialized();
|
|
5208
|
+
try {
|
|
5209
|
+
await this.clobClient.cancelAll();
|
|
5210
|
+
return true;
|
|
5211
|
+
} catch {
|
|
5212
|
+
return false;
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
/**
|
|
5216
|
+
* Get open orders for the agent's wallet.
|
|
5217
|
+
*/
|
|
5218
|
+
async getOpenOrders() {
|
|
5219
|
+
this.ensureInitialized();
|
|
5220
|
+
return this.clobClient.getOpenOrders();
|
|
5221
|
+
}
|
|
5222
|
+
/**
|
|
5223
|
+
* Get trade history (fills) for the agent's wallet.
|
|
5224
|
+
*/
|
|
5225
|
+
async getTradeHistory() {
|
|
5226
|
+
this.ensureInitialized();
|
|
5227
|
+
const trades = await this.clobClient.getTrades();
|
|
5228
|
+
return (trades || []).map((t) => this.parseRawFill(t));
|
|
5229
|
+
}
|
|
5230
|
+
// ============================================================
|
|
5231
|
+
// GAMMA API — MARKET DISCOVERY (public, no auth)
|
|
5232
|
+
// ============================================================
|
|
5233
|
+
/**
|
|
5234
|
+
* Get active prediction markets from Gamma API.
|
|
5235
|
+
*/
|
|
5236
|
+
async getMarkets(params) {
|
|
5237
|
+
const query = new URLSearchParams();
|
|
5238
|
+
if (params?.limit) query.set("limit", String(params.limit));
|
|
5239
|
+
if (params?.offset) query.set("offset", String(params.offset));
|
|
5240
|
+
if (params?.active !== void 0) query.set("active", String(params.active));
|
|
5241
|
+
if (params?.category) query.set("tag", params.category);
|
|
5242
|
+
const url = `${this.config.gammaApiUrl}/markets?${query.toString()}`;
|
|
5243
|
+
const resp = await fetch(url);
|
|
5244
|
+
if (!resp.ok) throw new Error(`Gamma API error: ${resp.status} ${await resp.text()}`);
|
|
5245
|
+
const raw = await resp.json();
|
|
5246
|
+
return (raw || []).map((m) => this.parseGammaMarket(m));
|
|
5247
|
+
}
|
|
5248
|
+
/**
|
|
5249
|
+
* Get a single market by condition ID.
|
|
5250
|
+
*/
|
|
5251
|
+
async getMarketByConditionId(conditionId) {
|
|
5252
|
+
const cached = this.marketCache.get(conditionId);
|
|
5253
|
+
if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
|
|
5254
|
+
return cached.market;
|
|
5255
|
+
}
|
|
5256
|
+
const url = `${this.config.gammaApiUrl}/markets?condition_id=${conditionId}`;
|
|
5257
|
+
const resp = await fetch(url);
|
|
5258
|
+
if (!resp.ok) return null;
|
|
5259
|
+
const raw = await resp.json();
|
|
5260
|
+
if (!raw || raw.length === 0) return null;
|
|
5261
|
+
const market = this.parseGammaMarket(raw[0]);
|
|
5262
|
+
this.marketCache.set(conditionId, { market, cachedAt: Date.now() });
|
|
5263
|
+
return market;
|
|
5264
|
+
}
|
|
5265
|
+
/**
|
|
5266
|
+
* Search markets by query string.
|
|
5267
|
+
*/
|
|
5268
|
+
async searchMarkets(query, limit = 20) {
|
|
5269
|
+
const url = `${this.config.gammaApiUrl}/markets?_q=${encodeURIComponent(query)}&limit=${limit}&active=true`;
|
|
5270
|
+
const resp = await fetch(url);
|
|
5271
|
+
if (!resp.ok) return [];
|
|
5272
|
+
const raw = await resp.json();
|
|
5273
|
+
return (raw || []).map((m) => this.parseGammaMarket(m));
|
|
5274
|
+
}
|
|
5275
|
+
/**
|
|
5276
|
+
* Get trending markets sorted by volume.
|
|
5277
|
+
*/
|
|
5278
|
+
async getTrendingMarkets(limit = 10) {
|
|
5279
|
+
const url = `${this.config.gammaApiUrl}/markets?active=true&limit=${limit}&order=volume24hr&ascending=false`;
|
|
5280
|
+
const resp = await fetch(url);
|
|
5281
|
+
if (!resp.ok) return [];
|
|
5282
|
+
const raw = await resp.json();
|
|
5283
|
+
return (raw || []).map((m) => this.parseGammaMarket(m));
|
|
5284
|
+
}
|
|
5285
|
+
// ============================================================
|
|
5286
|
+
// WALLET ADDRESS
|
|
5287
|
+
// ============================================================
|
|
5288
|
+
getWalletAddress() {
|
|
5289
|
+
return this.walletAddress;
|
|
5290
|
+
}
|
|
5291
|
+
/**
|
|
5292
|
+
* Get the effective maker address for CLOB orders.
|
|
5293
|
+
* In vault mode, this is the vault contract address.
|
|
5294
|
+
* In normal mode, this is the agent's EOA address.
|
|
5295
|
+
*/
|
|
5296
|
+
getMakerAddress() {
|
|
5297
|
+
return this.vaultMode && this.vaultAddress ? this.vaultAddress : this.walletAddress;
|
|
5298
|
+
}
|
|
5299
|
+
/**
|
|
5300
|
+
* Whether the client is operating in vault mode.
|
|
5301
|
+
*/
|
|
5302
|
+
get isVaultMode() {
|
|
5303
|
+
return this.vaultMode;
|
|
5304
|
+
}
|
|
5305
|
+
/**
|
|
5306
|
+
* Get the vault address (null if not in vault mode).
|
|
5307
|
+
*/
|
|
5308
|
+
getVaultAddress() {
|
|
5309
|
+
return this.vaultAddress;
|
|
5310
|
+
}
|
|
5311
|
+
// ============================================================
|
|
5312
|
+
// PRIVATE HELPERS
|
|
5313
|
+
// ============================================================
|
|
5314
|
+
ensureInitialized() {
|
|
5315
|
+
if (!this.clobClient) {
|
|
5316
|
+
throw new Error("PolymarketClient not initialized. Call initialize() first.");
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
parseGammaMarket(raw) {
|
|
5320
|
+
const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ["Yes", "No"];
|
|
5321
|
+
const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
|
|
5322
|
+
const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
|
|
5323
|
+
return {
|
|
5324
|
+
conditionId: raw.conditionId || raw.condition_id || "",
|
|
5325
|
+
question: raw.question || "",
|
|
5326
|
+
description: raw.description || "",
|
|
5327
|
+
category: raw.groupItemTitle || raw.category || "Other",
|
|
5328
|
+
outcomes,
|
|
5329
|
+
outcomeTokenIds,
|
|
5330
|
+
outcomePrices: outcomePrices.map((p) => parseFloat(p) || 0),
|
|
5331
|
+
volume24h: parseFloat(raw.volume24hr || raw.volume24h || "0"),
|
|
5332
|
+
liquidity: parseFloat(raw.liquidity || "0"),
|
|
5333
|
+
endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1e3 : 0,
|
|
5334
|
+
active: raw.active !== false && raw.closed !== true,
|
|
5335
|
+
resolved: raw.resolved === true,
|
|
5336
|
+
winningOutcome: raw.winningOutcome,
|
|
5337
|
+
resolutionSource: raw.resolutionSource || void 0,
|
|
5338
|
+
uniqueTraders: raw.uniqueTraders || void 0
|
|
5339
|
+
};
|
|
5340
|
+
}
|
|
5341
|
+
parseRawFill(raw) {
|
|
5342
|
+
const tokenId = raw.asset_id || void 0;
|
|
5343
|
+
const marketConditionId = raw.market || raw.asset_id || "";
|
|
5344
|
+
return {
|
|
5345
|
+
orderId: raw.orderId || raw.order_id || "",
|
|
5346
|
+
tradeId: raw.id || raw.tradeId || "",
|
|
5347
|
+
marketConditionId,
|
|
5348
|
+
outcomeIndex: raw.outcome_index ?? (raw.side === "BUY" ? 0 : 1),
|
|
5349
|
+
side: raw.side === "BUY" || raw.side === "buy" ? "BUY" : "SELL",
|
|
5350
|
+
price: String(raw.price || "0"),
|
|
5351
|
+
size: String(raw.size || "0"),
|
|
5352
|
+
fee: String(raw.fee || "0"),
|
|
5353
|
+
timestamp: raw.timestamp || raw.created_at ? new Date(raw.created_at).getTime() : Date.now(),
|
|
5354
|
+
isMaker: raw.maker_order || raw.is_maker || false,
|
|
5355
|
+
tokenId
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
};
|
|
5359
|
+
|
|
5360
|
+
// src/prediction/signer.ts
|
|
5361
|
+
var import_viem7 = require("viem");
|
|
5362
|
+
function tradeIdToBytes32(tradeId) {
|
|
5363
|
+
if (tradeId.startsWith("0x") && tradeId.length === 66) {
|
|
5364
|
+
return tradeId;
|
|
5365
|
+
}
|
|
5366
|
+
return (0, import_viem7.keccak256)((0, import_viem7.encodePacked)(["string"], [tradeId]));
|
|
5367
|
+
}
|
|
5368
|
+
function orderIdToBytes32(orderId) {
|
|
5369
|
+
return (0, import_viem7.keccak256)((0, import_viem7.encodePacked)(["string"], [`poly-order:${orderId}`]));
|
|
5370
|
+
}
|
|
5371
|
+
function encodePredictionInstrument(conditionId, outcomeIndex) {
|
|
5372
|
+
return `${PREDICTION_INSTRUMENT_PREFIX}${conditionId}:${outcomeIndex}`;
|
|
5373
|
+
}
|
|
5374
|
+
function decodePredictionInstrument(instrument) {
|
|
5375
|
+
if (!instrument.startsWith(PREDICTION_INSTRUMENT_PREFIX)) return null;
|
|
5376
|
+
const payload = instrument.slice(PREDICTION_INSTRUMENT_PREFIX.length);
|
|
5377
|
+
const lastColon = payload.lastIndexOf(":");
|
|
5378
|
+
if (lastColon === -1) return null;
|
|
5379
|
+
const conditionId = payload.slice(0, lastColon);
|
|
5380
|
+
const outcomeIndex = parseInt(payload.slice(lastColon + 1), 10);
|
|
5381
|
+
if (isNaN(outcomeIndex)) return null;
|
|
5382
|
+
return { conditionId, outcomeIndex };
|
|
5383
|
+
}
|
|
5384
|
+
function calculatePredictionFee(notionalUSD) {
|
|
5385
|
+
return notionalUSD * 20n / 10000n;
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5388
|
+
// src/prediction/order-manager.ts
|
|
5389
|
+
var PredictionOrderManager = class {
|
|
5390
|
+
client;
|
|
5391
|
+
config;
|
|
5392
|
+
/** Recent fills tracked for recording */
|
|
5393
|
+
recentFills = [];
|
|
5394
|
+
MAX_RECENT_FILLS = 100;
|
|
5395
|
+
constructor(client, config) {
|
|
5396
|
+
this.client = client;
|
|
5397
|
+
this.config = config;
|
|
5398
|
+
}
|
|
5399
|
+
// ============================================================
|
|
5400
|
+
// ORDER PLACEMENT
|
|
5401
|
+
// ============================================================
|
|
5402
|
+
/**
|
|
5403
|
+
* Execute a prediction trade signal.
|
|
5404
|
+
* Routes to limit or market order based on signal.orderType.
|
|
5405
|
+
*/
|
|
5406
|
+
async executeSignal(signal) {
|
|
5407
|
+
try {
|
|
5408
|
+
const violation = this.checkRiskLimits(signal);
|
|
5409
|
+
if (violation) {
|
|
5410
|
+
return { success: false, status: "error", error: violation };
|
|
5411
|
+
}
|
|
5412
|
+
const market = await this.client.getMarketByConditionId(signal.marketConditionId);
|
|
5413
|
+
if (!market) {
|
|
5414
|
+
return { success: false, status: "error", error: `Market not found: ${signal.marketConditionId}` };
|
|
5415
|
+
}
|
|
5416
|
+
if (!market.active) {
|
|
5417
|
+
return { success: false, status: "error", error: `Market is closed: ${signal.marketQuestion}` };
|
|
5418
|
+
}
|
|
5419
|
+
const tokenId = market.outcomeTokenIds[signal.outcomeIndex];
|
|
5420
|
+
if (!tokenId) {
|
|
5421
|
+
return { success: false, status: "error", error: `Invalid outcome index ${signal.outcomeIndex} for market` };
|
|
5422
|
+
}
|
|
5423
|
+
const isBuy = signal.action === "buy_yes" || signal.action === "buy_no";
|
|
5424
|
+
const side = isBuy ? "BUY" : "SELL";
|
|
5425
|
+
let result;
|
|
5426
|
+
if (signal.orderType === "market") {
|
|
5427
|
+
result = await this.client.placeMarketOrder({
|
|
5428
|
+
tokenId,
|
|
5429
|
+
amount: signal.amount,
|
|
5430
|
+
side
|
|
5431
|
+
});
|
|
5432
|
+
} else {
|
|
5433
|
+
result = await this.client.placeLimitOrder({
|
|
5434
|
+
tokenId,
|
|
5435
|
+
price: signal.limitPrice,
|
|
5436
|
+
size: signal.amount,
|
|
5437
|
+
side
|
|
5438
|
+
});
|
|
5439
|
+
}
|
|
5440
|
+
if (result.success) {
|
|
5441
|
+
console.log(
|
|
5442
|
+
`Prediction order placed: ${signal.action} ${signal.amount} @ $${signal.limitPrice} \u2014 "${signal.marketQuestion}" \u2014 order: ${result.orderId}`
|
|
5443
|
+
);
|
|
5444
|
+
return {
|
|
5445
|
+
success: true,
|
|
5446
|
+
orderId: result.orderId,
|
|
5447
|
+
status: signal.orderType === "market" ? "filled" : "resting"
|
|
5448
|
+
};
|
|
5449
|
+
}
|
|
5450
|
+
return { success: false, status: "error", error: "Order placement failed" };
|
|
5451
|
+
} catch (error) {
|
|
5452
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5453
|
+
console.error(`Prediction order failed for "${signal.marketQuestion}":`, message);
|
|
5454
|
+
return { success: false, status: "error", error: message };
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
/**
|
|
5458
|
+
* Cancel an open order by ID.
|
|
5459
|
+
*/
|
|
5460
|
+
async cancelOrder(orderId) {
|
|
5461
|
+
return this.client.cancelOrder(orderId);
|
|
5462
|
+
}
|
|
5463
|
+
/**
|
|
5464
|
+
* Cancel all open orders.
|
|
5465
|
+
*/
|
|
5466
|
+
async cancelAllOrders() {
|
|
5467
|
+
return this.client.cancelAllOrders();
|
|
5468
|
+
}
|
|
5469
|
+
// ============================================================
|
|
5470
|
+
// FILL TRACKING
|
|
5471
|
+
// ============================================================
|
|
5472
|
+
/**
|
|
5473
|
+
* Poll for new fills since last check.
|
|
5474
|
+
* Returns fills that haven't been seen before.
|
|
5475
|
+
*/
|
|
5476
|
+
async pollNewFills() {
|
|
5477
|
+
try {
|
|
5478
|
+
const allFills = await this.client.getTradeHistory();
|
|
5479
|
+
const seenIds = new Set(this.recentFills.map((f) => f.tradeId));
|
|
5480
|
+
const newFills = allFills.filter((f) => !seenIds.has(f.tradeId));
|
|
5481
|
+
for (const fill of newFills) {
|
|
5482
|
+
this.recentFills.push(fill);
|
|
5483
|
+
if (this.recentFills.length > this.MAX_RECENT_FILLS) {
|
|
5484
|
+
this.recentFills.shift();
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
return newFills;
|
|
5488
|
+
} catch (error) {
|
|
5489
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5490
|
+
console.error("Failed to poll prediction fills:", message);
|
|
5491
|
+
return [];
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
/**
|
|
5495
|
+
* Get recent fills (from local cache).
|
|
5496
|
+
*/
|
|
5497
|
+
getRecentFills() {
|
|
5498
|
+
return [...this.recentFills];
|
|
5499
|
+
}
|
|
5500
|
+
// ============================================================
|
|
5501
|
+
// RISK CHECKS
|
|
5502
|
+
// ============================================================
|
|
5503
|
+
/**
|
|
5504
|
+
* Check if a signal violates risk limits.
|
|
5505
|
+
* Returns error string if violated, null if OK.
|
|
5506
|
+
*/
|
|
5507
|
+
checkRiskLimits(signal) {
|
|
5508
|
+
if (signal.amount > this.config.maxNotionalUSD) {
|
|
5509
|
+
return `Trade amount $${signal.amount} exceeds max notional $${this.config.maxNotionalUSD}`;
|
|
5510
|
+
}
|
|
5511
|
+
if (signal.limitPrice <= 0 || signal.limitPrice >= 1) {
|
|
5512
|
+
return `Limit price ${signal.limitPrice} out of bounds (must be 0.01-0.99)`;
|
|
5513
|
+
}
|
|
5514
|
+
if (signal.confidence < 0.3) {
|
|
5515
|
+
return `Confidence ${signal.confidence} below minimum threshold (0.3)`;
|
|
5516
|
+
}
|
|
5517
|
+
if (this.config.allowedCategories && this.config.allowedCategories.length > 0) {
|
|
5518
|
+
}
|
|
5519
|
+
return null;
|
|
5520
|
+
}
|
|
5521
|
+
};
|
|
5522
|
+
|
|
5523
|
+
// src/prediction/position-manager.ts
|
|
5524
|
+
var PredictionPositionManager = class {
|
|
5525
|
+
client;
|
|
5526
|
+
config;
|
|
5527
|
+
/** Local position tracking (conditionId:outcomeIndex -> position data) */
|
|
5528
|
+
positions = /* @__PURE__ */ new Map();
|
|
5529
|
+
/** Cache TTL */
|
|
5530
|
+
lastPriceRefresh = 0;
|
|
5531
|
+
PRICE_REFRESH_MS = 1e4;
|
|
5532
|
+
// 10 seconds
|
|
5533
|
+
constructor(client, config) {
|
|
5534
|
+
this.client = client;
|
|
5535
|
+
this.config = config;
|
|
5536
|
+
}
|
|
5537
|
+
// ============================================================
|
|
5538
|
+
// POSITION QUERIES
|
|
5539
|
+
// ============================================================
|
|
5540
|
+
/**
|
|
5541
|
+
* Get all open prediction positions with current prices.
|
|
5542
|
+
*/
|
|
5543
|
+
async getPositions(forceRefresh = false) {
|
|
5544
|
+
if (forceRefresh || Date.now() - this.lastPriceRefresh > this.PRICE_REFRESH_MS) {
|
|
5545
|
+
await this.refreshPrices();
|
|
5546
|
+
}
|
|
5547
|
+
return Array.from(this.positions.values()).filter((p) => p.balance > 0).map((p) => this.toExternalPosition(p));
|
|
5548
|
+
}
|
|
5549
|
+
/**
|
|
5550
|
+
* Get a specific position.
|
|
5551
|
+
*/
|
|
5552
|
+
async getPosition(conditionId, outcomeIndex) {
|
|
5553
|
+
const key = `${conditionId}:${outcomeIndex}`;
|
|
5554
|
+
const pos = this.positions.get(key);
|
|
5555
|
+
if (!pos || pos.balance <= 0) return null;
|
|
5556
|
+
return this.toExternalPosition(pos);
|
|
5557
|
+
}
|
|
5558
|
+
/**
|
|
5559
|
+
* Get account summary (balances, exposure, PnL).
|
|
5560
|
+
*/
|
|
5561
|
+
async getAccountSummary() {
|
|
5562
|
+
const positions = await this.getPositions();
|
|
5563
|
+
const totalExposure = positions.reduce((sum, p) => sum + p.costBasis, 0);
|
|
5564
|
+
const totalUnrealizedPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
|
|
5565
|
+
const openMarkets = new Set(positions.map((p) => p.marketConditionId));
|
|
5566
|
+
return {
|
|
5567
|
+
polygonUSDC: 0,
|
|
5568
|
+
// Filled in by runtime from on-chain balance
|
|
5569
|
+
baseUSDC: 0,
|
|
5570
|
+
// Filled in by runtime from on-chain balance
|
|
5571
|
+
totalExposure,
|
|
5572
|
+
totalUnrealizedPnl,
|
|
5573
|
+
openMarketCount: openMarkets.size,
|
|
5574
|
+
openPositionCount: positions.length,
|
|
5575
|
+
pendingFees: 0
|
|
5576
|
+
// Filled in by recorder
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5579
|
+
/**
|
|
5580
|
+
* Get total exposure across all positions.
|
|
5581
|
+
*/
|
|
5582
|
+
getTotalExposure() {
|
|
5583
|
+
let total = 0;
|
|
5584
|
+
for (const pos of this.positions.values()) {
|
|
5585
|
+
if (pos.balance > 0) total += pos.totalCostBasis;
|
|
5586
|
+
}
|
|
5587
|
+
return total;
|
|
5588
|
+
}
|
|
5589
|
+
/**
|
|
5590
|
+
* Get total unrealized PnL.
|
|
5591
|
+
*/
|
|
5592
|
+
getTotalUnrealizedPnl() {
|
|
5593
|
+
let total = 0;
|
|
5594
|
+
for (const pos of this.positions.values()) {
|
|
5595
|
+
if (pos.balance > 0) {
|
|
5596
|
+
total += (pos.currentPrice - pos.averageEntryPrice) * pos.balance;
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
return total;
|
|
5600
|
+
}
|
|
5601
|
+
// ============================================================
|
|
5602
|
+
// FILL PROCESSING
|
|
5603
|
+
// ============================================================
|
|
5604
|
+
/**
|
|
5605
|
+
* Process a fill and update position tracking.
|
|
5606
|
+
* Called when a new fill is detected by the order manager.
|
|
5607
|
+
*/
|
|
5608
|
+
processFill(fill) {
|
|
5609
|
+
const key = `${fill.marketConditionId}:${fill.outcomeIndex}`;
|
|
5610
|
+
let pos = this.positions.get(key);
|
|
5611
|
+
const price = parseFloat(fill.price);
|
|
5612
|
+
const size = parseFloat(fill.size);
|
|
5613
|
+
if (!pos) {
|
|
5614
|
+
pos = {
|
|
5615
|
+
marketConditionId: fill.marketConditionId,
|
|
5616
|
+
outcomeIndex: fill.outcomeIndex,
|
|
5617
|
+
marketQuestion: fill.marketQuestion || "",
|
|
5618
|
+
tokenId: fill.tokenId || "",
|
|
5619
|
+
balance: 0,
|
|
5620
|
+
totalBought: 0,
|
|
5621
|
+
totalSold: 0,
|
|
5622
|
+
totalCostBasis: 0,
|
|
5623
|
+
totalProceeds: 0,
|
|
5624
|
+
averageEntryPrice: 0,
|
|
5625
|
+
currentPrice: price,
|
|
5626
|
+
category: void 0,
|
|
5627
|
+
endDate: void 0
|
|
5628
|
+
};
|
|
5629
|
+
this.positions.set(key, pos);
|
|
5630
|
+
} else if (!pos.tokenId && fill.tokenId) {
|
|
5631
|
+
pos.tokenId = fill.tokenId;
|
|
5632
|
+
}
|
|
5633
|
+
if (fill.side === "BUY") {
|
|
5634
|
+
const oldCost = pos.averageEntryPrice * pos.balance;
|
|
5635
|
+
const newCost = price * size;
|
|
5636
|
+
pos.balance += size;
|
|
5637
|
+
pos.totalBought += size;
|
|
5638
|
+
pos.totalCostBasis += newCost;
|
|
5639
|
+
pos.averageEntryPrice = pos.balance > 0 ? (oldCost + newCost) / pos.balance : 0;
|
|
5640
|
+
} else {
|
|
5641
|
+
pos.balance -= size;
|
|
5642
|
+
pos.totalSold += size;
|
|
5643
|
+
pos.totalProceeds += price * size;
|
|
5644
|
+
if (pos.balance < 0) pos.balance = 0;
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5648
|
+
* Process multiple fills (batch update).
|
|
5649
|
+
*/
|
|
5650
|
+
processFills(fills) {
|
|
5651
|
+
for (const fill of fills) {
|
|
5652
|
+
this.processFill(fill);
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
// ============================================================
|
|
5656
|
+
// MARKET RESOLUTION
|
|
5657
|
+
// ============================================================
|
|
5658
|
+
/**
|
|
5659
|
+
* Mark a market as resolved. Positions settle at $0 or $1.
|
|
5660
|
+
*/
|
|
5661
|
+
resolveMarket(conditionId, winningOutcome) {
|
|
5662
|
+
for (const [key, pos] of this.positions.entries()) {
|
|
5663
|
+
if (pos.marketConditionId === conditionId) {
|
|
5664
|
+
const isWinner = pos.outcomeIndex === winningOutcome;
|
|
5665
|
+
pos.currentPrice = isWinner ? 1 : 0;
|
|
5666
|
+
if (pos.balance > 0) {
|
|
5667
|
+
pos.totalProceeds += pos.currentPrice * pos.balance;
|
|
5668
|
+
pos.totalSold += pos.balance;
|
|
5669
|
+
pos.balance = 0;
|
|
5670
|
+
}
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
// ============================================================
|
|
5675
|
+
// PERSISTENCE
|
|
5676
|
+
// ============================================================
|
|
5677
|
+
/**
|
|
5678
|
+
* Export position state for persistence across restarts.
|
|
5679
|
+
*/
|
|
5680
|
+
exportState() {
|
|
5681
|
+
return Array.from(this.positions.values());
|
|
5682
|
+
}
|
|
5683
|
+
/**
|
|
5684
|
+
* Import position state from previous session.
|
|
5685
|
+
*/
|
|
5686
|
+
importState(state) {
|
|
5687
|
+
this.positions.clear();
|
|
5688
|
+
for (const pos of state) {
|
|
5689
|
+
const key = `${pos.marketConditionId}:${pos.outcomeIndex}`;
|
|
5690
|
+
this.positions.set(key, pos);
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
// ============================================================
|
|
5694
|
+
// PRIVATE
|
|
5695
|
+
// ============================================================
|
|
5696
|
+
/**
|
|
5697
|
+
* Refresh current prices for all open positions from CLOB.
|
|
5698
|
+
*/
|
|
5699
|
+
async refreshPrices() {
|
|
5700
|
+
const openPositions = Array.from(this.positions.values()).filter((p) => p.balance > 0);
|
|
5701
|
+
await Promise.all(
|
|
5702
|
+
openPositions.map(async (pos) => {
|
|
5703
|
+
try {
|
|
5704
|
+
if (!pos.tokenId) {
|
|
5705
|
+
const market = await this.client.getMarketByConditionId(pos.marketConditionId);
|
|
5706
|
+
if (market && market.outcomeTokenIds[pos.outcomeIndex]) {
|
|
5707
|
+
pos.tokenId = market.outcomeTokenIds[pos.outcomeIndex];
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
if (pos.tokenId) {
|
|
5711
|
+
const price = await this.client.getMidpointPrice(pos.tokenId);
|
|
5712
|
+
if (price > 0) pos.currentPrice = price;
|
|
5713
|
+
}
|
|
5714
|
+
} catch {
|
|
5715
|
+
}
|
|
5716
|
+
})
|
|
5717
|
+
);
|
|
5718
|
+
this.lastPriceRefresh = Date.now();
|
|
5719
|
+
}
|
|
5720
|
+
toExternalPosition(pos) {
|
|
5721
|
+
const unrealizedPnl = pos.balance > 0 ? (pos.currentPrice - pos.averageEntryPrice) * pos.balance : 0;
|
|
5722
|
+
return {
|
|
5723
|
+
marketConditionId: pos.marketConditionId,
|
|
5724
|
+
marketQuestion: pos.marketQuestion,
|
|
5725
|
+
outcomeIndex: pos.outcomeIndex,
|
|
5726
|
+
outcomeLabel: pos.outcomeIndex === 0 ? "Yes" : "No",
|
|
5727
|
+
tokenId: pos.tokenId,
|
|
5728
|
+
balance: pos.balance,
|
|
5729
|
+
averageEntryPrice: pos.averageEntryPrice,
|
|
5730
|
+
currentPrice: pos.currentPrice,
|
|
5731
|
+
unrealizedPnl,
|
|
5732
|
+
costBasis: pos.totalCostBasis - pos.totalProceeds,
|
|
5733
|
+
endDate: pos.endDate,
|
|
5734
|
+
category: pos.category
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5737
|
+
};
|
|
5738
|
+
|
|
5739
|
+
// src/prediction/recorder.ts
|
|
5740
|
+
var import_viem8 = require("viem");
|
|
5741
|
+
var import_chains5 = require("viem/chains");
|
|
5742
|
+
var import_accounts5 = require("viem/accounts");
|
|
5743
|
+
var ROUTER_ADDRESS2 = "0x1BCFa13f677fDCf697D8b7d5120f544817F1de1A";
|
|
5744
|
+
var ROUTER_ABI2 = [
|
|
5745
|
+
{
|
|
5746
|
+
type: "function",
|
|
5747
|
+
name: "recordPerpTrade",
|
|
5748
|
+
stateMutability: "nonpayable",
|
|
5749
|
+
inputs: [
|
|
5750
|
+
{ name: "agentId", type: "uint256" },
|
|
5751
|
+
{ name: "configHash", type: "bytes32" },
|
|
5752
|
+
{ name: "instrument", type: "string" },
|
|
5753
|
+
{ name: "isLong", type: "bool" },
|
|
5754
|
+
{ name: "notionalUSD", type: "uint256" },
|
|
5755
|
+
{ name: "feeUSD", type: "uint256" },
|
|
5756
|
+
{ name: "fillId", type: "bytes32" }
|
|
5757
|
+
],
|
|
5758
|
+
outputs: [{ name: "", type: "bool" }]
|
|
5759
|
+
}
|
|
5760
|
+
];
|
|
5761
|
+
var MAX_RETRIES2 = 3;
|
|
5762
|
+
var RETRY_DELAY_MS2 = 5e3;
|
|
5763
|
+
var PredictionTradeRecorder = class {
|
|
5764
|
+
publicClient;
|
|
5765
|
+
walletClient;
|
|
5766
|
+
account;
|
|
5767
|
+
agentId;
|
|
5768
|
+
configHash;
|
|
5769
|
+
/** Retry queue for failed recordings */
|
|
5770
|
+
retryQueue = [];
|
|
5771
|
+
/** Set of fill IDs already recorded (or in-progress) to prevent local dups */
|
|
5772
|
+
recordedFills = /* @__PURE__ */ new Set();
|
|
5773
|
+
/** Timer for processing retry queue */
|
|
5774
|
+
retryTimer = null;
|
|
5775
|
+
/** Accumulated fees pending bridge to Base treasury (in USD, 6-decimal) */
|
|
5776
|
+
accumulatedFees = 0n;
|
|
5777
|
+
constructor(opts) {
|
|
5778
|
+
this.agentId = opts.agentId;
|
|
5779
|
+
this.configHash = opts.configHash;
|
|
5780
|
+
this.account = (0, import_accounts5.privateKeyToAccount)(opts.privateKey);
|
|
5781
|
+
const rpcUrl = opts.rpcUrl || "https://mainnet.base.org";
|
|
5782
|
+
const transport = (0, import_viem8.http)(rpcUrl, { timeout: 6e4 });
|
|
5783
|
+
this.publicClient = (0, import_viem8.createPublicClient)({
|
|
5784
|
+
chain: import_chains5.base,
|
|
5785
|
+
transport
|
|
5786
|
+
});
|
|
5787
|
+
this.walletClient = (0, import_viem8.createWalletClient)({
|
|
5788
|
+
chain: import_chains5.base,
|
|
5789
|
+
transport,
|
|
5790
|
+
account: this.account
|
|
5791
|
+
});
|
|
5792
|
+
this.retryTimer = setInterval(() => this.processRetryQueue(), RETRY_DELAY_MS2);
|
|
5793
|
+
}
|
|
5794
|
+
// ============================================================
|
|
5795
|
+
// PUBLIC API
|
|
5796
|
+
// ============================================================
|
|
5797
|
+
/**
|
|
5798
|
+
* Record a prediction fill on-chain.
|
|
5799
|
+
* Converts the Polymarket fill into recordPerpTrade params with POLY: prefix.
|
|
5800
|
+
*/
|
|
5801
|
+
async recordFill(fill) {
|
|
5802
|
+
const fillId = tradeIdToBytes32(fill.tradeId);
|
|
5803
|
+
const fillIdStr = fillId.toLowerCase();
|
|
5804
|
+
if (this.recordedFills.has(fillIdStr)) {
|
|
5805
|
+
return { success: true };
|
|
5806
|
+
}
|
|
5807
|
+
this.recordedFills.add(fillIdStr);
|
|
5808
|
+
const notionalUSD = this.calculateNotionalUSD(fill);
|
|
5809
|
+
const feeUSD = calculatePredictionFee(notionalUSD);
|
|
5810
|
+
const params = {
|
|
5811
|
+
agentId: this.agentId,
|
|
5812
|
+
configHash: this.configHash,
|
|
5813
|
+
instrument: encodePredictionInstrument(fill.marketConditionId, fill.outcomeIndex),
|
|
5814
|
+
isLong: fill.side === "BUY",
|
|
5815
|
+
notionalUSD,
|
|
5816
|
+
feeUSD,
|
|
5817
|
+
fillId
|
|
5818
|
+
};
|
|
5819
|
+
this.accumulatedFees += feeUSD;
|
|
5820
|
+
return this.submitRecord(params);
|
|
5821
|
+
}
|
|
5822
|
+
/**
|
|
5823
|
+
* Update the config hash (when epoch changes).
|
|
5824
|
+
*/
|
|
5825
|
+
updateConfigHash(configHash) {
|
|
5826
|
+
this.configHash = configHash;
|
|
5827
|
+
}
|
|
5828
|
+
/**
|
|
5829
|
+
* Get the number of fills pending retry.
|
|
5830
|
+
*/
|
|
5831
|
+
get pendingRetries() {
|
|
5832
|
+
return this.retryQueue.length;
|
|
5833
|
+
}
|
|
5834
|
+
/**
|
|
5835
|
+
* Get the number of fills recorded (local dedup set size).
|
|
5836
|
+
*/
|
|
5837
|
+
get recordedCount() {
|
|
5838
|
+
return this.recordedFills.size;
|
|
5839
|
+
}
|
|
5840
|
+
/**
|
|
5841
|
+
* Get accumulated fees pending bridge (6-decimal USD).
|
|
5842
|
+
*/
|
|
5843
|
+
get pendingFees() {
|
|
5844
|
+
return this.accumulatedFees;
|
|
5845
|
+
}
|
|
5846
|
+
/**
|
|
5847
|
+
* Get accumulated fees as human-readable USD.
|
|
5848
|
+
*/
|
|
5849
|
+
get pendingFeesUSD() {
|
|
5850
|
+
return Number(this.accumulatedFees) / 1e6;
|
|
5851
|
+
}
|
|
5852
|
+
/**
|
|
5853
|
+
* Reset accumulated fees (after bridge to treasury).
|
|
5854
|
+
*/
|
|
5855
|
+
resetAccumulatedFees() {
|
|
5856
|
+
this.accumulatedFees = 0n;
|
|
5857
|
+
}
|
|
5858
|
+
/**
|
|
5859
|
+
* Stop the recorder (clear retry timer).
|
|
5860
|
+
*/
|
|
5861
|
+
stop() {
|
|
5862
|
+
if (this.retryTimer) {
|
|
5863
|
+
clearInterval(this.retryTimer);
|
|
5864
|
+
this.retryTimer = null;
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
// ============================================================
|
|
5868
|
+
// PRIVATE
|
|
5869
|
+
// ============================================================
|
|
5870
|
+
/**
|
|
5871
|
+
* Submit a recordPerpTrade transaction on Base.
|
|
5872
|
+
*/
|
|
5873
|
+
async submitRecord(params) {
|
|
5874
|
+
try {
|
|
5875
|
+
const { request } = await this.publicClient.simulateContract({
|
|
5876
|
+
address: ROUTER_ADDRESS2,
|
|
5877
|
+
abi: ROUTER_ABI2,
|
|
5878
|
+
functionName: "recordPerpTrade",
|
|
5879
|
+
args: [
|
|
5880
|
+
params.agentId,
|
|
5881
|
+
params.configHash,
|
|
5882
|
+
params.instrument,
|
|
5883
|
+
params.isLong,
|
|
5884
|
+
params.notionalUSD,
|
|
5885
|
+
params.feeUSD,
|
|
5886
|
+
params.fillId
|
|
5887
|
+
],
|
|
5888
|
+
account: this.account
|
|
5889
|
+
});
|
|
5890
|
+
const txHash = await this.walletClient.writeContract(request);
|
|
5891
|
+
const action = params.isLong ? "BUY" : "SELL";
|
|
5892
|
+
console.log(
|
|
5893
|
+
`Prediction trade recorded: ${action} ${params.instrument} $${Number(params.notionalUSD) / 1e6} \u2014 tx: ${txHash}`
|
|
5894
|
+
);
|
|
5895
|
+
return { success: true, txHash };
|
|
5896
|
+
} catch (error) {
|
|
5897
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5898
|
+
if (message.includes("FillAlreadyProcessed") || message.includes("already recorded")) {
|
|
5899
|
+
console.log(`Prediction fill already recorded on-chain: ${params.fillId}`);
|
|
5900
|
+
return { success: true };
|
|
5901
|
+
}
|
|
5902
|
+
console.error(`Failed to record prediction trade: ${message}`);
|
|
5903
|
+
this.retryQueue.push({
|
|
5904
|
+
params,
|
|
5905
|
+
retries: 0,
|
|
5906
|
+
lastAttempt: Date.now()
|
|
5907
|
+
});
|
|
5908
|
+
return { success: false, error: message };
|
|
5909
|
+
}
|
|
5910
|
+
}
|
|
5911
|
+
/**
|
|
5912
|
+
* Process the retry queue — attempt to re-submit failed recordings.
|
|
5913
|
+
*/
|
|
5914
|
+
async processRetryQueue() {
|
|
5915
|
+
if (this.retryQueue.length === 0) return;
|
|
5916
|
+
const now = Date.now();
|
|
5917
|
+
const toRetry = this.retryQueue.filter(
|
|
5918
|
+
(item) => now - item.lastAttempt >= RETRY_DELAY_MS2
|
|
5919
|
+
);
|
|
5920
|
+
for (const item of toRetry) {
|
|
5921
|
+
item.retries++;
|
|
5922
|
+
item.lastAttempt = now;
|
|
5923
|
+
if (item.retries > MAX_RETRIES2) {
|
|
5924
|
+
console.error(
|
|
5925
|
+
`Prediction trade recording permanently failed after ${MAX_RETRIES2} retries: ${item.params.instrument} ${item.params.fillId}`
|
|
5926
|
+
);
|
|
5927
|
+
const idx = this.retryQueue.indexOf(item);
|
|
5928
|
+
if (idx >= 0) this.retryQueue.splice(idx, 1);
|
|
5929
|
+
continue;
|
|
5930
|
+
}
|
|
5931
|
+
console.log(
|
|
5932
|
+
`Retrying prediction trade recording (attempt ${item.retries}/${MAX_RETRIES2}): ${item.params.instrument}`
|
|
5933
|
+
);
|
|
5934
|
+
const result = await this.submitRecord(item.params);
|
|
5935
|
+
if (result.success) {
|
|
5936
|
+
const idx = this.retryQueue.indexOf(item);
|
|
5937
|
+
if (idx >= 0) this.retryQueue.splice(idx, 1);
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
// ============================================================
|
|
5942
|
+
// CONVERSION HELPERS
|
|
5943
|
+
// ============================================================
|
|
5944
|
+
/**
|
|
5945
|
+
* Calculate notional USD from a fill (6-decimal).
|
|
5946
|
+
* notionalUSD = price * size * 1e6
|
|
5947
|
+
*/
|
|
5948
|
+
calculateNotionalUSD(fill) {
|
|
5949
|
+
const price = parseFloat(fill.price);
|
|
5950
|
+
const size = parseFloat(fill.size);
|
|
5951
|
+
return BigInt(Math.round(price * size * 1e6));
|
|
5952
|
+
}
|
|
5953
|
+
};
|
|
5954
|
+
|
|
5955
|
+
// src/prediction/market-browser.ts
|
|
5956
|
+
var MARKET_CATEGORIES = [
|
|
5957
|
+
"crypto",
|
|
5958
|
+
"politics",
|
|
5959
|
+
"sports",
|
|
5960
|
+
"entertainment",
|
|
5961
|
+
"science",
|
|
5962
|
+
"business",
|
|
5963
|
+
"culture",
|
|
5964
|
+
"weather",
|
|
5965
|
+
"tech"
|
|
5966
|
+
];
|
|
5967
|
+
var MarketBrowser = class {
|
|
5968
|
+
gammaUrl;
|
|
5969
|
+
allowedCategories;
|
|
5970
|
+
/** Cache for market listings */
|
|
5971
|
+
listCache = /* @__PURE__ */ new Map();
|
|
5972
|
+
CACHE_TTL_MS = 6e4;
|
|
5973
|
+
constructor(config) {
|
|
5974
|
+
const merged = { ...DEFAULT_PREDICTION_CONFIG, ...config };
|
|
5975
|
+
this.gammaUrl = merged.gammaApiUrl;
|
|
5976
|
+
this.allowedCategories = merged.allowedCategories || [];
|
|
5977
|
+
}
|
|
5978
|
+
// ============================================================
|
|
5979
|
+
// PUBLIC API
|
|
5980
|
+
// ============================================================
|
|
5981
|
+
/**
|
|
5982
|
+
* Get active markets, optionally filtered by category.
|
|
5983
|
+
* Results are sorted by volume (highest first).
|
|
5984
|
+
*/
|
|
5985
|
+
async getActiveMarkets(params) {
|
|
5986
|
+
const limit = params?.limit || 20;
|
|
5987
|
+
const offset = params?.offset || 0;
|
|
5988
|
+
const cacheKey = `active:${params?.category || "all"}:${limit}:${offset}`;
|
|
5989
|
+
const cached = this.listCache.get(cacheKey);
|
|
5990
|
+
if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
|
|
5991
|
+
return cached.data;
|
|
5992
|
+
}
|
|
5993
|
+
const query = new URLSearchParams({
|
|
5994
|
+
active: "true",
|
|
5995
|
+
closed: "false",
|
|
5996
|
+
limit: String(limit),
|
|
5997
|
+
offset: String(offset),
|
|
5998
|
+
order: "volume24hr",
|
|
5999
|
+
ascending: "false"
|
|
6000
|
+
});
|
|
6001
|
+
if (params?.category) query.set("tag", params.category);
|
|
6002
|
+
const markets = await this.fetchGamma(`/markets?${query.toString()}`);
|
|
6003
|
+
const parsed = markets.map((m) => this.parseMarket(m));
|
|
6004
|
+
const filtered = this.allowedCategories.length > 0 ? parsed.filter(
|
|
6005
|
+
(m) => this.allowedCategories.some((c) => m.category.toLowerCase().includes(c.toLowerCase()))
|
|
6006
|
+
) : parsed;
|
|
6007
|
+
this.listCache.set(cacheKey, { data: filtered, cachedAt: Date.now() });
|
|
6008
|
+
return filtered;
|
|
6009
|
+
}
|
|
6010
|
+
/**
|
|
6011
|
+
* Search markets by query text.
|
|
6012
|
+
*/
|
|
6013
|
+
async searchMarkets(query, limit = 20) {
|
|
6014
|
+
const params = new URLSearchParams({
|
|
6015
|
+
_q: query,
|
|
6016
|
+
limit: String(limit),
|
|
6017
|
+
active: "true"
|
|
6018
|
+
});
|
|
6019
|
+
const markets = await this.fetchGamma(`/markets?${params.toString()}`);
|
|
6020
|
+
return markets.map((m) => this.parseMarket(m));
|
|
6021
|
+
}
|
|
6022
|
+
/**
|
|
6023
|
+
* Get trending markets (highest 24h volume).
|
|
6024
|
+
*/
|
|
6025
|
+
async getTrendingMarkets(limit = 10) {
|
|
6026
|
+
return this.getActiveMarkets({ limit });
|
|
6027
|
+
}
|
|
6028
|
+
/**
|
|
6029
|
+
* Get markets expiring soon (within N days).
|
|
6030
|
+
*/
|
|
6031
|
+
async getExpiringMarkets(withinDays = 7, limit = 20) {
|
|
6032
|
+
const markets = await this.getActiveMarkets({ limit: 100 });
|
|
6033
|
+
const cutoff = Date.now() / 1e3 + withinDays * 86400;
|
|
6034
|
+
return markets.filter((m) => m.endDate > 0 && m.endDate < cutoff).sort((a, b) => a.endDate - b.endDate).slice(0, limit);
|
|
6035
|
+
}
|
|
6036
|
+
/**
|
|
6037
|
+
* Get recently resolved markets (for PnL tracking).
|
|
6038
|
+
*/
|
|
6039
|
+
async getRecentlyResolved(limit = 20) {
|
|
6040
|
+
const params = new URLSearchParams({
|
|
6041
|
+
closed: "true",
|
|
6042
|
+
limit: String(limit),
|
|
6043
|
+
order: "endDate",
|
|
6044
|
+
ascending: "false"
|
|
6045
|
+
});
|
|
6046
|
+
const markets = await this.fetchGamma(`/markets?${params.toString()}`);
|
|
6047
|
+
return markets.map((m) => this.parseMarket(m)).filter((m) => m.resolved);
|
|
6048
|
+
}
|
|
6049
|
+
/**
|
|
6050
|
+
* Get a single market's details by condition ID.
|
|
6051
|
+
*/
|
|
6052
|
+
async getMarketDetail(conditionId) {
|
|
6053
|
+
const markets = await this.fetchGamma(`/markets?condition_id=${conditionId}`);
|
|
6054
|
+
if (!markets || markets.length === 0) return null;
|
|
6055
|
+
return this.parseMarket(markets[0]);
|
|
6056
|
+
}
|
|
6057
|
+
/**
|
|
6058
|
+
* Build a concise market summary string for LLM context.
|
|
6059
|
+
* Keeps the prompt token count manageable.
|
|
6060
|
+
*/
|
|
6061
|
+
formatMarketsForLLM(markets, maxMarkets = 15) {
|
|
6062
|
+
const subset = markets.slice(0, maxMarkets);
|
|
6063
|
+
const lines = subset.map((m, i) => {
|
|
6064
|
+
const prices = m.outcomePrices.map((p, j) => `${m.outcomes[j]}: ${(p * 100).toFixed(1)}%`).join(", ");
|
|
6065
|
+
const vol = m.volume24h >= 1e3 ? `$${(m.volume24h / 1e3).toFixed(1)}K` : `$${m.volume24h.toFixed(0)}`;
|
|
6066
|
+
const endStr = m.endDate > 0 ? new Date(m.endDate * 1e3).toISOString().split("T")[0] : "TBD";
|
|
6067
|
+
return `${i + 1}. [${m.category}] "${m.question}" \u2014 ${prices} | Vol: ${vol} | Ends: ${endStr} | ID: ${m.conditionId}`;
|
|
6068
|
+
});
|
|
6069
|
+
return lines.join("\n");
|
|
6070
|
+
}
|
|
6071
|
+
// ============================================================
|
|
6072
|
+
// PRIVATE
|
|
6073
|
+
// ============================================================
|
|
6074
|
+
async fetchGamma(path4) {
|
|
6075
|
+
const url = `${this.gammaUrl}${path4}`;
|
|
6076
|
+
const resp = await fetch(url);
|
|
6077
|
+
if (!resp.ok) {
|
|
6078
|
+
console.error(`Gamma API error: ${resp.status} for ${url}`);
|
|
6079
|
+
return [];
|
|
6080
|
+
}
|
|
6081
|
+
const data = await resp.json();
|
|
6082
|
+
return Array.isArray(data) ? data : [];
|
|
6083
|
+
}
|
|
6084
|
+
parseMarket(raw) {
|
|
6085
|
+
const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ["Yes", "No"];
|
|
6086
|
+
const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
|
|
6087
|
+
const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
|
|
6088
|
+
return {
|
|
6089
|
+
conditionId: raw.conditionId || raw.condition_id || "",
|
|
6090
|
+
question: raw.question || "",
|
|
6091
|
+
description: raw.description || "",
|
|
6092
|
+
category: raw.groupItemTitle || raw.category || "Other",
|
|
6093
|
+
outcomes,
|
|
6094
|
+
outcomeTokenIds,
|
|
6095
|
+
outcomePrices: outcomePrices.map((p) => parseFloat(p) || 0),
|
|
6096
|
+
volume24h: parseFloat(raw.volume24hr || raw.volume24h || "0"),
|
|
6097
|
+
liquidity: parseFloat(raw.liquidity || "0"),
|
|
6098
|
+
endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1e3 : 0,
|
|
6099
|
+
active: raw.active !== false && raw.closed !== true,
|
|
6100
|
+
resolved: raw.resolved === true,
|
|
6101
|
+
winningOutcome: raw.winningOutcome,
|
|
6102
|
+
resolutionSource: raw.resolutionSource || void 0,
|
|
6103
|
+
uniqueTraders: raw.uniqueTraders || void 0
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
6106
|
+
};
|
|
6107
|
+
|
|
6108
|
+
// src/prediction/funding.ts
|
|
6109
|
+
var import_viem9 = require("viem");
|
|
6110
|
+
var import_chains6 = require("viem/chains");
|
|
6111
|
+
var import_accounts6 = require("viem/accounts");
|
|
6112
|
+
var CONTRACTS = {
|
|
6113
|
+
// Base
|
|
6114
|
+
BASE_USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
6115
|
+
BASE_TOKEN_MESSENGER_V2: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
|
|
6116
|
+
BASE_MESSAGE_TRANSMITTER_V2: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
|
|
6117
|
+
// Polygon
|
|
6118
|
+
POLYGON_USDC: POLYGON_USDC_ADDRESS,
|
|
6119
|
+
POLYGON_TOKEN_MESSENGER_V2,
|
|
6120
|
+
POLYGON_MESSAGE_TRANSMITTER_V2
|
|
6121
|
+
};
|
|
6122
|
+
var IRIS_API_URL = "https://iris-api.circle.com/v1/attestations";
|
|
6123
|
+
var MIN_BRIDGE_AMOUNT = 1000000n;
|
|
6124
|
+
var ERC20_ABI2 = (0, import_viem9.parseAbi)([
|
|
6125
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
6126
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
6127
|
+
"function allowance(address owner, address spender) external view returns (uint256)"
|
|
6128
|
+
]);
|
|
6129
|
+
var TOKEN_MESSENGER_V2_ABI2 = (0, import_viem9.parseAbi)([
|
|
6130
|
+
"function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) external returns (uint64 nonce)",
|
|
6131
|
+
"event DepositForBurn(uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller)"
|
|
6132
|
+
]);
|
|
6133
|
+
var MESSAGE_TRANSMITTER_V2_ABI2 = (0, import_viem9.parseAbi)([
|
|
6134
|
+
"function receiveMessage(bytes message, bytes attestation) external returns (bool success)",
|
|
6135
|
+
"event MessageSent(bytes message)"
|
|
6136
|
+
]);
|
|
6137
|
+
var PredictionFunding = class {
|
|
6138
|
+
basePublic;
|
|
6139
|
+
baseWallet;
|
|
6140
|
+
polygonPublic;
|
|
6141
|
+
polygonWallet;
|
|
6142
|
+
account;
|
|
6143
|
+
/** Override recipient for deposits to Polygon (vault address in vault mode) */
|
|
6144
|
+
polygonRecipient;
|
|
6145
|
+
constructor(config) {
|
|
6146
|
+
this.account = (0, import_accounts6.privateKeyToAccount)(config.privateKey);
|
|
6147
|
+
this.polygonRecipient = config.polygonRecipient;
|
|
6148
|
+
const baseTransport = (0, import_viem9.http)(config.baseRpcUrl || "https://mainnet.base.org", { timeout: 6e4 });
|
|
6149
|
+
const polygonTransport = (0, import_viem9.http)(config.polygonRpcUrl || "https://polygon-rpc.com", { timeout: 6e4 });
|
|
6150
|
+
this.basePublic = (0, import_viem9.createPublicClient)({ chain: import_chains6.base, transport: baseTransport });
|
|
6151
|
+
this.baseWallet = (0, import_viem9.createWalletClient)({ chain: import_chains6.base, transport: baseTransport, account: this.account });
|
|
6152
|
+
this.polygonPublic = (0, import_viem9.createPublicClient)({ chain: import_chains6.polygon, transport: polygonTransport });
|
|
6153
|
+
this.polygonWallet = (0, import_viem9.createWalletClient)({ chain: import_chains6.polygon, transport: polygonTransport, account: this.account });
|
|
6154
|
+
}
|
|
6155
|
+
// ============================================================
|
|
6156
|
+
// BALANCE QUERIES
|
|
6157
|
+
// ============================================================
|
|
6158
|
+
/** Get USDC balance on Base (6-decimal) */
|
|
6159
|
+
async getBaseUSDCBalance() {
|
|
6160
|
+
return this.basePublic.readContract({
|
|
6161
|
+
address: CONTRACTS.BASE_USDC,
|
|
6162
|
+
abi: ERC20_ABI2,
|
|
6163
|
+
functionName: "balanceOf",
|
|
6164
|
+
args: [this.account.address]
|
|
6165
|
+
});
|
|
6166
|
+
}
|
|
6167
|
+
/** Get USDC balance on Polygon (6-decimal) */
|
|
6168
|
+
async getPolygonUSDCBalance() {
|
|
6169
|
+
return this.polygonPublic.readContract({
|
|
6170
|
+
address: CONTRACTS.POLYGON_USDC,
|
|
6171
|
+
abi: ERC20_ABI2,
|
|
6172
|
+
functionName: "balanceOf",
|
|
6173
|
+
args: [this.account.address]
|
|
6174
|
+
});
|
|
6175
|
+
}
|
|
6176
|
+
/** Get MATIC balance on Polygon for gas (wei) */
|
|
6177
|
+
async getPolygonGasBalance() {
|
|
6178
|
+
return this.polygonPublic.getBalance({ address: this.account.address });
|
|
6179
|
+
}
|
|
6180
|
+
// ============================================================
|
|
6181
|
+
// DEPOSIT: Base → Polygon
|
|
6182
|
+
// ============================================================
|
|
6183
|
+
/**
|
|
6184
|
+
* Bridge USDC from Base to Polygon via CCTP V2.
|
|
6185
|
+
*
|
|
6186
|
+
* Steps:
|
|
6187
|
+
* 1. Approve USDC on Base for TokenMessengerV2
|
|
6188
|
+
* 2. Call depositForBurn (Base → Polygon)
|
|
6189
|
+
* 3. Wait for Circle attestation (~1-5 minutes)
|
|
6190
|
+
* 4. Call receiveMessage on Polygon
|
|
6191
|
+
*/
|
|
6192
|
+
async depositToPolygon(amount, onStep) {
|
|
6193
|
+
const startTime = Date.now();
|
|
6194
|
+
if (amount < MIN_BRIDGE_AMOUNT) {
|
|
6195
|
+
return { success: false, amount, error: "Amount below minimum (1 USDC)" };
|
|
6196
|
+
}
|
|
6197
|
+
try {
|
|
6198
|
+
onStep?.("approve_usdc");
|
|
6199
|
+
await this.approveIfNeeded(
|
|
6200
|
+
this.basePublic,
|
|
6201
|
+
this.baseWallet,
|
|
6202
|
+
CONTRACTS.BASE_USDC,
|
|
6203
|
+
CONTRACTS.BASE_TOKEN_MESSENGER_V2,
|
|
6204
|
+
amount
|
|
6205
|
+
);
|
|
6206
|
+
onStep?.("cctp_burn");
|
|
6207
|
+
const recipient = this.polygonRecipient ?? this.account.address;
|
|
6208
|
+
const mintRecipient = this.addressToBytes32(recipient);
|
|
6209
|
+
const burnHash = await this.baseWallet.writeContract({
|
|
6210
|
+
address: CONTRACTS.BASE_TOKEN_MESSENGER_V2,
|
|
6211
|
+
abi: TOKEN_MESSENGER_V2_ABI2,
|
|
6212
|
+
functionName: "depositForBurn",
|
|
6213
|
+
args: [amount, CCTP_DOMAIN_POLYGON, mintRecipient, CONTRACTS.BASE_USDC]
|
|
6214
|
+
});
|
|
6215
|
+
const burnReceipt = await this.basePublic.waitForTransactionReceipt({ hash: burnHash });
|
|
6216
|
+
const messageBytes = this.extractMessageFromReceipt(burnReceipt);
|
|
6217
|
+
if (!messageBytes) {
|
|
6218
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Failed to extract CCTP message from burn tx" };
|
|
6219
|
+
}
|
|
6220
|
+
onStep?.("wait_attestation");
|
|
6221
|
+
const attestation = await this.waitForAttestation(messageBytes);
|
|
6222
|
+
if (!attestation) {
|
|
6223
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Attestation timeout (15 min)" };
|
|
6224
|
+
}
|
|
6225
|
+
onStep?.("cctp_receive");
|
|
6226
|
+
const receiveHash = await this.polygonWallet.writeContract({
|
|
6227
|
+
address: CONTRACTS.POLYGON_MESSAGE_TRANSMITTER_V2,
|
|
6228
|
+
abi: MESSAGE_TRANSMITTER_V2_ABI2,
|
|
6229
|
+
functionName: "receiveMessage",
|
|
6230
|
+
args: [messageBytes, attestation]
|
|
6231
|
+
});
|
|
6232
|
+
await this.polygonPublic.waitForTransactionReceipt({ hash: receiveHash });
|
|
6233
|
+
console.log(`CCTP deposit complete: ${Number(amount) / 1e6} USDC Base \u2192 Polygon in ${((Date.now() - startTime) / 1e3).toFixed(0)}s`);
|
|
6234
|
+
return {
|
|
6235
|
+
success: true,
|
|
6236
|
+
amount,
|
|
6237
|
+
burnTxHash: burnHash,
|
|
6238
|
+
receiveTxHash: receiveHash,
|
|
6239
|
+
duration: Date.now() - startTime
|
|
6240
|
+
};
|
|
6241
|
+
} catch (error) {
|
|
6242
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6243
|
+
console.error("CCTP deposit to Polygon failed:", message);
|
|
6244
|
+
return { success: false, amount, error: message, duration: Date.now() - startTime };
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
// ============================================================
|
|
6248
|
+
// WITHDRAW: Polygon → Base
|
|
6249
|
+
// ============================================================
|
|
6250
|
+
/**
|
|
6251
|
+
* Bridge USDC from Polygon back to Base via CCTP V2.
|
|
6252
|
+
* Used for fee collection (bridge accumulated fees to Base treasury).
|
|
6253
|
+
*/
|
|
6254
|
+
async withdrawToBase(amount, onStep) {
|
|
6255
|
+
const startTime = Date.now();
|
|
6256
|
+
if (amount < MIN_BRIDGE_AMOUNT) {
|
|
6257
|
+
return { success: false, amount, error: "Amount below minimum (1 USDC)" };
|
|
6258
|
+
}
|
|
6259
|
+
try {
|
|
6260
|
+
onStep?.("approve_usdc");
|
|
6261
|
+
await this.approveIfNeeded(
|
|
6262
|
+
this.polygonPublic,
|
|
6263
|
+
this.polygonWallet,
|
|
6264
|
+
CONTRACTS.POLYGON_USDC,
|
|
6265
|
+
CONTRACTS.POLYGON_TOKEN_MESSENGER_V2,
|
|
6266
|
+
amount
|
|
6267
|
+
);
|
|
6268
|
+
onStep?.("cctp_burn");
|
|
6269
|
+
const mintRecipient = this.addressToBytes32(this.account.address);
|
|
6270
|
+
const burnHash = await this.polygonWallet.writeContract({
|
|
6271
|
+
address: CONTRACTS.POLYGON_TOKEN_MESSENGER_V2,
|
|
6272
|
+
abi: TOKEN_MESSENGER_V2_ABI2,
|
|
6273
|
+
functionName: "depositForBurn",
|
|
6274
|
+
args: [amount, CCTP_DOMAIN_BASE, mintRecipient, CONTRACTS.POLYGON_USDC]
|
|
6275
|
+
});
|
|
6276
|
+
const burnReceipt = await this.polygonPublic.waitForTransactionReceipt({ hash: burnHash });
|
|
6277
|
+
const messageBytes = this.extractMessageFromReceipt(burnReceipt);
|
|
6278
|
+
if (!messageBytes) {
|
|
6279
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Failed to extract CCTP message" };
|
|
6280
|
+
}
|
|
6281
|
+
onStep?.("wait_attestation");
|
|
6282
|
+
const attestation = await this.waitForAttestation(messageBytes);
|
|
6283
|
+
if (!attestation) {
|
|
6284
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Attestation timeout" };
|
|
6285
|
+
}
|
|
6286
|
+
onStep?.("cctp_receive");
|
|
6287
|
+
const receiveHash = await this.baseWallet.writeContract({
|
|
6288
|
+
address: CONTRACTS.BASE_MESSAGE_TRANSMITTER_V2,
|
|
6289
|
+
abi: MESSAGE_TRANSMITTER_V2_ABI2,
|
|
6290
|
+
functionName: "receiveMessage",
|
|
6291
|
+
args: [messageBytes, attestation]
|
|
6292
|
+
});
|
|
6293
|
+
await this.basePublic.waitForTransactionReceipt({ hash: receiveHash });
|
|
6294
|
+
console.log(`CCTP withdrawal complete: ${Number(amount) / 1e6} USDC Polygon \u2192 Base in ${((Date.now() - startTime) / 1e3).toFixed(0)}s`);
|
|
6295
|
+
return {
|
|
6296
|
+
success: true,
|
|
6297
|
+
amount,
|
|
6298
|
+
burnTxHash: burnHash,
|
|
6299
|
+
receiveTxHash: receiveHash,
|
|
6300
|
+
duration: Date.now() - startTime
|
|
6301
|
+
};
|
|
6302
|
+
} catch (error) {
|
|
6303
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6304
|
+
console.error("CCTP withdrawal to Base failed:", message);
|
|
6305
|
+
return { success: false, amount, error: message, duration: Date.now() - startTime };
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
// ============================================================
|
|
6309
|
+
// PRIVATE HELPERS
|
|
6310
|
+
// ============================================================
|
|
6311
|
+
/** Convert address to bytes32 (left-padded) for CCTP mintRecipient */
|
|
6312
|
+
addressToBytes32(address) {
|
|
6313
|
+
return `0x000000000000000000000000${address.slice(2)}`;
|
|
6314
|
+
}
|
|
6315
|
+
/** Approve USDC spend if needed (maxUint256 pattern) */
|
|
6316
|
+
async approveIfNeeded(publicClient, walletClient, token, spender, amount) {
|
|
6317
|
+
const allowance = await publicClient.readContract({
|
|
6318
|
+
address: token,
|
|
6319
|
+
abi: ERC20_ABI2,
|
|
6320
|
+
functionName: "allowance",
|
|
6321
|
+
args: [this.account.address, spender]
|
|
6322
|
+
});
|
|
6323
|
+
if (allowance < amount) {
|
|
6324
|
+
const maxUint256 = 2n ** 256n - 1n;
|
|
6325
|
+
const hash = await walletClient.writeContract({
|
|
6326
|
+
address: token,
|
|
6327
|
+
abi: ERC20_ABI2,
|
|
6328
|
+
functionName: "approve",
|
|
6329
|
+
args: [spender, maxUint256]
|
|
6330
|
+
});
|
|
6331
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
/** Extract MessageSent bytes from a CCTP burn transaction receipt */
|
|
6335
|
+
extractMessageFromReceipt(receipt) {
|
|
6336
|
+
for (const log of receipt.logs || []) {
|
|
6337
|
+
try {
|
|
6338
|
+
if (log.topics[0] === "0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036") {
|
|
6339
|
+
return log.data;
|
|
6340
|
+
}
|
|
6341
|
+
} catch {
|
|
6342
|
+
continue;
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
return null;
|
|
6346
|
+
}
|
|
6347
|
+
/** Wait for Circle attestation (polls Iris API) */
|
|
6348
|
+
async waitForAttestation(messageBytes, timeoutMs = 9e5) {
|
|
6349
|
+
const { keccak256: keccak2565 } = await import("viem");
|
|
6350
|
+
const messageHash = keccak2565(messageBytes);
|
|
6351
|
+
const start = Date.now();
|
|
6352
|
+
const pollInterval = 5e3;
|
|
6353
|
+
while (Date.now() - start < timeoutMs) {
|
|
6354
|
+
try {
|
|
6355
|
+
const resp = await fetch(`${IRIS_API_URL}/${messageHash}`);
|
|
6356
|
+
if (resp.ok) {
|
|
6357
|
+
const data = await resp.json();
|
|
6358
|
+
if (data.status === "complete" && data.attestation) {
|
|
6359
|
+
return data.attestation;
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6362
|
+
} catch {
|
|
6363
|
+
}
|
|
6364
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
6365
|
+
}
|
|
6366
|
+
return null;
|
|
6367
|
+
}
|
|
6368
|
+
};
|
|
6369
|
+
|
|
6370
|
+
// src/prediction/vault-manager.ts
|
|
6371
|
+
var import_viem10 = require("viem");
|
|
6372
|
+
var import_chains7 = require("viem/chains");
|
|
6373
|
+
var import_accounts7 = require("viem/accounts");
|
|
6374
|
+
var ERC20_ABI3 = (0, import_viem10.parseAbi)([
|
|
6375
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
6376
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
6377
|
+
"function allowance(address owner, address spender) external view returns (uint256)"
|
|
6378
|
+
]);
|
|
6379
|
+
var VAULT_FACTORY_ABI2 = (0, import_viem10.parseAbi)([
|
|
6380
|
+
"function createVault(uint256 agentId, address agentWallet, uint256 seedAmount, string name, string symbol, address feeRecipient, bool isVerified, bytes agentSig) external returns (address)",
|
|
6381
|
+
"function vaults(uint256 agentId) external view returns (address)",
|
|
6382
|
+
"function isFactoryVault(address vault) external view returns (bool)",
|
|
6383
|
+
"function MIN_SEED_AMOUNT() external view returns (uint256)"
|
|
6384
|
+
]);
|
|
6385
|
+
var VAULT_ABI2 = (0, import_viem10.parseAbi)([
|
|
6386
|
+
// ERC-4626
|
|
6387
|
+
"function deposit(uint256 assets, address receiver) external returns (uint256)",
|
|
6388
|
+
"function withdraw(uint256 assets, address receiver, address owner) external returns (uint256)",
|
|
6389
|
+
"function redeem(uint256 shares, address receiver, address owner) external returns (uint256)",
|
|
6390
|
+
"function redeemMax(address receiver, address owner) external returns (uint256)",
|
|
6391
|
+
"function totalAssets() external view returns (uint256)",
|
|
6392
|
+
"function totalSupply() external view returns (uint256)",
|
|
6393
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
6394
|
+
"function convertToAssets(uint256 shares) external view returns (uint256)",
|
|
6395
|
+
"function convertToShares(uint256 assets) external view returns (uint256)",
|
|
6396
|
+
// Vault-specific views
|
|
6397
|
+
"function sharePrice() external view returns (uint256)",
|
|
6398
|
+
"function availableTradingBalance() external view returns (uint256)",
|
|
6399
|
+
"function currentExposureBps() external view returns (uint256)",
|
|
6400
|
+
"function cachedCtfValue() external view returns (uint256)",
|
|
6401
|
+
"function lastCtfUpdate() external view returns (uint256)",
|
|
6402
|
+
"function depositCap() external view returns (uint256)",
|
|
6403
|
+
"function agentSeedAmount() external view returns (uint256)",
|
|
6404
|
+
"function circuitBreakerActive() external view returns (bool)",
|
|
6405
|
+
"function paused() external view returns (bool)",
|
|
6406
|
+
"function pendingTradingVolume() external view returns (uint256)",
|
|
6407
|
+
"function getTrackedTokenCount() external view returns (uint256)",
|
|
6408
|
+
"function agentWallet() external view returns (address)",
|
|
6409
|
+
"function agentId() external view returns (uint256)",
|
|
6410
|
+
"function isVerified() external view returns (bool)",
|
|
6411
|
+
"function maxExposureBps() external view returns (uint256)",
|
|
6412
|
+
// Keeper functions
|
|
6413
|
+
"function updatePositionValues(uint256[] tokenIds, uint256[] values) external",
|
|
6414
|
+
"function collectTradingFees(uint256 volume) external",
|
|
6415
|
+
// Emergency
|
|
6416
|
+
"function emergencyWithdraw() external returns (uint256)"
|
|
6417
|
+
]);
|
|
6418
|
+
var PredictionVaultManager = class {
|
|
6419
|
+
config;
|
|
6420
|
+
publicClient;
|
|
6421
|
+
walletClient;
|
|
6422
|
+
account;
|
|
6423
|
+
/** Resolved vault address (discovered or created) */
|
|
6424
|
+
vaultAddress = null;
|
|
6425
|
+
/** NAV update timer */
|
|
6426
|
+
navTimer = null;
|
|
6427
|
+
/** Fee collection timer */
|
|
6428
|
+
feeTimer = null;
|
|
6429
|
+
/** Last known accumulated trading volume (for delta tracking) */
|
|
6430
|
+
lastReportedVolume = 0n;
|
|
6431
|
+
constructor(privateKey, config) {
|
|
6432
|
+
this.config = config;
|
|
6433
|
+
this.account = (0, import_accounts7.privateKeyToAccount)(privateKey);
|
|
6434
|
+
const transport = (0, import_viem10.http)(config.polygonRpcUrl, { timeout: 6e4 });
|
|
6435
|
+
this.publicClient = (0, import_viem10.createPublicClient)({ chain: import_chains7.polygon, transport });
|
|
6436
|
+
this.walletClient = (0, import_viem10.createWalletClient)({
|
|
6437
|
+
chain: import_chains7.polygon,
|
|
6438
|
+
transport,
|
|
6439
|
+
account: this.account
|
|
6440
|
+
});
|
|
6441
|
+
if (config.vaultAddress) {
|
|
6442
|
+
this.vaultAddress = config.vaultAddress;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
// ============================================================
|
|
6446
|
+
// INITIALIZATION
|
|
6447
|
+
// ============================================================
|
|
6448
|
+
/**
|
|
6449
|
+
* Initialize the vault manager — discover existing vault or prepare for creation.
|
|
6450
|
+
*/
|
|
6451
|
+
async initialize() {
|
|
6452
|
+
if (!this.vaultAddress) {
|
|
6453
|
+
try {
|
|
6454
|
+
const vaultAddr = await this.publicClient.readContract({
|
|
6455
|
+
address: this.config.factoryAddress,
|
|
6456
|
+
abi: VAULT_FACTORY_ABI2,
|
|
6457
|
+
functionName: "vaults",
|
|
6458
|
+
args: [this.config.agentId]
|
|
6459
|
+
});
|
|
6460
|
+
if (vaultAddr && vaultAddr !== "0x0000000000000000000000000000000000000000") {
|
|
6461
|
+
this.vaultAddress = vaultAddr;
|
|
6462
|
+
console.log(`Discovered existing prediction vault: ${this.vaultAddress}`);
|
|
6463
|
+
}
|
|
6464
|
+
} catch {
|
|
6465
|
+
console.log("No existing prediction vault found \u2014 create one via createVault()");
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
if (this.vaultAddress) {
|
|
6469
|
+
console.log(`Prediction vault manager initialized for vault: ${this.vaultAddress}`);
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
/**
|
|
6473
|
+
* Get the vault address (null if no vault exists).
|
|
6474
|
+
*/
|
|
6475
|
+
getVaultAddress() {
|
|
6476
|
+
return this.vaultAddress;
|
|
6477
|
+
}
|
|
6478
|
+
/**
|
|
6479
|
+
* Whether a vault has been created/discovered.
|
|
6480
|
+
*/
|
|
6481
|
+
get hasVault() {
|
|
6482
|
+
return this.vaultAddress !== null;
|
|
6483
|
+
}
|
|
6484
|
+
// ============================================================
|
|
6485
|
+
// VAULT CREATION
|
|
6486
|
+
// ============================================================
|
|
6487
|
+
/**
|
|
6488
|
+
* Create a new prediction vault via PredictionVaultFactory.
|
|
6489
|
+
*
|
|
6490
|
+
* Deploys PredictionVault on Polygon with:
|
|
6491
|
+
* - Agent's EOA as the authorized signer for ERC-1271
|
|
6492
|
+
* - Seed USDC transferred from agent wallet to vault
|
|
6493
|
+
* - Deposit cap = seed × multiplier (10x unverified, 100x verified)
|
|
6494
|
+
*/
|
|
6495
|
+
async createVault(seedAmount, name, symbol) {
|
|
6496
|
+
if (this.vaultAddress) {
|
|
6497
|
+
return { success: false, error: `Vault already exists: ${this.vaultAddress}` };
|
|
6498
|
+
}
|
|
6499
|
+
try {
|
|
6500
|
+
const message = (0, import_viem10.keccak256)(
|
|
6501
|
+
(0, import_viem10.encodePacked)(
|
|
6502
|
+
["uint256", "address", "address"],
|
|
6503
|
+
[this.config.agentId, this.account.address, this.config.factoryAddress]
|
|
6504
|
+
)
|
|
6505
|
+
);
|
|
6506
|
+
const agentSig = await this.account.signMessage({ message: { raw: message } });
|
|
6507
|
+
await this.approveIfNeeded(
|
|
6508
|
+
POLYGON_USDC_ADDRESS,
|
|
6509
|
+
this.config.factoryAddress,
|
|
6510
|
+
seedAmount
|
|
6511
|
+
);
|
|
6512
|
+
const hash = await this.walletClient.writeContract({
|
|
6513
|
+
address: this.config.factoryAddress,
|
|
6514
|
+
abi: VAULT_FACTORY_ABI2,
|
|
6515
|
+
functionName: "createVault",
|
|
6516
|
+
args: [
|
|
6517
|
+
this.config.agentId,
|
|
6518
|
+
this.account.address,
|
|
6519
|
+
seedAmount,
|
|
6520
|
+
name,
|
|
6521
|
+
symbol,
|
|
6522
|
+
this.config.feeRecipient,
|
|
6523
|
+
this.config.isVerified ?? false,
|
|
6524
|
+
agentSig
|
|
6525
|
+
]
|
|
6526
|
+
});
|
|
6527
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6528
|
+
const vaultAddr = await this.publicClient.readContract({
|
|
6529
|
+
address: this.config.factoryAddress,
|
|
6530
|
+
abi: VAULT_FACTORY_ABI2,
|
|
6531
|
+
functionName: "vaults",
|
|
6532
|
+
args: [this.config.agentId]
|
|
6533
|
+
});
|
|
6534
|
+
this.vaultAddress = vaultAddr;
|
|
6535
|
+
console.log(`Prediction vault created: ${this.vaultAddress} (seed: ${(0, import_viem10.formatUnits)(seedAmount, 6)} USDC)`);
|
|
6536
|
+
return {
|
|
6537
|
+
success: true,
|
|
6538
|
+
vaultAddress: this.vaultAddress,
|
|
6539
|
+
txHash: hash
|
|
6540
|
+
};
|
|
6541
|
+
} catch (error) {
|
|
6542
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6543
|
+
console.error("Failed to create prediction vault:", message);
|
|
6544
|
+
return { success: false, error: message };
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
// ============================================================
|
|
6548
|
+
// DEPOSITS & WITHDRAWALS
|
|
6549
|
+
// ============================================================
|
|
6550
|
+
/**
|
|
6551
|
+
* Deposit USDC into the prediction vault.
|
|
6552
|
+
* Returns the number of shares received.
|
|
6553
|
+
*/
|
|
6554
|
+
async deposit(amount) {
|
|
6555
|
+
if (!this.vaultAddress) {
|
|
6556
|
+
return { success: false, error: "No vault exists" };
|
|
6557
|
+
}
|
|
6558
|
+
try {
|
|
6559
|
+
await this.approveIfNeeded(
|
|
6560
|
+
POLYGON_USDC_ADDRESS,
|
|
6561
|
+
this.vaultAddress,
|
|
6562
|
+
amount
|
|
6563
|
+
);
|
|
6564
|
+
const hash = await this.walletClient.writeContract({
|
|
6565
|
+
address: this.vaultAddress,
|
|
6566
|
+
abi: VAULT_ABI2,
|
|
6567
|
+
functionName: "deposit",
|
|
6568
|
+
args: [amount, this.account.address]
|
|
6569
|
+
});
|
|
6570
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6571
|
+
const shares = await this.publicClient.readContract({
|
|
6572
|
+
address: this.vaultAddress,
|
|
6573
|
+
abi: VAULT_ABI2,
|
|
6574
|
+
functionName: "balanceOf",
|
|
6575
|
+
args: [this.account.address]
|
|
6576
|
+
});
|
|
6577
|
+
console.log(`Deposited ${(0, import_viem10.formatUnits)(amount, 6)} USDC into prediction vault`);
|
|
6578
|
+
return { success: true, shares, txHash: hash };
|
|
6579
|
+
} catch (error) {
|
|
6580
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6581
|
+
console.error("Vault deposit failed:", message);
|
|
6582
|
+
return { success: false, error: message };
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
/**
|
|
6586
|
+
* Withdraw USDC from the prediction vault.
|
|
6587
|
+
* Redeems all shares and returns the USDC received (net of fees).
|
|
6588
|
+
*/
|
|
6589
|
+
async withdrawAll() {
|
|
6590
|
+
if (!this.vaultAddress) {
|
|
6591
|
+
return { success: false, error: "No vault exists" };
|
|
6592
|
+
}
|
|
6593
|
+
try {
|
|
6594
|
+
const hash = await this.walletClient.writeContract({
|
|
6595
|
+
address: this.vaultAddress,
|
|
6596
|
+
abi: VAULT_ABI2,
|
|
6597
|
+
functionName: "redeemMax",
|
|
6598
|
+
args: [this.account.address, this.account.address]
|
|
6599
|
+
});
|
|
6600
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6601
|
+
const balance = await this.publicClient.readContract({
|
|
6602
|
+
address: POLYGON_USDC_ADDRESS,
|
|
6603
|
+
abi: ERC20_ABI3,
|
|
6604
|
+
functionName: "balanceOf",
|
|
6605
|
+
args: [this.account.address]
|
|
6606
|
+
});
|
|
6607
|
+
console.log("Withdrew all shares from prediction vault");
|
|
6608
|
+
return { success: true, assets: balance, txHash: hash };
|
|
6609
|
+
} catch (error) {
|
|
6610
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6611
|
+
console.error("Vault withdrawal failed:", message);
|
|
6612
|
+
return { success: false, error: message };
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6615
|
+
/**
|
|
6616
|
+
* Emergency withdraw with penalty.
|
|
6617
|
+
*/
|
|
6618
|
+
async emergencyWithdraw() {
|
|
6619
|
+
if (!this.vaultAddress) {
|
|
6620
|
+
return { success: false, error: "No vault exists" };
|
|
6621
|
+
}
|
|
6622
|
+
try {
|
|
6623
|
+
const hash = await this.walletClient.writeContract({
|
|
6624
|
+
address: this.vaultAddress,
|
|
6625
|
+
abi: VAULT_ABI2,
|
|
6626
|
+
functionName: "emergencyWithdraw",
|
|
6627
|
+
args: []
|
|
6628
|
+
});
|
|
6629
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6630
|
+
console.log("Emergency withdrew from prediction vault (penalty applied)");
|
|
6631
|
+
return { success: true, txHash: hash };
|
|
6632
|
+
} catch (error) {
|
|
6633
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6634
|
+
return { success: false, error: message };
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
// ============================================================
|
|
6638
|
+
// VAULT STATUS
|
|
6639
|
+
// ============================================================
|
|
6640
|
+
/**
|
|
6641
|
+
* Get comprehensive vault status.
|
|
6642
|
+
*/
|
|
6643
|
+
async getVaultStatus() {
|
|
6644
|
+
if (!this.vaultAddress) {
|
|
6645
|
+
return {
|
|
6646
|
+
vaultAddress: "0x0000000000000000000000000000000000000000",
|
|
6647
|
+
hasVault: false,
|
|
6648
|
+
totalAssets: 0n,
|
|
6649
|
+
totalSupply: 0n,
|
|
6650
|
+
sharePrice: 0,
|
|
6651
|
+
usdcBalance: 0n,
|
|
6652
|
+
cachedCtfValue: 0n,
|
|
6653
|
+
currentExposureBps: 0,
|
|
6654
|
+
depositCap: 0n,
|
|
6655
|
+
seedAmount: 0n,
|
|
6656
|
+
circuitBreakerActive: false,
|
|
6657
|
+
isPaused: false,
|
|
6658
|
+
lastCtfUpdate: 0,
|
|
6659
|
+
pendingTradingVolume: 0n,
|
|
6660
|
+
trackedTokenCount: 0
|
|
6661
|
+
};
|
|
6662
|
+
}
|
|
6663
|
+
try {
|
|
6664
|
+
const [
|
|
6665
|
+
totalAssets,
|
|
6666
|
+
totalSupply,
|
|
6667
|
+
sharePrice,
|
|
6668
|
+
usdcBalance,
|
|
6669
|
+
cachedCtfValue,
|
|
6670
|
+
currentExposureBps,
|
|
6671
|
+
depositCap,
|
|
6672
|
+
seedAmount,
|
|
6673
|
+
circuitBreakerActive,
|
|
6674
|
+
isPaused,
|
|
6675
|
+
lastCtfUpdate,
|
|
6676
|
+
pendingTradingVolume,
|
|
6677
|
+
trackedTokenCount
|
|
6678
|
+
] = await Promise.all([
|
|
6679
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "totalAssets" }),
|
|
6680
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "totalSupply" }),
|
|
6681
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "sharePrice" }),
|
|
6682
|
+
this.publicClient.readContract({ address: POLYGON_USDC_ADDRESS, abi: ERC20_ABI3, functionName: "balanceOf", args: [this.vaultAddress] }),
|
|
6683
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "cachedCtfValue" }),
|
|
6684
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "currentExposureBps" }),
|
|
6685
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "depositCap" }),
|
|
6686
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "agentSeedAmount" }),
|
|
6687
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "circuitBreakerActive" }),
|
|
6688
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "paused" }),
|
|
6689
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "lastCtfUpdate" }),
|
|
6690
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "pendingTradingVolume" }),
|
|
6691
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "getTrackedTokenCount" })
|
|
6692
|
+
]);
|
|
6693
|
+
return {
|
|
6694
|
+
vaultAddress: this.vaultAddress,
|
|
6695
|
+
hasVault: true,
|
|
6696
|
+
totalAssets,
|
|
6697
|
+
totalSupply,
|
|
6698
|
+
sharePrice: Number(sharePrice) / 1e6,
|
|
6699
|
+
usdcBalance,
|
|
6700
|
+
cachedCtfValue,
|
|
6701
|
+
currentExposureBps: Number(currentExposureBps),
|
|
6702
|
+
depositCap,
|
|
6703
|
+
seedAmount,
|
|
6704
|
+
circuitBreakerActive,
|
|
6705
|
+
isPaused,
|
|
6706
|
+
lastCtfUpdate: Number(lastCtfUpdate),
|
|
6707
|
+
pendingTradingVolume,
|
|
6708
|
+
trackedTokenCount: Number(trackedTokenCount)
|
|
6709
|
+
};
|
|
6710
|
+
} catch (error) {
|
|
6711
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6712
|
+
console.error("Failed to read vault status:", message);
|
|
6713
|
+
return {
|
|
6714
|
+
vaultAddress: this.vaultAddress,
|
|
6715
|
+
hasVault: true,
|
|
6716
|
+
totalAssets: 0n,
|
|
6717
|
+
totalSupply: 0n,
|
|
6718
|
+
sharePrice: 0,
|
|
6719
|
+
usdcBalance: 0n,
|
|
6720
|
+
cachedCtfValue: 0n,
|
|
6721
|
+
currentExposureBps: 0,
|
|
6722
|
+
depositCap: 0n,
|
|
6723
|
+
seedAmount: 0n,
|
|
6724
|
+
circuitBreakerActive: false,
|
|
6725
|
+
isPaused: false,
|
|
6726
|
+
lastCtfUpdate: 0,
|
|
6727
|
+
pendingTradingVolume: 0n,
|
|
6728
|
+
trackedTokenCount: 0
|
|
6729
|
+
};
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6732
|
+
// ============================================================
|
|
6733
|
+
// KEEPER: NAV UPDATES
|
|
6734
|
+
// ============================================================
|
|
6735
|
+
/**
|
|
6736
|
+
* Update cached CTF position values (keeper function).
|
|
6737
|
+
* Fetches CLOB midpoint prices for all tracked tokens and updates the vault.
|
|
6738
|
+
*
|
|
6739
|
+
* @param tokenIds - CTF token IDs
|
|
6740
|
+
* @param values - USD values in 6-decimal (midpoint price × balance for each token)
|
|
6741
|
+
*/
|
|
6742
|
+
async updatePositionValues(tokenIds, values) {
|
|
6743
|
+
if (!this.vaultAddress) {
|
|
6744
|
+
return { success: false, error: "No vault exists" };
|
|
6745
|
+
}
|
|
6746
|
+
try {
|
|
6747
|
+
const hash = await this.walletClient.writeContract({
|
|
6748
|
+
address: this.vaultAddress,
|
|
6749
|
+
abi: VAULT_ABI2,
|
|
6750
|
+
functionName: "updatePositionValues",
|
|
6751
|
+
args: [tokenIds, values]
|
|
6752
|
+
});
|
|
6753
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6754
|
+
console.log(`Updated ${tokenIds.length} position values in prediction vault`);
|
|
6755
|
+
return { success: true, txHash: hash };
|
|
6756
|
+
} catch (error) {
|
|
6757
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6758
|
+
console.error("Failed to update position values:", message);
|
|
6759
|
+
return { success: false, error: message };
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
/**
|
|
6763
|
+
* Collect trading fees based on reported volume (keeper function).
|
|
6764
|
+
* Transfers 0.2% of volume as fees to PredictionFeeCollector.
|
|
6765
|
+
*
|
|
6766
|
+
* @param volume - Trading volume to report (6-decimal USDC)
|
|
6767
|
+
*/
|
|
6768
|
+
async collectTradingFees(volume) {
|
|
6769
|
+
if (!this.vaultAddress) {
|
|
6770
|
+
return { success: false, error: "No vault exists" };
|
|
6771
|
+
}
|
|
6772
|
+
try {
|
|
6773
|
+
const hash = await this.walletClient.writeContract({
|
|
6774
|
+
address: this.vaultAddress,
|
|
6775
|
+
abi: VAULT_ABI2,
|
|
6776
|
+
functionName: "collectTradingFees",
|
|
6777
|
+
args: [volume]
|
|
6778
|
+
});
|
|
6779
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6780
|
+
const feeAmount = volume * 20n / 10000n;
|
|
6781
|
+
console.log(`Collected trading fees: ${(0, import_viem10.formatUnits)(feeAmount, 6)} USDC from ${(0, import_viem10.formatUnits)(volume, 6)} USDC volume`);
|
|
6782
|
+
return { success: true, txHash: hash };
|
|
6783
|
+
} catch (error) {
|
|
6784
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6785
|
+
console.error("Failed to collect trading fees:", message);
|
|
6786
|
+
return { success: false, error: message };
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
// ============================================================
|
|
6790
|
+
// KEEPER LOOPS
|
|
6791
|
+
// ============================================================
|
|
6792
|
+
/**
|
|
6793
|
+
* Start the keeper loops (NAV updates + fee collection).
|
|
6794
|
+
* These run on timers and automatically update vault state.
|
|
6795
|
+
*
|
|
6796
|
+
* @param getPositionValues - Callback that returns current CTF position values
|
|
6797
|
+
* (token IDs and their midpoint-price USD values)
|
|
6798
|
+
* @param getAccumulatedVolume - Callback that returns accumulated trading volume
|
|
6799
|
+
* since last fee collection
|
|
6800
|
+
*/
|
|
6801
|
+
startKeeperLoops(getPositionValues, getAccumulatedVolume) {
|
|
6802
|
+
if (!this.vaultAddress) {
|
|
6803
|
+
console.log("Cannot start keeper loops \u2014 no vault exists");
|
|
6804
|
+
return;
|
|
6805
|
+
}
|
|
6806
|
+
const navInterval = this.config.navUpdateIntervalMs ?? 15 * 60 * 1e3;
|
|
6807
|
+
this.navTimer = setInterval(async () => {
|
|
6808
|
+
try {
|
|
6809
|
+
const { tokenIds, values } = await getPositionValues();
|
|
6810
|
+
if (tokenIds.length > 0) {
|
|
6811
|
+
await this.updatePositionValues(tokenIds, values);
|
|
6812
|
+
}
|
|
6813
|
+
} catch (error) {
|
|
6814
|
+
console.error("Keeper NAV update failed:", error instanceof Error ? error.message : error);
|
|
6815
|
+
}
|
|
6816
|
+
}, navInterval);
|
|
6817
|
+
const feeInterval = this.config.feeCollectionIntervalMs ?? 24 * 60 * 60 * 1e3;
|
|
6818
|
+
this.feeTimer = setInterval(async () => {
|
|
6819
|
+
try {
|
|
6820
|
+
const currentVolume = getAccumulatedVolume();
|
|
6821
|
+
const volumeDelta = currentVolume - this.lastReportedVolume;
|
|
6822
|
+
if (volumeDelta > 0n) {
|
|
6823
|
+
await this.collectTradingFees(volumeDelta);
|
|
6824
|
+
this.lastReportedVolume = currentVolume;
|
|
6825
|
+
}
|
|
6826
|
+
} catch (error) {
|
|
6827
|
+
console.error("Keeper fee collection failed:", error instanceof Error ? error.message : error);
|
|
6828
|
+
}
|
|
6829
|
+
}, feeInterval);
|
|
6830
|
+
console.log(`Keeper loops started (NAV: every ${navInterval / 6e4}min, fees: every ${feeInterval / 36e5}h)`);
|
|
6831
|
+
}
|
|
6832
|
+
/**
|
|
6833
|
+
* Stop keeper loops.
|
|
6834
|
+
*/
|
|
6835
|
+
stopKeeperLoops() {
|
|
6836
|
+
if (this.navTimer) {
|
|
6837
|
+
clearInterval(this.navTimer);
|
|
6838
|
+
this.navTimer = null;
|
|
6839
|
+
}
|
|
6840
|
+
if (this.feeTimer) {
|
|
6841
|
+
clearInterval(this.feeTimer);
|
|
6842
|
+
this.feeTimer = null;
|
|
6843
|
+
}
|
|
6844
|
+
console.log("Keeper loops stopped");
|
|
6845
|
+
}
|
|
6846
|
+
// ============================================================
|
|
6847
|
+
// CLEANUP
|
|
6848
|
+
// ============================================================
|
|
6849
|
+
/**
|
|
6850
|
+
* Stop all timers and clean up.
|
|
6851
|
+
*/
|
|
6852
|
+
stop() {
|
|
6853
|
+
this.stopKeeperLoops();
|
|
6854
|
+
}
|
|
6855
|
+
// ============================================================
|
|
6856
|
+
// PRIVATE HELPERS
|
|
6857
|
+
// ============================================================
|
|
6858
|
+
/** Approve USDC spend if needed (maxUint256 pattern) */
|
|
6859
|
+
async approveIfNeeded(token, spender, amount) {
|
|
6860
|
+
const allowance = await this.publicClient.readContract({
|
|
6861
|
+
address: token,
|
|
6862
|
+
abi: ERC20_ABI3,
|
|
6863
|
+
functionName: "allowance",
|
|
6864
|
+
args: [this.account.address, spender]
|
|
6865
|
+
});
|
|
6866
|
+
if (allowance < amount) {
|
|
6867
|
+
const maxUint256 = 2n ** 256n - 1n;
|
|
6868
|
+
const hash = await this.walletClient.writeContract({
|
|
6869
|
+
address: token,
|
|
6870
|
+
abi: ERC20_ABI3,
|
|
6871
|
+
functionName: "approve",
|
|
6872
|
+
args: [spender, maxUint256]
|
|
6873
|
+
});
|
|
6874
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6875
|
+
}
|
|
6876
|
+
}
|
|
6877
|
+
};
|
|
6878
|
+
|
|
6879
|
+
// src/runtime.ts
|
|
6880
|
+
var FUNDS_LOW_THRESHOLD = 5e-3;
|
|
6881
|
+
var FUNDS_CRITICAL_THRESHOLD = 1e-3;
|
|
6882
|
+
var AgentRuntime = class {
|
|
6883
|
+
config;
|
|
6884
|
+
client;
|
|
6885
|
+
llm;
|
|
6886
|
+
strategy;
|
|
6887
|
+
executor;
|
|
6888
|
+
riskManager;
|
|
6889
|
+
marketData;
|
|
6890
|
+
vaultManager;
|
|
6891
|
+
relay = null;
|
|
6892
|
+
isRunning = false;
|
|
6893
|
+
mode = "idle";
|
|
6894
|
+
configHash;
|
|
6895
|
+
pendingConfigHash = null;
|
|
6896
|
+
lastConfigCheckAt = 0;
|
|
6897
|
+
// Timestamp of last pending config RPC check
|
|
6898
|
+
cycleCount = 0;
|
|
6899
|
+
lastCycleAt = 0;
|
|
6900
|
+
lastPortfolioValue = 0;
|
|
6901
|
+
lastEthBalance = "0";
|
|
6902
|
+
lastPrices = {};
|
|
6903
|
+
processAlive = true;
|
|
6904
|
+
riskUniverse = 0;
|
|
6905
|
+
allowedTokens = /* @__PURE__ */ new Set();
|
|
6906
|
+
strategyContext;
|
|
6907
|
+
positionTracker;
|
|
6908
|
+
// Paper trading components (null when not in paper mode)
|
|
6909
|
+
paperExecutor = null;
|
|
6910
|
+
paperPortfolio = null;
|
|
6911
|
+
/** Whether agent was started directly in paper mode via CLI */
|
|
6912
|
+
startInPaperMode = false;
|
|
6913
|
+
// Perp trading components (null if perp not enabled)
|
|
6914
|
+
perpClient = null;
|
|
6915
|
+
perpSigner = null;
|
|
6916
|
+
perpOrders = null;
|
|
6917
|
+
perpPositions = null;
|
|
6918
|
+
perpWebSocket = null;
|
|
6919
|
+
perpRecorder = null;
|
|
6920
|
+
perpOnboarding = null;
|
|
6921
|
+
perpStrategy = null;
|
|
6922
|
+
// Two-layer perp control:
|
|
6923
|
+
// perpConnected = Hyperliquid infrastructure is initialized (WS, signer, recorder ready)
|
|
6924
|
+
// perpTradingActive = Dedicated perp trading cycle is mandated to run
|
|
6925
|
+
// When perpConnected && !perpTradingActive: agent's strategy can optionally return perp signals
|
|
6926
|
+
// When perpConnected && perpTradingActive: dedicated runPerpCycle() runs every interval
|
|
6927
|
+
perpConnected = false;
|
|
6928
|
+
perpTradingActive = false;
|
|
6929
|
+
// Cached perp account data for synchronous heartbeat inclusion (refreshed async)
|
|
6930
|
+
cachedPerpEquity = 0;
|
|
6931
|
+
cachedPerpUnrealizedPnl = 0;
|
|
6932
|
+
cachedPerpMarginUsed = 0;
|
|
6933
|
+
cachedPerpLeverage = 0;
|
|
6934
|
+
cachedPerpOpenPositions = 0;
|
|
6935
|
+
// Prediction market components (null if prediction not enabled)
|
|
6936
|
+
predictionClient = null;
|
|
6937
|
+
predictionOrders = null;
|
|
6938
|
+
predictionPositions = null;
|
|
6939
|
+
predictionRecorder = null;
|
|
6940
|
+
predictionFunding = null;
|
|
6941
|
+
predictionBrowser = null;
|
|
6942
|
+
predictionStrategy = null;
|
|
6943
|
+
// Two-layer prediction control (mirrors perp pattern):
|
|
6944
|
+
// predictionConnected = Polymarket infrastructure initialized (CLOB client, recorder ready)
|
|
6945
|
+
// predictionTradingActive = Dedicated prediction trading cycle is mandated to run
|
|
6946
|
+
predictionConnected = false;
|
|
6947
|
+
predictionTradingActive = false;
|
|
6948
|
+
// Cached prediction data for synchronous heartbeat inclusion
|
|
6949
|
+
cachedPredictionPolygonUSDC = 0;
|
|
6950
|
+
cachedPredictionBaseUSDC = 0;
|
|
6951
|
+
cachedPredictionExposure = 0;
|
|
6952
|
+
cachedPredictionOpenMarkets = 0;
|
|
6953
|
+
cachedPredictionOpenPositions = 0;
|
|
6954
|
+
cachedPredictionUnrealizedPnl = 0;
|
|
6955
|
+
// Prediction vault manager (null if vault mode not enabled)
|
|
6956
|
+
predictionVaultManager = null;
|
|
6957
|
+
// Cached vault status for synchronous heartbeat
|
|
6958
|
+
cachedPredictionVaultStatus = void 0;
|
|
6959
|
+
constructor(config, options) {
|
|
6960
|
+
this.config = config;
|
|
6961
|
+
this.startInPaperMode = options?.paperMode ?? false;
|
|
6962
|
+
this._paperInitialBalances = options?.paperBalances;
|
|
6963
|
+
}
|
|
6964
|
+
/** Initial balances override for paper mode (from CLI --initial-eth/--initial-usdc) */
|
|
6965
|
+
_paperInitialBalances;
|
|
6966
|
+
/**
|
|
6967
|
+
* Initialize the agent runtime
|
|
6968
|
+
*/
|
|
6969
|
+
async initialize() {
|
|
6970
|
+
console.log(`Initializing agent: ${this.config.name} (ID: ${this.config.agentId})`);
|
|
6971
|
+
this.client = new import_sdk.ExagentClient({
|
|
6972
|
+
privateKey: this.config.privateKey,
|
|
6973
|
+
network: this.config.network
|
|
6974
|
+
});
|
|
6975
|
+
console.log(`Wallet: ${this.client.address}`);
|
|
6976
|
+
const agent = await this.client.registry.getAgent(BigInt(this.config.agentId));
|
|
6977
|
+
if (!agent) {
|
|
6978
|
+
throw new Error(`Agent ID ${this.config.agentId} not found on-chain. Please register first.`);
|
|
6979
|
+
}
|
|
6980
|
+
console.log(`Agent verified: ${agent.name}`);
|
|
6981
|
+
await this.ensureWalletLinked();
|
|
6982
|
+
await this.loadTradingRestrictions();
|
|
6983
|
+
console.log(`Initializing LLM: ${this.config.llm.provider}`);
|
|
6984
|
+
this.llm = await createLLMAdapter(this.config.llm);
|
|
6985
|
+
const llmMeta = this.llm.getMetadata();
|
|
6986
|
+
console.log(`LLM ready: ${llmMeta.provider} (${llmMeta.model})`);
|
|
6987
|
+
await this.syncConfigHash();
|
|
6988
|
+
this.strategy = await loadStrategy();
|
|
6989
|
+
const store = new FileStore();
|
|
6990
|
+
this.strategyContext = {
|
|
6991
|
+
store,
|
|
6992
|
+
agentId: Number(this.config.agentId),
|
|
6993
|
+
walletAddress: this.client.address
|
|
6994
|
+
};
|
|
6995
|
+
this.positionTracker = new PositionTracker(store, { maxTradeHistory: 50 });
|
|
6996
|
+
this.executor = new TradeExecutor(this.client, this.config, () => this.getConfigHash());
|
|
4472
6997
|
this.riskManager = new RiskManager(this.config.trading, this.riskUniverse);
|
|
4473
6998
|
this.marketData = new MarketDataService(this.getRpcUrl(), store);
|
|
4474
6999
|
setGlobalResolver(this.marketData.getResolver());
|
|
@@ -4478,6 +7003,7 @@ var AgentRuntime = class {
|
|
|
4478
7003
|
}
|
|
4479
7004
|
await this.initializeVaultManager();
|
|
4480
7005
|
await this.initializePerp();
|
|
7006
|
+
await this.initializePrediction();
|
|
4481
7007
|
await this.initializeRelay();
|
|
4482
7008
|
console.log("Agent initialized successfully");
|
|
4483
7009
|
}
|
|
@@ -4567,10 +7093,10 @@ var AgentRuntime = class {
|
|
|
4567
7093
|
};
|
|
4568
7094
|
this.perpClient = new HyperliquidClient(config);
|
|
4569
7095
|
const perpKey = perpConfig.perpRelayerKey || this.config.privateKey;
|
|
4570
|
-
const account = (0,
|
|
4571
|
-
const walletClient = (0,
|
|
7096
|
+
const account = (0, import_accounts8.privateKeyToAccount)(perpKey);
|
|
7097
|
+
const walletClient = (0, import_viem11.createWalletClient)({
|
|
4572
7098
|
chain: { id: 42161, name: "Arbitrum", nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: ["https://arb1.arbitrum.io/rpc"] } } },
|
|
4573
|
-
transport: (0,
|
|
7099
|
+
transport: (0, import_viem11.http)("https://arb1.arbitrum.io/rpc", { timeout: 6e4 }),
|
|
4574
7100
|
account
|
|
4575
7101
|
});
|
|
4576
7102
|
this.perpSigner = new HyperliquidSigner(walletClient);
|
|
@@ -4646,6 +7172,101 @@ var AgentRuntime = class {
|
|
|
4646
7172
|
console.warn("Perp trading will be disabled for this session");
|
|
4647
7173
|
}
|
|
4648
7174
|
}
|
|
7175
|
+
/**
|
|
7176
|
+
* Initialize Polymarket prediction market components.
|
|
7177
|
+
* Only initializes if prediction is enabled in config AND risk universe >= 2.
|
|
7178
|
+
*/
|
|
7179
|
+
async initializePrediction() {
|
|
7180
|
+
const predictionConfig = this.config.prediction;
|
|
7181
|
+
if (!predictionConfig?.enabled) {
|
|
7182
|
+
console.log("Prediction markets: Disabled");
|
|
7183
|
+
return;
|
|
7184
|
+
}
|
|
7185
|
+
if (this.riskUniverse < 2) {
|
|
7186
|
+
console.warn(`Prediction markets: Blocked by risk universe ${this.riskUniverse} (need >= 2)`);
|
|
7187
|
+
return;
|
|
7188
|
+
}
|
|
7189
|
+
try {
|
|
7190
|
+
const config = {
|
|
7191
|
+
enabled: true,
|
|
7192
|
+
clobApiUrl: predictionConfig.clobApiUrl || "https://clob.polymarket.com",
|
|
7193
|
+
gammaApiUrl: predictionConfig.gammaApiUrl || "https://gamma-api.polymarket.com",
|
|
7194
|
+
polygonRpcUrl: predictionConfig.polygonRpcUrl || "https://polygon-rpc.com",
|
|
7195
|
+
maxNotionalUSD: predictionConfig.maxNotionalUSD ?? 500,
|
|
7196
|
+
maxTotalExposureUSD: predictionConfig.maxTotalExposureUSD ?? 5e3,
|
|
7197
|
+
polygonAllocationUSD: predictionConfig.polygonAllocationUSD ?? 100,
|
|
7198
|
+
allowedCategories: predictionConfig.allowedCategories,
|
|
7199
|
+
feeBridgeThresholdUSD: predictionConfig.feeBridgeThresholdUSD ?? 10
|
|
7200
|
+
};
|
|
7201
|
+
const vaultConfig = predictionConfig.vault;
|
|
7202
|
+
let vaultOpts;
|
|
7203
|
+
if (vaultConfig?.enabled && vaultConfig.factoryAddress) {
|
|
7204
|
+
this.predictionVaultManager = new PredictionVaultManager(
|
|
7205
|
+
this.config.privateKey,
|
|
7206
|
+
{
|
|
7207
|
+
enabled: true,
|
|
7208
|
+
factoryAddress: vaultConfig.factoryAddress,
|
|
7209
|
+
feeCollectorAddress: vaultConfig.feeCollectorAddress || "0x0000000000000000000000000000000000000000",
|
|
7210
|
+
polygonRpcUrl: predictionConfig.polygonRpcUrl || "https://polygon-rpc.com",
|
|
7211
|
+
vaultAddress: vaultConfig.vaultAddress,
|
|
7212
|
+
agentId: BigInt(this.config.agentId),
|
|
7213
|
+
feeRecipient: vaultConfig.feeRecipient || this.client.address,
|
|
7214
|
+
isVerified: vaultConfig.isVerified ?? false
|
|
7215
|
+
}
|
|
7216
|
+
);
|
|
7217
|
+
await this.predictionVaultManager.initialize();
|
|
7218
|
+
if (this.predictionVaultManager.hasVault) {
|
|
7219
|
+
const vaultAddr = this.predictionVaultManager.getVaultAddress();
|
|
7220
|
+
vaultOpts = { vaultMode: true, vaultAddress: vaultAddr };
|
|
7221
|
+
console.log(` Vault mode: ENABLED \u2014 vault ${vaultAddr}`);
|
|
7222
|
+
} else {
|
|
7223
|
+
console.log(" Vault mode: configured but no vault exists \u2014 create one via command center");
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
this.predictionClient = new PolymarketClient(this.config.privateKey, config, vaultOpts);
|
|
7227
|
+
await this.predictionClient.initialize();
|
|
7228
|
+
this.predictionOrders = new PredictionOrderManager(this.predictionClient, config);
|
|
7229
|
+
this.predictionPositions = new PredictionPositionManager(this.predictionClient, config);
|
|
7230
|
+
this.predictionBrowser = new MarketBrowser(config);
|
|
7231
|
+
const recorderKey = predictionConfig.predictionRelayerKey || this.config.privateKey;
|
|
7232
|
+
this.predictionRecorder = new PredictionTradeRecorder({
|
|
7233
|
+
privateKey: recorderKey,
|
|
7234
|
+
rpcUrl: this.getRpcUrl(),
|
|
7235
|
+
agentId: BigInt(this.config.agentId),
|
|
7236
|
+
configHash: this.configHash
|
|
7237
|
+
});
|
|
7238
|
+
this.predictionFunding = new PredictionFunding({
|
|
7239
|
+
privateKey: this.config.privateKey,
|
|
7240
|
+
baseRpcUrl: this.getRpcUrl(),
|
|
7241
|
+
polygonRpcUrl: predictionConfig.polygonRpcUrl,
|
|
7242
|
+
...vaultOpts?.vaultMode && vaultOpts?.vaultAddress && {
|
|
7243
|
+
polygonRecipient: vaultOpts.vaultAddress
|
|
7244
|
+
}
|
|
7245
|
+
});
|
|
7246
|
+
this.predictionConnected = true;
|
|
7247
|
+
console.log(`Polymarket: Connected`);
|
|
7248
|
+
console.log(` CLOB: ${config.clobApiUrl}`);
|
|
7249
|
+
console.log(` Max notional: $${config.maxNotionalUSD}, Max exposure: $${config.maxTotalExposureUSD}`);
|
|
7250
|
+
if (config.allowedCategories?.length) {
|
|
7251
|
+
console.log(` Categories: ${config.allowedCategories.join(", ")}`);
|
|
7252
|
+
}
|
|
7253
|
+
try {
|
|
7254
|
+
const polygonBalance = await this.predictionFunding.getPolygonUSDCBalance();
|
|
7255
|
+
const polygonUSDC = Number(polygonBalance) / 1e6;
|
|
7256
|
+
this.cachedPredictionPolygonUSDC = polygonUSDC;
|
|
7257
|
+
console.log(` Polygon USDC: $${polygonUSDC.toFixed(2)}`);
|
|
7258
|
+
if (polygonUSDC < 1) {
|
|
7259
|
+
console.warn(" No USDC on Polygon \u2014 bridge required before trading");
|
|
7260
|
+
}
|
|
7261
|
+
} catch {
|
|
7262
|
+
console.warn(" Could not check Polygon USDC balance");
|
|
7263
|
+
}
|
|
7264
|
+
} catch (error) {
|
|
7265
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7266
|
+
console.error(`Prediction initialization failed: ${message}`);
|
|
7267
|
+
console.warn("Prediction market trading will be disabled for this session");
|
|
7268
|
+
}
|
|
7269
|
+
}
|
|
4649
7270
|
/**
|
|
4650
7271
|
* Ensure the current wallet is linked to the agent.
|
|
4651
7272
|
* If the trading wallet differs from the owner, enters a recovery loop
|
|
@@ -4810,9 +7431,9 @@ var AgentRuntime = class {
|
|
|
4810
7431
|
const message = error instanceof Error ? error.message : String(error);
|
|
4811
7432
|
if (message.includes("insufficient funds") || message.includes("intrinsic gas too low") || message.includes("exceeds the balance")) {
|
|
4812
7433
|
const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
|
|
4813
|
-
const publicClientInstance = (0,
|
|
4814
|
-
chain:
|
|
4815
|
-
transport: (0,
|
|
7434
|
+
const publicClientInstance = (0, import_viem11.createPublicClient)({
|
|
7435
|
+
chain: import_chains8.base,
|
|
7436
|
+
transport: (0, import_viem11.http)(this.getRpcUrl(), { timeout: 6e4 })
|
|
4816
7437
|
});
|
|
4817
7438
|
console.log("");
|
|
4818
7439
|
console.log("=== ETH NEEDED FOR GAS ===");
|
|
@@ -4890,16 +7511,20 @@ var AgentRuntime = class {
|
|
|
4890
7511
|
} catch (error) {
|
|
4891
7512
|
console.warn("Initial balance sync failed (non-fatal):", error instanceof Error ? error.message : error);
|
|
4892
7513
|
}
|
|
7514
|
+
if (this.startInPaperMode) {
|
|
7515
|
+
await this.startPaperTrading(this._paperInitialBalances);
|
|
7516
|
+
}
|
|
4893
7517
|
this.sendRelayStatus();
|
|
4894
7518
|
this.relay.sendMessage(
|
|
4895
7519
|
"system",
|
|
4896
7520
|
"success",
|
|
4897
7521
|
"Agent Connected",
|
|
4898
|
-
`${this.config.name} is online and waiting for commands.`,
|
|
7522
|
+
`${this.config.name} is online${this.mode === "paper" ? " in PAPER TRADING mode" : " and waiting for commands"}.`,
|
|
4899
7523
|
{ wallet: this.client.address }
|
|
4900
7524
|
);
|
|
4901
7525
|
while (this.processAlive) {
|
|
4902
|
-
|
|
7526
|
+
const currentMode = this.mode;
|
|
7527
|
+
if ((currentMode === "trading" || currentMode === "paper") && this.isRunning) {
|
|
4903
7528
|
try {
|
|
4904
7529
|
await this.runCycle();
|
|
4905
7530
|
} catch (error) {
|
|
@@ -4923,8 +7548,13 @@ var AgentRuntime = class {
|
|
|
4923
7548
|
throw new Error("Agent is already running");
|
|
4924
7549
|
}
|
|
4925
7550
|
this.isRunning = true;
|
|
4926
|
-
this.
|
|
4927
|
-
|
|
7551
|
+
if (this.startInPaperMode) {
|
|
7552
|
+
await this.startPaperTrading(this._paperInitialBalances);
|
|
7553
|
+
console.log("Starting paper trading loop...");
|
|
7554
|
+
} else {
|
|
7555
|
+
this.mode = "trading";
|
|
7556
|
+
console.log("Starting trading loop...");
|
|
7557
|
+
}
|
|
4928
7558
|
console.log(`Interval: ${this.config.trading.tradingIntervalMs}ms`);
|
|
4929
7559
|
while (this.isRunning) {
|
|
4930
7560
|
try {
|
|
@@ -5070,107 +7700,360 @@ var AgentRuntime = class {
|
|
|
5070
7700
|
'Hyperliquid infrastructure connected. Agent can now include perp signals in its strategy. Use "Start Perp Trading" to mandate a dedicated perp cycle.'
|
|
5071
7701
|
);
|
|
5072
7702
|
} else {
|
|
5073
|
-
this.relay?.sendCommandResult(cmd.id, false, "Failed to connect to Hyperliquid");
|
|
7703
|
+
this.relay?.sendCommandResult(cmd.id, false, "Failed to connect to Hyperliquid");
|
|
7704
|
+
}
|
|
7705
|
+
} catch (error) {
|
|
7706
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
7707
|
+
this.relay?.sendCommandResult(cmd.id, false, `Hyperliquid init failed: ${msg}`);
|
|
7708
|
+
}
|
|
7709
|
+
this.sendRelayStatus();
|
|
7710
|
+
break;
|
|
7711
|
+
case "disable_hyperliquid":
|
|
7712
|
+
if (!this.perpConnected) {
|
|
7713
|
+
this.relay?.sendCommandResult(cmd.id, true, "Hyperliquid already disconnected");
|
|
7714
|
+
break;
|
|
7715
|
+
}
|
|
7716
|
+
this.perpTradingActive = false;
|
|
7717
|
+
if (this.perpWebSocket) {
|
|
7718
|
+
this.perpWebSocket.disconnect();
|
|
7719
|
+
}
|
|
7720
|
+
if (this.perpRecorder) {
|
|
7721
|
+
this.perpRecorder.stop();
|
|
7722
|
+
}
|
|
7723
|
+
this.perpConnected = false;
|
|
7724
|
+
console.log("Hyperliquid disabled via command center");
|
|
7725
|
+
this.relay?.sendCommandResult(cmd.id, true, "Hyperliquid disconnected");
|
|
7726
|
+
this.relay?.sendMessage(
|
|
7727
|
+
"system",
|
|
7728
|
+
"info",
|
|
7729
|
+
"Hyperliquid Disabled",
|
|
7730
|
+
"Hyperliquid infrastructure disconnected. Agent will trade spot only."
|
|
7731
|
+
);
|
|
7732
|
+
this.sendRelayStatus();
|
|
7733
|
+
break;
|
|
7734
|
+
case "start_perp_trading":
|
|
7735
|
+
if (!this.perpConnected) {
|
|
7736
|
+
this.relay?.sendCommandResult(cmd.id, false, "Hyperliquid not connected. Enable Hyperliquid first.");
|
|
7737
|
+
break;
|
|
7738
|
+
}
|
|
7739
|
+
if (this.perpTradingActive) {
|
|
7740
|
+
this.relay?.sendCommandResult(cmd.id, true, "Perp trading already active");
|
|
7741
|
+
break;
|
|
7742
|
+
}
|
|
7743
|
+
this.perpTradingActive = true;
|
|
7744
|
+
if (this.mode !== "trading") {
|
|
7745
|
+
this.mode = "trading";
|
|
7746
|
+
this.isRunning = true;
|
|
7747
|
+
}
|
|
7748
|
+
console.log("Perp trading mandated via command center");
|
|
7749
|
+
this.relay?.sendCommandResult(cmd.id, true, "Perp trading cycle active");
|
|
7750
|
+
this.relay?.sendMessage(
|
|
7751
|
+
"system",
|
|
7752
|
+
"success",
|
|
7753
|
+
"Perp Trading Active",
|
|
7754
|
+
"Dedicated perp trading cycle is now running every interval."
|
|
7755
|
+
);
|
|
7756
|
+
this.sendRelayStatus();
|
|
7757
|
+
break;
|
|
7758
|
+
case "stop_perp_trading":
|
|
7759
|
+
if (!this.perpTradingActive) {
|
|
7760
|
+
this.relay?.sendCommandResult(cmd.id, true, "Perp trading already stopped");
|
|
7761
|
+
break;
|
|
7762
|
+
}
|
|
7763
|
+
this.perpTradingActive = false;
|
|
7764
|
+
console.log("Perp trading cycle stopped via command center");
|
|
7765
|
+
this.relay?.sendCommandResult(cmd.id, true, "Perp trading cycle stopped");
|
|
7766
|
+
this.relay?.sendMessage(
|
|
7767
|
+
"system",
|
|
7768
|
+
"info",
|
|
7769
|
+
"Perp Trading Stopped",
|
|
7770
|
+
"Dedicated perp cycle stopped. Hyperliquid remains connected \u2014 strategy can still include perp signals."
|
|
7771
|
+
);
|
|
7772
|
+
this.sendRelayStatus();
|
|
7773
|
+
break;
|
|
7774
|
+
case "update_perp_params": {
|
|
7775
|
+
const perpParams = cmd.params || {};
|
|
7776
|
+
let perpUpdated = false;
|
|
7777
|
+
if (this.config.perp && perpParams.maxLeverage !== void 0) {
|
|
7778
|
+
const val = Number(perpParams.maxLeverage);
|
|
7779
|
+
if (val >= 1 && val <= 50) {
|
|
7780
|
+
this.config.perp.maxLeverage = val;
|
|
7781
|
+
perpUpdated = true;
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
if (this.config.perp && perpParams.maxNotionalUSD !== void 0) {
|
|
7785
|
+
const val = Number(perpParams.maxNotionalUSD);
|
|
7786
|
+
if (val >= 100) {
|
|
7787
|
+
this.config.perp.maxNotionalUSD = val;
|
|
7788
|
+
perpUpdated = true;
|
|
7789
|
+
}
|
|
7790
|
+
}
|
|
7791
|
+
if (perpUpdated) {
|
|
7792
|
+
this.relay?.sendCommandResult(cmd.id, true, "Perp parameters updated");
|
|
7793
|
+
this.relay?.sendMessage(
|
|
7794
|
+
"config_updated",
|
|
7795
|
+
"info",
|
|
7796
|
+
"Perp Params Updated",
|
|
7797
|
+
`Max leverage: ${this.config.perp?.maxLeverage}x, Max notional: $${this.config.perp?.maxNotionalUSD?.toLocaleString()}`
|
|
7798
|
+
);
|
|
7799
|
+
} else {
|
|
7800
|
+
this.relay?.sendCommandResult(cmd.id, false, "No valid perp parameters provided");
|
|
7801
|
+
}
|
|
7802
|
+
break;
|
|
7803
|
+
}
|
|
7804
|
+
case "enable_polymarket":
|
|
7805
|
+
if (this.predictionConnected) {
|
|
7806
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket already connected");
|
|
7807
|
+
break;
|
|
7808
|
+
}
|
|
7809
|
+
if (!this.config.prediction?.enabled) {
|
|
7810
|
+
this.relay?.sendCommandResult(cmd.id, false, "Prediction trading not configured in agent config");
|
|
7811
|
+
break;
|
|
7812
|
+
}
|
|
7813
|
+
if (this.riskUniverse < 2) {
|
|
7814
|
+
this.relay?.sendCommandResult(cmd.id, false, `Risk universe ${this.riskUniverse} too low (need >= 2)`);
|
|
7815
|
+
break;
|
|
7816
|
+
}
|
|
7817
|
+
try {
|
|
7818
|
+
await this.initializePrediction();
|
|
7819
|
+
if (this.predictionConnected) {
|
|
7820
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket connected");
|
|
7821
|
+
this.relay?.sendMessage(
|
|
7822
|
+
"system",
|
|
7823
|
+
"success",
|
|
7824
|
+
"Polymarket Enabled",
|
|
7825
|
+
'Polymarket infrastructure connected. Use "Start Prediction Trading" to begin a dedicated prediction cycle.'
|
|
7826
|
+
);
|
|
7827
|
+
} else {
|
|
7828
|
+
this.relay?.sendCommandResult(cmd.id, false, "Failed to connect to Polymarket");
|
|
5074
7829
|
}
|
|
5075
7830
|
} catch (error) {
|
|
5076
7831
|
const msg = error instanceof Error ? error.message : String(error);
|
|
5077
|
-
this.relay?.sendCommandResult(cmd.id, false, `
|
|
7832
|
+
this.relay?.sendCommandResult(cmd.id, false, `Polymarket init failed: ${msg}`);
|
|
5078
7833
|
}
|
|
5079
7834
|
this.sendRelayStatus();
|
|
5080
7835
|
break;
|
|
5081
|
-
case "
|
|
5082
|
-
if (!this.
|
|
5083
|
-
this.relay?.sendCommandResult(cmd.id, true, "
|
|
7836
|
+
case "disable_polymarket":
|
|
7837
|
+
if (!this.predictionConnected) {
|
|
7838
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket already disconnected");
|
|
5084
7839
|
break;
|
|
5085
7840
|
}
|
|
5086
|
-
this.
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
this.
|
|
5094
|
-
console.log("
|
|
5095
|
-
this.relay?.sendCommandResult(cmd.id, true, "
|
|
7841
|
+
this.predictionTradingActive = false;
|
|
7842
|
+
this.predictionConnected = false;
|
|
7843
|
+
this.predictionClient = null;
|
|
7844
|
+
this.predictionOrders = null;
|
|
7845
|
+
this.predictionPositions = null;
|
|
7846
|
+
this.predictionRecorder = null;
|
|
7847
|
+
this.predictionFunding = null;
|
|
7848
|
+
this.predictionBrowser = null;
|
|
7849
|
+
console.log("Polymarket disabled via command center");
|
|
7850
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket disconnected");
|
|
5096
7851
|
this.relay?.sendMessage(
|
|
5097
7852
|
"system",
|
|
5098
7853
|
"info",
|
|
5099
|
-
"
|
|
5100
|
-
"
|
|
7854
|
+
"Polymarket Disabled",
|
|
7855
|
+
"Polymarket infrastructure disconnected."
|
|
5101
7856
|
);
|
|
5102
7857
|
this.sendRelayStatus();
|
|
5103
7858
|
break;
|
|
5104
|
-
case "
|
|
5105
|
-
if (!this.
|
|
5106
|
-
this.relay?.sendCommandResult(cmd.id, false, "
|
|
7859
|
+
case "start_prediction_trading":
|
|
7860
|
+
if (!this.predictionConnected) {
|
|
7861
|
+
this.relay?.sendCommandResult(cmd.id, false, "Polymarket not connected. Enable Polymarket first.");
|
|
5107
7862
|
break;
|
|
5108
7863
|
}
|
|
5109
|
-
if (this.
|
|
5110
|
-
this.relay?.sendCommandResult(cmd.id, true, "
|
|
7864
|
+
if (this.predictionTradingActive) {
|
|
7865
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading already active");
|
|
5111
7866
|
break;
|
|
5112
7867
|
}
|
|
5113
|
-
this.
|
|
7868
|
+
this.predictionTradingActive = true;
|
|
5114
7869
|
if (this.mode !== "trading") {
|
|
5115
7870
|
this.mode = "trading";
|
|
5116
7871
|
this.isRunning = true;
|
|
5117
7872
|
}
|
|
5118
|
-
console.log("
|
|
5119
|
-
this.relay?.sendCommandResult(cmd.id, true, "
|
|
7873
|
+
console.log("Prediction trading mandated via command center");
|
|
7874
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading cycle active");
|
|
5120
7875
|
this.relay?.sendMessage(
|
|
5121
7876
|
"system",
|
|
5122
7877
|
"success",
|
|
5123
|
-
"
|
|
5124
|
-
"Dedicated
|
|
7878
|
+
"Prediction Trading Active",
|
|
7879
|
+
"Dedicated prediction trading cycle is now running every interval."
|
|
5125
7880
|
);
|
|
5126
7881
|
this.sendRelayStatus();
|
|
5127
7882
|
break;
|
|
5128
|
-
case "
|
|
5129
|
-
if (!this.
|
|
5130
|
-
this.relay?.sendCommandResult(cmd.id, true, "
|
|
7883
|
+
case "stop_prediction_trading":
|
|
7884
|
+
if (!this.predictionTradingActive) {
|
|
7885
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading already stopped");
|
|
5131
7886
|
break;
|
|
5132
7887
|
}
|
|
5133
|
-
this.
|
|
5134
|
-
console.log("
|
|
5135
|
-
this.relay?.sendCommandResult(cmd.id, true, "
|
|
7888
|
+
this.predictionTradingActive = false;
|
|
7889
|
+
console.log("Prediction trading cycle stopped via command center");
|
|
7890
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading cycle stopped");
|
|
5136
7891
|
this.relay?.sendMessage(
|
|
5137
7892
|
"system",
|
|
5138
7893
|
"info",
|
|
5139
|
-
"
|
|
5140
|
-
"Dedicated
|
|
7894
|
+
"Prediction Trading Stopped",
|
|
7895
|
+
"Dedicated prediction cycle stopped. Polymarket remains connected."
|
|
5141
7896
|
);
|
|
5142
7897
|
this.sendRelayStatus();
|
|
5143
7898
|
break;
|
|
5144
|
-
case "
|
|
5145
|
-
const
|
|
5146
|
-
let
|
|
5147
|
-
if (this.config.
|
|
5148
|
-
const val = Number(
|
|
5149
|
-
if (val >= 1
|
|
5150
|
-
this.config.
|
|
5151
|
-
|
|
7899
|
+
case "update_prediction_params": {
|
|
7900
|
+
const predParams = cmd.params || {};
|
|
7901
|
+
let predUpdated = false;
|
|
7902
|
+
if (this.config.prediction && predParams.maxNotionalUSD !== void 0) {
|
|
7903
|
+
const val = Number(predParams.maxNotionalUSD);
|
|
7904
|
+
if (val >= 1) {
|
|
7905
|
+
this.config.prediction.maxNotionalUSD = val;
|
|
7906
|
+
predUpdated = true;
|
|
5152
7907
|
}
|
|
5153
7908
|
}
|
|
5154
|
-
if (this.config.
|
|
5155
|
-
const val = Number(
|
|
5156
|
-
if (val >=
|
|
5157
|
-
this.config.
|
|
5158
|
-
|
|
7909
|
+
if (this.config.prediction && predParams.maxTotalExposureUSD !== void 0) {
|
|
7910
|
+
const val = Number(predParams.maxTotalExposureUSD);
|
|
7911
|
+
if (val >= 1) {
|
|
7912
|
+
this.config.prediction.maxTotalExposureUSD = val;
|
|
7913
|
+
predUpdated = true;
|
|
5159
7914
|
}
|
|
5160
7915
|
}
|
|
5161
|
-
if (
|
|
5162
|
-
this.relay?.sendCommandResult(cmd.id, true, "
|
|
7916
|
+
if (predUpdated) {
|
|
7917
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction parameters updated");
|
|
5163
7918
|
this.relay?.sendMessage(
|
|
5164
7919
|
"config_updated",
|
|
5165
7920
|
"info",
|
|
5166
|
-
"
|
|
5167
|
-
`Max
|
|
7921
|
+
"Prediction Params Updated",
|
|
7922
|
+
`Max notional: $${this.config.prediction?.maxNotionalUSD}, Max exposure: $${this.config.prediction?.maxTotalExposureUSD}`
|
|
5168
7923
|
);
|
|
5169
7924
|
} else {
|
|
5170
|
-
this.relay?.sendCommandResult(cmd.id, false, "No valid
|
|
7925
|
+
this.relay?.sendCommandResult(cmd.id, false, "No valid prediction parameters provided");
|
|
7926
|
+
}
|
|
7927
|
+
break;
|
|
7928
|
+
}
|
|
7929
|
+
case "create_prediction_vault": {
|
|
7930
|
+
if (!this.predictionVaultManager) {
|
|
7931
|
+
this.relay?.sendCommandResult(cmd.id, false, "Vault mode not configured. Set prediction.vault config.");
|
|
7932
|
+
break;
|
|
7933
|
+
}
|
|
7934
|
+
if (this.predictionVaultManager.hasVault) {
|
|
7935
|
+
const addr = this.predictionVaultManager.getVaultAddress();
|
|
7936
|
+
this.relay?.sendCommandResult(cmd.id, true, `Vault already exists: ${addr}`);
|
|
7937
|
+
break;
|
|
7938
|
+
}
|
|
7939
|
+
try {
|
|
7940
|
+
const vaultParams = cmd.params || {};
|
|
7941
|
+
const seedAmount = BigInt(Number(vaultParams.seedAmount || 100) * 1e6);
|
|
7942
|
+
const name = vaultParams.name || `Prediction Fund ${this.config.agentId}`;
|
|
7943
|
+
const symbol = vaultParams.symbol || `pxAGT${this.config.agentId}`;
|
|
7944
|
+
const result = await this.predictionVaultManager.createVault(seedAmount, name, symbol);
|
|
7945
|
+
if (result.success) {
|
|
7946
|
+
this.relay?.sendCommandResult(cmd.id, true, `Vault created: ${result.vaultAddress}`);
|
|
7947
|
+
this.relay?.sendMessage(
|
|
7948
|
+
"prediction_vault_created",
|
|
7949
|
+
"success",
|
|
7950
|
+
"Prediction Vault Created",
|
|
7951
|
+
`Vault deployed at ${result.vaultAddress} with ${Number(seedAmount) / 1e6} USDC seed`,
|
|
7952
|
+
{ vaultAddress: result.vaultAddress, txHash: result.txHash }
|
|
7953
|
+
);
|
|
7954
|
+
} else {
|
|
7955
|
+
this.relay?.sendCommandResult(cmd.id, false, result.error || "Vault creation failed");
|
|
7956
|
+
}
|
|
7957
|
+
} catch (error) {
|
|
7958
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
7959
|
+
this.relay?.sendCommandResult(cmd.id, false, `Vault creation failed: ${msg}`);
|
|
7960
|
+
}
|
|
7961
|
+
this.sendRelayStatus();
|
|
7962
|
+
break;
|
|
7963
|
+
}
|
|
7964
|
+
case "deposit_prediction_vault": {
|
|
7965
|
+
if (!this.predictionVaultManager?.hasVault) {
|
|
7966
|
+
this.relay?.sendCommandResult(cmd.id, false, "No prediction vault exists");
|
|
7967
|
+
break;
|
|
7968
|
+
}
|
|
7969
|
+
try {
|
|
7970
|
+
const amount = BigInt(Number(cmd.params?.amount || 0) * 1e6);
|
|
7971
|
+
if (amount <= 0n) {
|
|
7972
|
+
this.relay?.sendCommandResult(cmd.id, false, "Amount must be positive");
|
|
7973
|
+
break;
|
|
7974
|
+
}
|
|
7975
|
+
const result = await this.predictionVaultManager.deposit(amount);
|
|
7976
|
+
if (result.success) {
|
|
7977
|
+
this.relay?.sendCommandResult(cmd.id, true, `Deposited ${Number(amount) / 1e6} USDC`);
|
|
7978
|
+
this.relay?.sendMessage(
|
|
7979
|
+
"prediction_vault_deposit",
|
|
7980
|
+
"success",
|
|
7981
|
+
"Vault Deposit",
|
|
7982
|
+
`${Number(amount) / 1e6} USDC deposited into prediction vault`,
|
|
7983
|
+
{ amount: Number(amount) / 1e6, txHash: result.txHash }
|
|
7984
|
+
);
|
|
7985
|
+
} else {
|
|
7986
|
+
this.relay?.sendCommandResult(cmd.id, false, result.error || "Deposit failed");
|
|
7987
|
+
}
|
|
7988
|
+
} catch (error) {
|
|
7989
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
7990
|
+
this.relay?.sendCommandResult(cmd.id, false, `Deposit failed: ${msg}`);
|
|
7991
|
+
}
|
|
7992
|
+
this.sendRelayStatus();
|
|
7993
|
+
break;
|
|
7994
|
+
}
|
|
7995
|
+
case "withdraw_prediction_vault": {
|
|
7996
|
+
if (!this.predictionVaultManager?.hasVault) {
|
|
7997
|
+
this.relay?.sendCommandResult(cmd.id, false, "No prediction vault exists");
|
|
7998
|
+
break;
|
|
7999
|
+
}
|
|
8000
|
+
try {
|
|
8001
|
+
const isEmergency = cmd.params?.emergency === true;
|
|
8002
|
+
const result = isEmergency ? await this.predictionVaultManager.emergencyWithdraw() : await this.predictionVaultManager.withdrawAll();
|
|
8003
|
+
if (result.success) {
|
|
8004
|
+
this.relay?.sendCommandResult(cmd.id, true, isEmergency ? "Emergency withdrawn (penalty applied)" : "Withdrawn all shares");
|
|
8005
|
+
this.relay?.sendMessage(
|
|
8006
|
+
"prediction_vault_withdraw",
|
|
8007
|
+
isEmergency ? "warning" : "success",
|
|
8008
|
+
isEmergency ? "Emergency Withdrawal" : "Vault Withdrawal",
|
|
8009
|
+
isEmergency ? "Emergency withdrawn with penalty" : "All shares redeemed from prediction vault",
|
|
8010
|
+
{ emergency: isEmergency, txHash: result.txHash }
|
|
8011
|
+
);
|
|
8012
|
+
} else {
|
|
8013
|
+
this.relay?.sendCommandResult(cmd.id, false, result.error || "Withdrawal failed");
|
|
8014
|
+
}
|
|
8015
|
+
} catch (error) {
|
|
8016
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8017
|
+
this.relay?.sendCommandResult(cmd.id, false, `Withdrawal failed: ${msg}`);
|
|
5171
8018
|
}
|
|
8019
|
+
this.sendRelayStatus();
|
|
5172
8020
|
break;
|
|
5173
8021
|
}
|
|
8022
|
+
case "start_paper_trading":
|
|
8023
|
+
if (this.mode === "paper") {
|
|
8024
|
+
this.relay?.sendCommandResult(cmd.id, true, "Already paper trading");
|
|
8025
|
+
return;
|
|
8026
|
+
}
|
|
8027
|
+
try {
|
|
8028
|
+
await this.startPaperTrading();
|
|
8029
|
+
this.relay?.sendCommandResult(cmd.id, true, "Paper trading started");
|
|
8030
|
+
this.relay?.sendMessage(
|
|
8031
|
+
"system",
|
|
8032
|
+
"success",
|
|
8033
|
+
"Paper Trading Started",
|
|
8034
|
+
"Agent is now paper trading with simulated execution. No on-chain transactions will be made. Results are estimates \u2014 real trading may differ due to slippage, liquidity, and execution timing."
|
|
8035
|
+
);
|
|
8036
|
+
} catch (error) {
|
|
8037
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8038
|
+
this.relay?.sendCommandResult(cmd.id, false, `Failed to start paper trading: ${msg}`);
|
|
8039
|
+
}
|
|
8040
|
+
this.sendRelayStatus();
|
|
8041
|
+
break;
|
|
8042
|
+
case "stop_paper_trading":
|
|
8043
|
+
if (this.mode !== "paper") {
|
|
8044
|
+
this.relay?.sendCommandResult(cmd.id, true, "Not paper trading");
|
|
8045
|
+
return;
|
|
8046
|
+
}
|
|
8047
|
+
this.stopPaperTrading();
|
|
8048
|
+
this.relay?.sendCommandResult(cmd.id, true, "Paper trading stopped");
|
|
8049
|
+
this.relay?.sendMessage(
|
|
8050
|
+
"system",
|
|
8051
|
+
"info",
|
|
8052
|
+
"Paper Trading Stopped",
|
|
8053
|
+
`Paper session ended. ${this.paperPortfolio ? `Trades: ${this.paperPortfolio.getTrades().length}` : ""}`
|
|
8054
|
+
);
|
|
8055
|
+
this.sendRelayStatus();
|
|
8056
|
+
break;
|
|
5174
8057
|
case "refresh_status":
|
|
5175
8058
|
this.sendRelayStatus();
|
|
5176
8059
|
this.relay?.sendCommandResult(cmd.id, true, "Status refreshed");
|
|
@@ -5197,6 +8080,63 @@ var AgentRuntime = class {
|
|
|
5197
8080
|
this.relay?.sendCommandResult(cmd.id, false, message);
|
|
5198
8081
|
}
|
|
5199
8082
|
}
|
|
8083
|
+
// --- Paper Trading ---
|
|
8084
|
+
/**
|
|
8085
|
+
* Initialize and enter paper trading mode.
|
|
8086
|
+
* Snapshots current on-chain balances (or uses provided overrides) as starting state.
|
|
8087
|
+
*/
|
|
8088
|
+
async startPaperTrading(initialBalances) {
|
|
8089
|
+
console.log("");
|
|
8090
|
+
console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
8091
|
+
console.log(" \u2502 PAPER TRADING MODE \u2502");
|
|
8092
|
+
console.log(" \u2502 Simulated execution \u2014 no on-chain trades \u2502");
|
|
8093
|
+
console.log(" \u2502 Results are estimates only. \u2502");
|
|
8094
|
+
console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
8095
|
+
console.log("");
|
|
8096
|
+
const paperDataDir = require("path").join(process.cwd(), "data", "paper");
|
|
8097
|
+
if (initialBalances) {
|
|
8098
|
+
this.paperPortfolio = new SimulatedPortfolio(initialBalances, paperDataDir);
|
|
8099
|
+
console.log(" [PAPER] Using custom initial balances");
|
|
8100
|
+
} else {
|
|
8101
|
+
const existing = SimulatedPortfolio.load(paperDataDir);
|
|
8102
|
+
if (existing) {
|
|
8103
|
+
this.paperPortfolio = existing;
|
|
8104
|
+
console.log(" [PAPER] Resumed existing paper session");
|
|
8105
|
+
} else {
|
|
8106
|
+
const tokens = this.getTokensToTrack();
|
|
8107
|
+
const realData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
8108
|
+
this.paperPortfolio = new SimulatedPortfolio(realData.balances, paperDataDir);
|
|
8109
|
+
console.log(` [PAPER] Snapshotted on-chain balances ($${realData.portfolioValue.toFixed(2)})`);
|
|
8110
|
+
}
|
|
8111
|
+
}
|
|
8112
|
+
this.paperExecutor = new PaperExecutor(this.config);
|
|
8113
|
+
this.mode = "paper";
|
|
8114
|
+
this.isRunning = true;
|
|
8115
|
+
}
|
|
8116
|
+
/**
|
|
8117
|
+
* Exit paper trading mode and return to idle.
|
|
8118
|
+
* Saves the paper portfolio state to disk.
|
|
8119
|
+
*/
|
|
8120
|
+
stopPaperTrading() {
|
|
8121
|
+
if (this.paperPortfolio) {
|
|
8122
|
+
this.paperPortfolio.save();
|
|
8123
|
+
const summary = this.paperPortfolio.getSummary(this.lastPrices);
|
|
8124
|
+
const dataDir = require("path").join(process.cwd(), "data", "paper");
|
|
8125
|
+
const result = saveSessionResult(
|
|
8126
|
+
dataDir,
|
|
8127
|
+
this.config.name,
|
|
8128
|
+
{ provider: this.config.llm.provider, model: this.config.llm.model || "default" },
|
|
8129
|
+
summary,
|
|
8130
|
+
this.paperPortfolio.getEquityCurve(),
|
|
8131
|
+
this.paperPortfolio.getTrades(),
|
|
8132
|
+
summary.startedAt
|
|
8133
|
+
);
|
|
8134
|
+
console.log(formatSessionReport(result));
|
|
8135
|
+
}
|
|
8136
|
+
this.mode = "idle";
|
|
8137
|
+
this.isRunning = false;
|
|
8138
|
+
this.paperExecutor = null;
|
|
8139
|
+
}
|
|
5200
8140
|
/**
|
|
5201
8141
|
* Periodically check if the owner has approved the pending config hash.
|
|
5202
8142
|
* Called from sendRelayStatus at most every 2.5 minutes (timestamp-throttled).
|
|
@@ -5206,7 +8146,7 @@ var AgentRuntime = class {
|
|
|
5206
8146
|
try {
|
|
5207
8147
|
const onChain = await this.client.registry.getConfigHash(BigInt(this.config.agentId));
|
|
5208
8148
|
if (onChain === this.pendingConfigHash) {
|
|
5209
|
-
this.
|
|
8149
|
+
this.updateConfigHashEverywhere(this.pendingConfigHash);
|
|
5210
8150
|
this.pendingConfigHash = null;
|
|
5211
8151
|
console.log("Config verified on-chain! Your agent will now appear on the leaderboard.");
|
|
5212
8152
|
this.relay?.sendMessage(
|
|
@@ -5219,6 +8159,34 @@ var AgentRuntime = class {
|
|
|
5219
8159
|
} catch {
|
|
5220
8160
|
}
|
|
5221
8161
|
}
|
|
8162
|
+
/**
|
|
8163
|
+
* Periodically sync config hash from on-chain to detect external changes.
|
|
8164
|
+
* This catches cases where the config was changed via the website or another
|
|
8165
|
+
* session while this agent is running. Without this, all trades and recordings
|
|
8166
|
+
* fail with ConfigMismatch until the agent is restarted.
|
|
8167
|
+
*
|
|
8168
|
+
* Called from sendRelayStatus at most every 5 minutes (timestamp-throttled).
|
|
8169
|
+
*/
|
|
8170
|
+
lastConfigSyncAt = 0;
|
|
8171
|
+
async syncConfigHashFromChain() {
|
|
8172
|
+
try {
|
|
8173
|
+
const onChain = await this.client.registry.getConfigHash(BigInt(this.config.agentId));
|
|
8174
|
+
if (onChain && onChain !== this.configHash && onChain !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
8175
|
+
console.log(`Config hash changed on-chain (external update detected): ${this.configHash} \u2192 ${onChain}`);
|
|
8176
|
+
this.updateConfigHashEverywhere(onChain);
|
|
8177
|
+
}
|
|
8178
|
+
} catch {
|
|
8179
|
+
}
|
|
8180
|
+
}
|
|
8181
|
+
/**
|
|
8182
|
+
* Propagate a config hash update to runtime + all consumers (recorders).
|
|
8183
|
+
* Ensures executor, perp recorder, and prediction recorder all stay in sync.
|
|
8184
|
+
*/
|
|
8185
|
+
updateConfigHashEverywhere(newHash) {
|
|
8186
|
+
this.configHash = newHash;
|
|
8187
|
+
this.perpRecorder?.updateConfigHash(newHash);
|
|
8188
|
+
this.predictionRecorder?.updateConfigHash(newHash);
|
|
8189
|
+
}
|
|
5222
8190
|
/**
|
|
5223
8191
|
* Send current status to the relay
|
|
5224
8192
|
*/
|
|
@@ -5229,6 +8197,11 @@ var AgentRuntime = class {
|
|
|
5229
8197
|
this.lastConfigCheckAt = Date.now();
|
|
5230
8198
|
this.checkPendingConfigApproval();
|
|
5231
8199
|
}
|
|
8200
|
+
const CONFIG_SYNC_INTERVAL_MS = 3e5;
|
|
8201
|
+
if (!this.pendingConfigHash && Date.now() - this.lastConfigSyncAt >= CONFIG_SYNC_INTERVAL_MS) {
|
|
8202
|
+
this.lastConfigSyncAt = Date.now();
|
|
8203
|
+
this.syncConfigHashFromChain();
|
|
8204
|
+
}
|
|
5232
8205
|
const vaultConfig = this.config.vault || { policy: "disabled" };
|
|
5233
8206
|
const status = {
|
|
5234
8207
|
mode: this.mode,
|
|
@@ -5266,7 +8239,43 @@ var AgentRuntime = class {
|
|
|
5266
8239
|
effectiveLeverage: this.cachedPerpLeverage,
|
|
5267
8240
|
pendingRecords: this.perpRecorder?.pendingRetries ?? 0
|
|
5268
8241
|
} : void 0,
|
|
8242
|
+
prediction: this.predictionConnected ? {
|
|
8243
|
+
enabled: true,
|
|
8244
|
+
trading: this.predictionTradingActive,
|
|
8245
|
+
polygonUSDC: this.cachedPredictionPolygonUSDC,
|
|
8246
|
+
baseUSDC: this.cachedPredictionBaseUSDC,
|
|
8247
|
+
totalExposure: this.cachedPredictionExposure,
|
|
8248
|
+
openMarkets: this.cachedPredictionOpenMarkets,
|
|
8249
|
+
openPositions: this.cachedPredictionOpenPositions,
|
|
8250
|
+
unrealizedPnl: this.cachedPredictionUnrealizedPnl,
|
|
8251
|
+
pendingFees: this.predictionRecorder?.pendingFeesUSD ?? 0,
|
|
8252
|
+
pendingRecords: this.predictionRecorder?.pendingRetries ?? 0,
|
|
8253
|
+
vault: this.cachedPredictionVaultStatus
|
|
8254
|
+
} : void 0,
|
|
5269
8255
|
positions: this.positionTracker ? this.positionTracker.getPositionSummary(this.lastPrices) : void 0,
|
|
8256
|
+
paper: this.mode === "paper" && this.paperPortfolio ? (() => {
|
|
8257
|
+
const summary = this.paperPortfolio.getSummary(this.lastPrices);
|
|
8258
|
+
const trades = this.paperPortfolio.getTrades();
|
|
8259
|
+
let wins = 0, losses = 0;
|
|
8260
|
+
for (const t of trades) {
|
|
8261
|
+
if (t.action === "hold") continue;
|
|
8262
|
+
if (t.valueOutUSD >= t.valueInUSD) wins++;
|
|
8263
|
+
else losses++;
|
|
8264
|
+
}
|
|
8265
|
+
const curve = this.paperPortfolio.getEquityCurve();
|
|
8266
|
+
const equityCurve = curve.length > 50 ? curve.slice(-50) : curve;
|
|
8267
|
+
return {
|
|
8268
|
+
active: true,
|
|
8269
|
+
startedAt: summary.startedAt,
|
|
8270
|
+
simulatedValue: summary.currentValue,
|
|
8271
|
+
simulatedPnLPct: summary.totalReturnPct,
|
|
8272
|
+
tradeCount: summary.tradeCount,
|
|
8273
|
+
totalFees: summary.totalFees,
|
|
8274
|
+
wins,
|
|
8275
|
+
losses,
|
|
8276
|
+
equityCurve
|
|
8277
|
+
};
|
|
8278
|
+
})() : void 0,
|
|
5270
8279
|
configHash: this.configHash || void 0,
|
|
5271
8280
|
pendingConfigHash: this.pendingConfigHash
|
|
5272
8281
|
// null preserved by JSON.stringify for clearing
|
|
@@ -5285,28 +8294,71 @@ var AgentRuntime = class {
|
|
|
5285
8294
|
}).catch(() => {
|
|
5286
8295
|
});
|
|
5287
8296
|
}
|
|
8297
|
+
if (this.predictionConnected && this.predictionPositions && this.predictionFunding) {
|
|
8298
|
+
this.predictionPositions.getAccountSummary().then((summary) => {
|
|
8299
|
+
this.cachedPredictionExposure = summary.totalExposure;
|
|
8300
|
+
this.cachedPredictionOpenMarkets = summary.openMarketCount;
|
|
8301
|
+
this.cachedPredictionOpenPositions = summary.openPositionCount;
|
|
8302
|
+
this.cachedPredictionUnrealizedPnl = summary.totalUnrealizedPnl;
|
|
8303
|
+
}).catch(() => {
|
|
8304
|
+
});
|
|
8305
|
+
this.predictionFunding.getPolygonUSDCBalance().then((bal) => {
|
|
8306
|
+
this.cachedPredictionPolygonUSDC = Number(bal) / 1e6;
|
|
8307
|
+
}).catch(() => {
|
|
8308
|
+
});
|
|
8309
|
+
this.predictionFunding.getBaseUSDCBalance().then((bal) => {
|
|
8310
|
+
this.cachedPredictionBaseUSDC = Number(bal) / 1e6;
|
|
8311
|
+
}).catch(() => {
|
|
8312
|
+
});
|
|
8313
|
+
if (this.predictionVaultManager?.hasVault) {
|
|
8314
|
+
this.predictionVaultManager.getVaultStatus().then((vs) => {
|
|
8315
|
+
if (vs.hasVault) {
|
|
8316
|
+
this.cachedPredictionVaultStatus = {
|
|
8317
|
+
address: vs.vaultAddress,
|
|
8318
|
+
totalAssets: Number(vs.totalAssets) / 1e6,
|
|
8319
|
+
sharePrice: vs.sharePrice,
|
|
8320
|
+
cachedCtfValue: Number(vs.cachedCtfValue) / 1e6,
|
|
8321
|
+
currentExposureBps: vs.currentExposureBps,
|
|
8322
|
+
circuitBreakerActive: vs.circuitBreakerActive,
|
|
8323
|
+
isPaused: vs.isPaused
|
|
8324
|
+
};
|
|
8325
|
+
}
|
|
8326
|
+
}).catch(() => {
|
|
8327
|
+
});
|
|
8328
|
+
}
|
|
8329
|
+
}
|
|
5288
8330
|
}
|
|
5289
8331
|
/**
|
|
5290
8332
|
* Run a single trading cycle
|
|
5291
8333
|
*/
|
|
5292
8334
|
async runCycle() {
|
|
8335
|
+
const isPaper = this.mode === "paper";
|
|
8336
|
+
const modeTag = isPaper ? "[PAPER] " : "";
|
|
5293
8337
|
console.log(`
|
|
5294
|
-
--- Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
|
|
8338
|
+
--- ${modeTag}Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
|
|
5295
8339
|
this.cycleCount++;
|
|
5296
8340
|
this.lastCycleAt = Date.now();
|
|
5297
8341
|
const tokens = this.getTokensToTrack();
|
|
5298
8342
|
const marketData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
5299
|
-
|
|
8343
|
+
if (isPaper && this.paperPortfolio) {
|
|
8344
|
+
const simBalances = this.paperPortfolio.getBalances();
|
|
8345
|
+
marketData.balances = simBalances;
|
|
8346
|
+
marketData.portfolioValue = this.paperPortfolio.calculateValue(marketData.prices);
|
|
8347
|
+
this.paperExecutor?.updatePrices(marketData.prices);
|
|
8348
|
+
}
|
|
8349
|
+
console.log(`${modeTag}Portfolio value: $${marketData.portfolioValue.toFixed(2)}`);
|
|
5300
8350
|
this.lastPortfolioValue = marketData.portfolioValue;
|
|
5301
8351
|
this.lastPrices = marketData.prices;
|
|
5302
8352
|
const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
5303
8353
|
this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
|
|
5304
8354
|
this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
8355
|
+
if (!isPaper) {
|
|
8356
|
+
const fundsOk = this.checkFundsLow(marketData);
|
|
8357
|
+
if (!fundsOk) {
|
|
8358
|
+
console.warn("Skipping trading cycle \u2014 ETH balance critically low");
|
|
8359
|
+
this.sendRelayStatus();
|
|
8360
|
+
return;
|
|
8361
|
+
}
|
|
5310
8362
|
}
|
|
5311
8363
|
this.strategyContext.positions = this.positionTracker.getPositions();
|
|
5312
8364
|
this.strategyContext.tradeHistory = this.positionTracker.getTradeHistory(20);
|
|
@@ -5337,74 +8389,133 @@ var AgentRuntime = class {
|
|
|
5337
8389
|
);
|
|
5338
8390
|
}
|
|
5339
8391
|
if (filteredSignals.length > 0) {
|
|
5340
|
-
const vaultStatus = await this.vaultManager?.getVaultStatus();
|
|
5341
|
-
if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
|
|
5342
|
-
console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
|
|
5343
|
-
}
|
|
5344
8392
|
const preTradePortfolioValue = marketData.portfolioValue;
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
8393
|
+
if (isPaper && this.paperExecutor && this.paperPortfolio) {
|
|
8394
|
+
const results = await this.paperExecutor.executeAll(filteredSignals);
|
|
8395
|
+
let totalFeesUSD = 0;
|
|
8396
|
+
for (const result of results) {
|
|
8397
|
+
if (result.success && result.paperFill) {
|
|
8398
|
+
this.paperPortfolio.applyFill(result.paperFill);
|
|
8399
|
+
totalFeesUSD += result.paperFill.feeUSD;
|
|
8400
|
+
this.riskManager.updateFees(result.paperFill.feeUSD);
|
|
8401
|
+
this.relay?.sendMessage(
|
|
8402
|
+
"paper_trade_executed",
|
|
8403
|
+
"success",
|
|
8404
|
+
"[PAPER] Trade Executed",
|
|
8405
|
+
`${result.signal.action.toUpperCase()}: ${result.signal.reasoning || "No reason provided"}`,
|
|
8406
|
+
{
|
|
8407
|
+
action: result.signal.action,
|
|
8408
|
+
txHash: result.txHash,
|
|
8409
|
+
tokenIn: result.signal.tokenIn,
|
|
8410
|
+
tokenOut: result.signal.tokenOut,
|
|
8411
|
+
paper: true,
|
|
8412
|
+
valueInUSD: result.paperFill.valueInUSD,
|
|
8413
|
+
valueOutUSD: result.paperFill.valueOutUSD
|
|
8414
|
+
}
|
|
8415
|
+
);
|
|
8416
|
+
} else {
|
|
8417
|
+
this.relay?.sendMessage(
|
|
8418
|
+
"paper_trade_failed",
|
|
8419
|
+
"error",
|
|
8420
|
+
"[PAPER] Trade Failed",
|
|
8421
|
+
result.error || "Unknown error",
|
|
8422
|
+
{ action: result.signal.action, paper: true }
|
|
8423
|
+
);
|
|
8424
|
+
}
|
|
8425
|
+
const tokenIn = result.signal.tokenIn.toLowerCase();
|
|
8426
|
+
const tokenOut = result.signal.tokenOut.toLowerCase();
|
|
8427
|
+
this.positionTracker.recordTrade({
|
|
8428
|
+
action: result.signal.action,
|
|
8429
|
+
tokenIn,
|
|
8430
|
+
tokenOut,
|
|
8431
|
+
amountIn: result.signal.amountIn,
|
|
8432
|
+
priceIn: marketData.prices[tokenIn] || 0,
|
|
8433
|
+
priceOut: marketData.prices[tokenOut] || 0,
|
|
8434
|
+
txHash: result.txHash,
|
|
8435
|
+
reasoning: result.signal.reasoning,
|
|
8436
|
+
success: result.success
|
|
8437
|
+
});
|
|
5377
8438
|
}
|
|
8439
|
+
const postValue = this.paperPortfolio.calculateValue(marketData.prices);
|
|
8440
|
+
const marketPnL = postValue - preTradePortfolioValue + totalFeesUSD;
|
|
8441
|
+
this.riskManager.updatePnL(marketPnL);
|
|
8442
|
+
this.positionTracker.saveRiskState(this.riskManager.exportState());
|
|
8443
|
+
if (marketPnL !== 0) {
|
|
8444
|
+
console.log(` [PAPER] Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
8445
|
+
}
|
|
8446
|
+
this.paperPortfolio.recordEquityPoint(marketData.prices);
|
|
8447
|
+
this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
8448
|
+
this.lastPortfolioValue = postValue;
|
|
8449
|
+
this.paperPortfolio.save();
|
|
8450
|
+
} else {
|
|
8451
|
+
const vaultStatus = await this.vaultManager?.getVaultStatus();
|
|
8452
|
+
if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
|
|
8453
|
+
console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
|
|
8454
|
+
}
|
|
8455
|
+
const results = await this.executor.executeAll(filteredSignals);
|
|
8456
|
+
let totalFeesUSD = 0;
|
|
8457
|
+
for (const result of results) {
|
|
8458
|
+
if (result.success) {
|
|
8459
|
+
console.log(`Trade executed: ${result.signal.action} - ${result.txHash}`);
|
|
8460
|
+
const feeCostBps = 20;
|
|
8461
|
+
const signalPrice = marketData.prices[result.signal.tokenIn.toLowerCase()] || 0;
|
|
8462
|
+
const amountUSD = Number(result.signal.amountIn) / Math.pow(10, getTokenDecimals(result.signal.tokenIn)) * signalPrice;
|
|
8463
|
+
const feeCostUSD = amountUSD * feeCostBps / 1e4;
|
|
8464
|
+
totalFeesUSD += feeCostUSD;
|
|
8465
|
+
this.riskManager.updateFees(feeCostUSD);
|
|
8466
|
+
this.relay?.sendMessage(
|
|
8467
|
+
"trade_executed",
|
|
8468
|
+
"success",
|
|
8469
|
+
"Trade Executed",
|
|
8470
|
+
`${result.signal.action.toUpperCase()}: ${result.signal.reasoning || "No reason provided"}`,
|
|
8471
|
+
{
|
|
8472
|
+
action: result.signal.action,
|
|
8473
|
+
txHash: result.txHash,
|
|
8474
|
+
tokenIn: result.signal.tokenIn,
|
|
8475
|
+
tokenOut: result.signal.tokenOut
|
|
8476
|
+
}
|
|
8477
|
+
);
|
|
8478
|
+
} else {
|
|
8479
|
+
console.warn(`Trade failed: ${result.error}`);
|
|
8480
|
+
this.relay?.sendMessage(
|
|
8481
|
+
"trade_failed",
|
|
8482
|
+
"error",
|
|
8483
|
+
"Trade Failed",
|
|
8484
|
+
result.error || "Unknown error",
|
|
8485
|
+
{ action: result.signal.action }
|
|
8486
|
+
);
|
|
8487
|
+
}
|
|
8488
|
+
}
|
|
8489
|
+
for (const result of results) {
|
|
8490
|
+
const tokenIn = result.signal.tokenIn.toLowerCase();
|
|
8491
|
+
const tokenOut = result.signal.tokenOut.toLowerCase();
|
|
8492
|
+
this.positionTracker.recordTrade({
|
|
8493
|
+
action: result.signal.action,
|
|
8494
|
+
tokenIn,
|
|
8495
|
+
tokenOut,
|
|
8496
|
+
amountIn: result.signal.amountIn,
|
|
8497
|
+
priceIn: marketData.prices[tokenIn] || 0,
|
|
8498
|
+
priceOut: marketData.prices[tokenOut] || 0,
|
|
8499
|
+
txHash: result.txHash,
|
|
8500
|
+
reasoning: result.signal.reasoning,
|
|
8501
|
+
success: result.success
|
|
8502
|
+
});
|
|
8503
|
+
}
|
|
8504
|
+
const postTokens = this.getTokensToTrack();
|
|
8505
|
+
const postTradeData = await this.marketData.fetchMarketData(this.client.address, postTokens);
|
|
8506
|
+
const marketPnL = postTradeData.portfolioValue - preTradePortfolioValue + totalFeesUSD;
|
|
8507
|
+
this.riskManager.updatePnL(marketPnL);
|
|
8508
|
+
this.positionTracker.saveRiskState(this.riskManager.exportState());
|
|
8509
|
+
if (marketPnL !== 0) {
|
|
8510
|
+
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
8511
|
+
}
|
|
8512
|
+
this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
8513
|
+
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
8514
|
+
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
8515
|
+
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|
|
5378
8516
|
}
|
|
5379
|
-
for (const result of results) {
|
|
5380
|
-
const tokenIn = result.signal.tokenIn.toLowerCase();
|
|
5381
|
-
const tokenOut = result.signal.tokenOut.toLowerCase();
|
|
5382
|
-
this.positionTracker.recordTrade({
|
|
5383
|
-
action: result.signal.action,
|
|
5384
|
-
tokenIn,
|
|
5385
|
-
tokenOut,
|
|
5386
|
-
amountIn: result.signal.amountIn,
|
|
5387
|
-
priceIn: marketData.prices[tokenIn] || 0,
|
|
5388
|
-
priceOut: marketData.prices[tokenOut] || 0,
|
|
5389
|
-
txHash: result.txHash,
|
|
5390
|
-
reasoning: result.signal.reasoning,
|
|
5391
|
-
success: result.success
|
|
5392
|
-
});
|
|
5393
|
-
}
|
|
5394
|
-
const postTokens = this.getTokensToTrack();
|
|
5395
|
-
const postTradeData = await this.marketData.fetchMarketData(this.client.address, postTokens);
|
|
5396
|
-
const marketPnL = postTradeData.portfolioValue - preTradePortfolioValue + totalFeesUSD;
|
|
5397
|
-
this.riskManager.updatePnL(marketPnL);
|
|
5398
|
-
this.positionTracker.saveRiskState(this.riskManager.exportState());
|
|
5399
|
-
if (marketPnL !== 0) {
|
|
5400
|
-
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
5401
|
-
}
|
|
5402
|
-
this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
5403
|
-
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
5404
|
-
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
5405
|
-
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|
|
5406
8517
|
}
|
|
5407
|
-
if (this.perpConnected && this.perpTradingActive) {
|
|
8518
|
+
if (!isPaper && this.perpConnected && this.perpTradingActive) {
|
|
5408
8519
|
try {
|
|
5409
8520
|
await this.runPerpCycle();
|
|
5410
8521
|
} catch (error) {
|
|
@@ -5413,6 +8524,15 @@ var AgentRuntime = class {
|
|
|
5413
8524
|
this.relay?.sendMessage("system", "error", "Perp Cycle Error", message);
|
|
5414
8525
|
}
|
|
5415
8526
|
}
|
|
8527
|
+
if (!isPaper && this.predictionConnected && this.predictionTradingActive) {
|
|
8528
|
+
try {
|
|
8529
|
+
await this.runPredictionCycle();
|
|
8530
|
+
} catch (error) {
|
|
8531
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8532
|
+
console.error("Error in prediction cycle:", message);
|
|
8533
|
+
this.relay?.sendMessage("system", "error", "Prediction Cycle Error", message);
|
|
8534
|
+
}
|
|
8535
|
+
}
|
|
5416
8536
|
this.sendRelayStatus();
|
|
5417
8537
|
}
|
|
5418
8538
|
/**
|
|
@@ -5473,6 +8593,101 @@ var AgentRuntime = class {
|
|
|
5473
8593
|
}
|
|
5474
8594
|
}
|
|
5475
8595
|
}
|
|
8596
|
+
/**
|
|
8597
|
+
* Run a single prediction market trading cycle.
|
|
8598
|
+
* Fetches active markets, positions, calls prediction strategy, executes signals.
|
|
8599
|
+
* Fills are recorded on Base via Router.recordPerpTrade() with POLY: prefix.
|
|
8600
|
+
*/
|
|
8601
|
+
async runPredictionCycle() {
|
|
8602
|
+
if (!this.predictionClient || !this.predictionOrders || !this.predictionPositions || !this.predictionRecorder || !this.predictionBrowser || !this.predictionConnected) return;
|
|
8603
|
+
const predictionConfig = this.config.prediction;
|
|
8604
|
+
if (!predictionConfig?.enabled) return;
|
|
8605
|
+
console.log(" [POLY] Running prediction cycle...");
|
|
8606
|
+
try {
|
|
8607
|
+
const newFills = await this.predictionOrders.pollNewFills();
|
|
8608
|
+
if (newFills.length > 0) {
|
|
8609
|
+
console.log(` [POLY] ${newFills.length} new fill(s) detected`);
|
|
8610
|
+
for (const fill of newFills) {
|
|
8611
|
+
this.predictionPositions.processFill(fill);
|
|
8612
|
+
const result = await this.predictionRecorder.recordFill(fill);
|
|
8613
|
+
if (result.success) {
|
|
8614
|
+
this.relay?.sendMessage(
|
|
8615
|
+
"prediction_fill",
|
|
8616
|
+
"success",
|
|
8617
|
+
"Prediction Fill",
|
|
8618
|
+
`${fill.side} ${fill.size} @ $${parseFloat(fill.price).toFixed(2)} \u2014 market ${fill.marketConditionId.slice(0, 12)}...`,
|
|
8619
|
+
{ tradeId: fill.tradeId, side: fill.side, size: fill.size, price: fill.price, txHash: result.txHash }
|
|
8620
|
+
);
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
}
|
|
8624
|
+
} catch (error) {
|
|
8625
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8626
|
+
console.warn(" [POLY] Fill polling failed:", message);
|
|
8627
|
+
}
|
|
8628
|
+
const account = await this.predictionPositions.getAccountSummary();
|
|
8629
|
+
const positions = await this.predictionPositions.getPositions();
|
|
8630
|
+
console.log(` [POLY] Polygon USDC: $${account.polygonUSDC.toFixed(2)}, Positions: ${positions.length}, Exposure: $${account.totalExposure.toFixed(2)}`);
|
|
8631
|
+
if (!this.predictionStrategy) {
|
|
8632
|
+
return;
|
|
8633
|
+
}
|
|
8634
|
+
let markets;
|
|
8635
|
+
try {
|
|
8636
|
+
const category = predictionConfig.allowedCategories?.[0];
|
|
8637
|
+
markets = await this.predictionBrowser.getActiveMarkets({
|
|
8638
|
+
limit: 50,
|
|
8639
|
+
category
|
|
8640
|
+
});
|
|
8641
|
+
} catch (error) {
|
|
8642
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8643
|
+
console.error(" [POLY] Market fetch error:", message);
|
|
8644
|
+
return;
|
|
8645
|
+
}
|
|
8646
|
+
let signals;
|
|
8647
|
+
try {
|
|
8648
|
+
signals = await this.predictionStrategy(
|
|
8649
|
+
markets,
|
|
8650
|
+
positions,
|
|
8651
|
+
account,
|
|
8652
|
+
this.llm,
|
|
8653
|
+
this.config
|
|
8654
|
+
);
|
|
8655
|
+
} catch (error) {
|
|
8656
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8657
|
+
console.error(" [POLY] Strategy error:", message);
|
|
8658
|
+
return;
|
|
8659
|
+
}
|
|
8660
|
+
console.log(` [POLY] Strategy generated ${signals.length} signals`);
|
|
8661
|
+
for (const signal of signals) {
|
|
8662
|
+
if (signal.action === "hold") continue;
|
|
8663
|
+
const result = await this.predictionOrders.executeSignal(signal);
|
|
8664
|
+
if (result.success) {
|
|
8665
|
+
console.log(` [POLY] Order placed: ${signal.action} "${signal.marketQuestion.slice(0, 50)}..." \u2014 ${result.status}`);
|
|
8666
|
+
} else {
|
|
8667
|
+
console.warn(` [POLY] Order failed: ${signal.action} \u2014 ${result.error}`);
|
|
8668
|
+
}
|
|
8669
|
+
}
|
|
8670
|
+
const pendingFees = this.predictionRecorder.pendingFeesUSD;
|
|
8671
|
+
const threshold = predictionConfig.feeBridgeThresholdUSD ?? 10;
|
|
8672
|
+
if (pendingFees >= threshold && this.predictionFunding) {
|
|
8673
|
+
console.log(` [POLY] Bridging $${pendingFees.toFixed(2)} in accumulated fees to Base...`);
|
|
8674
|
+
const feeAmount = BigInt(Math.floor(pendingFees * 1e6));
|
|
8675
|
+
try {
|
|
8676
|
+
const bridgeResult = await this.predictionFunding.withdrawToBase(feeAmount, (step) => {
|
|
8677
|
+
console.log(` [POLY] Fee bridge: ${step}`);
|
|
8678
|
+
});
|
|
8679
|
+
if (bridgeResult.success) {
|
|
8680
|
+
this.predictionRecorder.resetAccumulatedFees();
|
|
8681
|
+
console.log(` [POLY] Fee bridge complete: $${pendingFees.toFixed(2)} \u2192 Base`);
|
|
8682
|
+
} else {
|
|
8683
|
+
console.warn(` [POLY] Fee bridge failed: ${bridgeResult.error}`);
|
|
8684
|
+
}
|
|
8685
|
+
} catch (error) {
|
|
8686
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8687
|
+
console.warn(` [POLY] Fee bridge error: ${message}`);
|
|
8688
|
+
}
|
|
8689
|
+
}
|
|
8690
|
+
}
|
|
5476
8691
|
/**
|
|
5477
8692
|
* Check if ETH balance is below threshold and notify.
|
|
5478
8693
|
* Returns true if trading should continue, false if ETH is critically low.
|
|
@@ -5524,6 +8739,14 @@ var AgentRuntime = class {
|
|
|
5524
8739
|
if (this.perpRecorder) {
|
|
5525
8740
|
this.perpRecorder.stop();
|
|
5526
8741
|
}
|
|
8742
|
+
this.predictionConnected = false;
|
|
8743
|
+
this.predictionTradingActive = false;
|
|
8744
|
+
if (this.predictionVaultManager) {
|
|
8745
|
+
this.predictionVaultManager.stop();
|
|
8746
|
+
}
|
|
8747
|
+
if (this.predictionRecorder) {
|
|
8748
|
+
this.predictionRecorder.stop();
|
|
8749
|
+
}
|
|
5527
8750
|
if (this.relay) {
|
|
5528
8751
|
this.relay.disconnect();
|
|
5529
8752
|
}
|
|
@@ -5541,8 +8764,8 @@ var AgentRuntime = class {
|
|
|
5541
8764
|
* for Frontier agents trading outside the whitelist) stay visible.
|
|
5542
8765
|
*/
|
|
5543
8766
|
getTokensToTrack() {
|
|
5544
|
-
const
|
|
5545
|
-
const baseSet = new Set(
|
|
8767
|
+
const base8 = this.config.allowedTokens || this.getDefaultTokens();
|
|
8768
|
+
const baseSet = new Set(base8.map((t) => t.toLowerCase()));
|
|
5546
8769
|
const trackedPositions = this.positionTracker.getPositions();
|
|
5547
8770
|
const extras = [];
|
|
5548
8771
|
for (const pos of trackedPositions) {
|
|
@@ -5565,7 +8788,7 @@ var AgentRuntime = class {
|
|
|
5565
8788
|
console.log(`Auto-tracking ${extras.length} additional token(s) from position history`);
|
|
5566
8789
|
}
|
|
5567
8790
|
const resolver = this.marketData.getResolver();
|
|
5568
|
-
const allTokens = [...
|
|
8791
|
+
const allTokens = [...base8, ...extras];
|
|
5569
8792
|
const filtered = allTokens.filter((t) => !resolver.isUnresolvable(t.toLowerCase()));
|
|
5570
8793
|
const dropped = allTokens.length - filtered.length;
|
|
5571
8794
|
if (dropped > 0) {
|
|
@@ -5719,8 +8942,8 @@ var AgentRuntime = class {
|
|
|
5719
8942
|
|
|
5720
8943
|
// src/secure-env.ts
|
|
5721
8944
|
var crypto = __toESM(require("crypto"));
|
|
5722
|
-
var
|
|
5723
|
-
var
|
|
8945
|
+
var fs3 = __toESM(require("fs"));
|
|
8946
|
+
var path3 = __toESM(require("path"));
|
|
5724
8947
|
var ALGORITHM = "aes-256-gcm";
|
|
5725
8948
|
var PBKDF2_ITERATIONS = 1e5;
|
|
5726
8949
|
var SALT_LENGTH = 32;
|
|
@@ -5787,10 +9010,10 @@ function parseEnvFile(content) {
|
|
|
5787
9010
|
return entries;
|
|
5788
9011
|
}
|
|
5789
9012
|
function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
|
|
5790
|
-
if (!
|
|
9013
|
+
if (!fs3.existsSync(envPath)) {
|
|
5791
9014
|
throw new Error(`File not found: ${envPath}`);
|
|
5792
9015
|
}
|
|
5793
|
-
const content =
|
|
9016
|
+
const content = fs3.readFileSync(envPath, "utf-8");
|
|
5794
9017
|
const entries = parseEnvFile(content);
|
|
5795
9018
|
if (entries.length === 0) {
|
|
5796
9019
|
throw new Error("No environment variables found in file");
|
|
@@ -5820,9 +9043,9 @@ function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
|
|
|
5820
9043
|
entries: encryptedEntries
|
|
5821
9044
|
};
|
|
5822
9045
|
const encPath = envPath + ".enc";
|
|
5823
|
-
|
|
9046
|
+
fs3.writeFileSync(encPath, JSON.stringify(encryptedEnv, null, 2), { mode: 384 });
|
|
5824
9047
|
if (deleteOriginal) {
|
|
5825
|
-
|
|
9048
|
+
fs3.unlinkSync(envPath);
|
|
5826
9049
|
}
|
|
5827
9050
|
const sensitiveCount = encryptedEntries.filter((e) => e.encrypted).length;
|
|
5828
9051
|
const plainCount = encryptedEntries.filter((e) => !e.encrypted).length;
|
|
@@ -5832,10 +9055,10 @@ function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
|
|
|
5832
9055
|
return encPath;
|
|
5833
9056
|
}
|
|
5834
9057
|
function decryptEnvFile(encPath, passphrase) {
|
|
5835
|
-
if (!
|
|
9058
|
+
if (!fs3.existsSync(encPath)) {
|
|
5836
9059
|
throw new Error(`Encrypted env file not found: ${encPath}`);
|
|
5837
9060
|
}
|
|
5838
|
-
const content = JSON.parse(
|
|
9061
|
+
const content = JSON.parse(fs3.readFileSync(encPath, "utf-8"));
|
|
5839
9062
|
if (content.version !== 1) {
|
|
5840
9063
|
throw new Error(`Unsupported encrypted env version: ${content.version}`);
|
|
5841
9064
|
}
|
|
@@ -5861,9 +9084,9 @@ function decryptEnvFile(encPath, passphrase) {
|
|
|
5861
9084
|
return result;
|
|
5862
9085
|
}
|
|
5863
9086
|
function loadSecureEnv(basePath, passphrase) {
|
|
5864
|
-
const encPath =
|
|
5865
|
-
const envPath =
|
|
5866
|
-
if (
|
|
9087
|
+
const encPath = path3.join(basePath, ".env.enc");
|
|
9088
|
+
const envPath = path3.join(basePath, ".env");
|
|
9089
|
+
if (fs3.existsSync(encPath)) {
|
|
5867
9090
|
if (!passphrase) {
|
|
5868
9091
|
passphrase = process.env.EXAGENT_PASSPHRASE;
|
|
5869
9092
|
}
|
|
@@ -5882,8 +9105,8 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
5882
9105
|
return true;
|
|
5883
9106
|
}
|
|
5884
9107
|
}
|
|
5885
|
-
if (
|
|
5886
|
-
const content =
|
|
9108
|
+
if (fs3.existsSync(envPath)) {
|
|
9109
|
+
const content = fs3.readFileSync(envPath, "utf-8");
|
|
5887
9110
|
const entries = parseEnvFile(content);
|
|
5888
9111
|
const sensitiveKeys = entries.filter(({ key }) => isSensitiveKey(key)).map(({ key }) => key);
|
|
5889
9112
|
if (sensitiveKeys.length > 0) {
|
|
@@ -5902,7 +9125,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
5902
9125
|
}
|
|
5903
9126
|
|
|
5904
9127
|
// src/index.ts
|
|
5905
|
-
var AGENT_VERSION = "0.1.
|
|
9128
|
+
var AGENT_VERSION = "0.1.40";
|
|
5906
9129
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5907
9130
|
0 && (module.exports = {
|
|
5908
9131
|
AGENT_VERSION,
|
|
@@ -5920,6 +9143,8 @@ var AGENT_VERSION = "0.1.38";
|
|
|
5920
9143
|
HyperliquidWebSocket,
|
|
5921
9144
|
LLMConfigSchema,
|
|
5922
9145
|
LLMProviderSchema,
|
|
9146
|
+
MARKET_CATEGORIES,
|
|
9147
|
+
MarketBrowser,
|
|
5923
9148
|
MarketDataService,
|
|
5924
9149
|
MistralAdapter,
|
|
5925
9150
|
OllamaAdapter,
|
|
@@ -5928,8 +9153,14 @@ var AGENT_VERSION = "0.1.38";
|
|
|
5928
9153
|
PerpConfigSchema,
|
|
5929
9154
|
PerpOnboarding,
|
|
5930
9155
|
PerpTradeRecorder,
|
|
9156
|
+
PolymarketClient,
|
|
5931
9157
|
PositionManager,
|
|
5932
9158
|
PositionTracker,
|
|
9159
|
+
PredictionConfigSchema,
|
|
9160
|
+
PredictionFunding,
|
|
9161
|
+
PredictionOrderManager,
|
|
9162
|
+
PredictionPositionManager,
|
|
9163
|
+
PredictionTradeRecorder,
|
|
5933
9164
|
RelayClient,
|
|
5934
9165
|
RelayConfigSchema,
|
|
5935
9166
|
RiskManager,
|
|
@@ -5941,9 +9172,12 @@ var AGENT_VERSION = "0.1.38";
|
|
|
5941
9172
|
VaultConfigSchema,
|
|
5942
9173
|
VaultManager,
|
|
5943
9174
|
VaultPolicySchema,
|
|
9175
|
+
calculatePredictionFee,
|
|
5944
9176
|
createLLMAdapter,
|
|
5945
9177
|
createSampleConfig,
|
|
9178
|
+
decodePredictionInstrument,
|
|
5946
9179
|
decryptEnvFile,
|
|
9180
|
+
encodePredictionInstrument,
|
|
5947
9181
|
encryptEnvFile,
|
|
5948
9182
|
fillHashToBytes32,
|
|
5949
9183
|
fillOidToBytes32,
|
|
@@ -5953,6 +9187,8 @@ var AGENT_VERSION = "0.1.38";
|
|
|
5953
9187
|
loadConfig,
|
|
5954
9188
|
loadSecureEnv,
|
|
5955
9189
|
loadStrategy,
|
|
9190
|
+
orderIdToBytes32,
|
|
9191
|
+
tradeIdToBytes32,
|
|
5956
9192
|
validateConfig,
|
|
5957
9193
|
validateStrategy
|
|
5958
9194
|
});
|