@exagent/agent 0.1.41 → 0.1.43
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-CQBWIY4B.mjs +9699 -0
- package/dist/chunk-FOYVWN7Q.mjs +9715 -0
- package/dist/chunk-MEYC4ASJ.mjs +9597 -0
- package/dist/chunk-OYQO7EUW.mjs +9712 -0
- package/dist/chunk-PUQW5VB2.mjs +9699 -0
- package/dist/chunk-R7LBXQH2.mjs +9712 -0
- package/dist/chunk-TI3C2W62.mjs +9585 -0
- package/dist/chunk-XGSGGIVT.mjs +9699 -0
- package/dist/cli.js +262 -22
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +67 -5
- package/dist/index.d.ts +67 -5
- package/dist/index.js +262 -22
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,24 @@ interface RiskState {
|
|
|
9
9
|
dailyFees: number;
|
|
10
10
|
lastResetDate: string;
|
|
11
11
|
}
|
|
12
|
+
/** Stuck position detection result */
|
|
13
|
+
interface StuckPosition {
|
|
14
|
+
token: string;
|
|
15
|
+
symbol: string;
|
|
16
|
+
consecutiveFailures: number;
|
|
17
|
+
lastFailureReason: string;
|
|
18
|
+
holdingDurationMs: number;
|
|
19
|
+
estimatedValueUSD: number;
|
|
20
|
+
}
|
|
21
|
+
/** Result of an inferred exit detected during balance sync */
|
|
22
|
+
interface InferredExit {
|
|
23
|
+
token: string;
|
|
24
|
+
symbol: string;
|
|
25
|
+
amountExited: number;
|
|
26
|
+
exitValueUSD: number;
|
|
27
|
+
realizedPnL: number;
|
|
28
|
+
costBasis: number;
|
|
29
|
+
}
|
|
12
30
|
/**
|
|
13
31
|
* PositionTracker — automatic position tracking with persistence.
|
|
14
32
|
*
|
|
@@ -25,6 +43,7 @@ declare class PositionTracker {
|
|
|
25
43
|
private store;
|
|
26
44
|
private positions;
|
|
27
45
|
private tradeHistory;
|
|
46
|
+
private sellFailures;
|
|
28
47
|
private maxTradeHistory;
|
|
29
48
|
constructor(store: StrategyStore, options?: {
|
|
30
49
|
maxTradeHistory?: number;
|
|
@@ -55,9 +74,20 @@ declare class PositionTracker {
|
|
|
55
74
|
private handleSell;
|
|
56
75
|
/**
|
|
57
76
|
* Sync tracked positions with on-chain balances.
|
|
58
|
-
* Updates currentAmount, detects new tokens (airdrops),
|
|
77
|
+
* Updates currentAmount, detects new tokens (airdrops), and handles position exits.
|
|
78
|
+
*
|
|
79
|
+
* CRITICAL: When a tracked position's balance decreases without a corresponding
|
|
80
|
+
* sell trade, we infer a manual exit and record it with realized PnL calculated
|
|
81
|
+
* at current market price. This prevents PnL manipulation via off-router sells.
|
|
82
|
+
*
|
|
83
|
+
* Returns array of inferred exits for the runtime to report to Command Center.
|
|
59
84
|
*/
|
|
60
|
-
syncBalances(balances: Record<string, bigint>, prices: Record<string, number>):
|
|
85
|
+
syncBalances(balances: Record<string, bigint>, prices: Record<string, number>): InferredExit[];
|
|
86
|
+
/**
|
|
87
|
+
* Record an inferred exit in trade history.
|
|
88
|
+
* These are exits detected from balance changes, not from tracked sell executions.
|
|
89
|
+
*/
|
|
90
|
+
private recordInferredExit;
|
|
61
91
|
/** Get all tracked positions (backfills missing symbols from resolver) */
|
|
62
92
|
getPositions(): TrackedPosition[];
|
|
63
93
|
/** Get a single position by token address */
|
|
@@ -68,6 +98,31 @@ declare class PositionTracker {
|
|
|
68
98
|
getUnrealizedPnL(prices: Record<string, number>): Record<string, number>;
|
|
69
99
|
/** Get total unrealized PnL across all positions */
|
|
70
100
|
getTotalUnrealizedPnL(prices: Record<string, number>): number;
|
|
101
|
+
/**
|
|
102
|
+
* Record a sell failure for a token. Called when a sell attempt fails
|
|
103
|
+
* due to liquidity issues, route simulation failure, or excessive price impact.
|
|
104
|
+
*/
|
|
105
|
+
recordSellFailure(token: string, reason: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Clear sell failure tracking for a token (called after successful sell).
|
|
108
|
+
*/
|
|
109
|
+
clearSellFailure(token: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Detect positions that are "stuck" — we've tried and failed to sell them
|
|
112
|
+
* multiple times, likely due to insufficient liquidity.
|
|
113
|
+
*
|
|
114
|
+
* Returns positions that have failed to sell >= STUCK_FAILURE_THRESHOLD times.
|
|
115
|
+
*/
|
|
116
|
+
getStuckPositions(prices: Record<string, number>): StuckPosition[];
|
|
117
|
+
/**
|
|
118
|
+
* Check if a specific position is stuck.
|
|
119
|
+
*/
|
|
120
|
+
isPositionStuck(token: string): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Check if we should alert about a stuck position (rate-limited to once per 24h).
|
|
123
|
+
* Returns true if we should alert, and marks the alert as sent.
|
|
124
|
+
*/
|
|
125
|
+
shouldAlertStuckPosition(token: string): boolean;
|
|
71
126
|
/** Load persisted risk state */
|
|
72
127
|
getRiskState(): RiskState;
|
|
73
128
|
/** Save risk state to persistent store */
|
|
@@ -783,6 +838,13 @@ interface TradeRecord {
|
|
|
783
838
|
/** Realized PnL in USD (only for sells) */
|
|
784
839
|
realizedPnL?: number;
|
|
785
840
|
success: boolean;
|
|
841
|
+
/**
|
|
842
|
+
* True if this trade was inferred from balance changes, not from a tracked execution.
|
|
843
|
+
* Inferred exits occur when a position's balance decreases without a corresponding
|
|
844
|
+
* sell trade — typically from manual swaps outside the ExagentRouter.
|
|
845
|
+
* PnL is calculated at current market price and counts toward total realized PnL.
|
|
846
|
+
*/
|
|
847
|
+
inferred?: boolean;
|
|
786
848
|
}
|
|
787
849
|
/**
|
|
788
850
|
* Compact position summary for relay heartbeats.
|
|
@@ -936,7 +998,7 @@ interface RelayCommand {
|
|
|
936
998
|
type: CommandType;
|
|
937
999
|
params?: Record<string, unknown>;
|
|
938
1000
|
}
|
|
939
|
-
type MessageType = 'trade_executed' | 'trade_failed' | 'paper_trade_executed' | 'paper_trade_failed' | 'perp_fill' | 'perp_liquidation_warning' | 'perp_funding' | 'prediction_fill' | 'prediction_market_resolved' | 'prediction_vault_created' | 'prediction_vault_deposit' | 'prediction_vault_withdraw' | 'funds_low' | 'risk_limit_hit' | 'vault_created' | 'config_updated' | 'llm_error' | 'command_result' | 'system';
|
|
1001
|
+
type MessageType = 'trade_executed' | 'trade_failed' | 'paper_trade_executed' | 'paper_trade_failed' | 'perp_fill' | 'perp_liquidation_warning' | 'perp_funding' | 'prediction_fill' | 'prediction_market_resolved' | 'prediction_vault_created' | 'prediction_vault_deposit' | 'prediction_vault_withdraw' | 'funds_low' | 'risk_limit_hit' | 'position_stuck' | 'position_inferred_exit' | 'vault_created' | 'config_updated' | 'llm_error' | 'command_result' | 'system';
|
|
940
1002
|
type MessageLevel = 'info' | 'warning' | 'error' | 'success';
|
|
941
1003
|
interface AgentStatusPayload {
|
|
942
1004
|
mode: AgentMode;
|
|
@@ -3283,6 +3345,6 @@ declare function decryptEnvFile(encPath: string, passphrase: string): Record<str
|
|
|
3283
3345
|
declare function loadSecureEnv(basePath: string, passphrase?: string): boolean;
|
|
3284
3346
|
|
|
3285
3347
|
/** @exagent/agent package version — update alongside package.json */
|
|
3286
|
-
declare const AGENT_VERSION = "0.1.
|
|
3348
|
+
declare const AGENT_VERSION = "0.1.43";
|
|
3287
3349
|
|
|
3288
|
-
export { AGENT_VERSION, type AccountSummary, type AgentConfig, AgentConfigSchema, type AgentMode, AgentRuntime, type AgentStatusPayload, AnthropicAdapter, BaseLLMAdapter, type BridgeResult, type BridgeStep, type CommandType, DeepSeekAdapter, FileStore, type FillCallback, type FundingCallback, type FundingPayment, GoogleAdapter, GroqAdapter, HYPERLIQUID_DOMAIN, HyperliquidClient, HyperliquidSigner, HyperliquidWebSocket, type LLMAdapter, type LLMConfig, LLMConfigSchema, type LLMMessage, type LLMMetadata, type LLMProvider, LLMProviderSchema, type LLMResponse, type LiquidationCallback, type LocalPosition, MARKET_CATEGORIES, MarketBrowser, type MarketCategory, type MarketData, MarketDataService, type MessageLevel, type MessageType, MistralAdapter, OllamaAdapter, type OnboardingStatus, OpenAIAdapter, OrderManager, type OrderResult, type PerpAction, type PerpConfig$1 as PerpConfig, PerpConfigSchema, type PerpFill, type PerpMarketData, PerpOnboarding, type PerpPosition, type PerpStrategyFunction, PerpTradeRecorder, type PerpTradeSignal, type PerpConfig as PerpTradingConfig, PolymarketClient, PositionManager, type PositionSummary, PositionTracker, type PredictionAccountSummary, type PredictionAction, type PredictionConfig$1 as PredictionConfig, PredictionConfigSchema, type PredictionFill, PredictionFunding, type PredictionFundingConfig, type PredictionMarket, PredictionOrderManager, type PredictionPosition, PredictionPositionManager, type PredictionStrategyFunction, PredictionTradeRecorder, type PredictionTradeSignal, type PredictionConfig as PredictionTradingConfig, type RecordPerpTradeParams, type RecordPredictionTradeParams, RelayClient, type RelayCommand, type RelayConfig$1 as RelayConfig, RelayConfigSchema, RiskManager, type RiskState, type RiskUniverse, RiskUniverseSchema, type RuntimeConfig, STRATEGY_TEMPLATES, type StrategyContext, type StrategyFunction, type StrategyStore, type StrategyTemplate, TogetherAdapter, type TrackedPosition, TradeExecutor, type TradeRecord, type TradeSignal, type TradingConfig, TradingConfigSchema, type VaultConfig, VaultConfigSchema, VaultManager, type VaultManagerConfig, type VaultPolicy, VaultPolicySchema, type VaultStatus, calculatePredictionFee, createLLMAdapter, createSampleConfig, decodePredictionInstrument, decryptEnvFile, encodePredictionInstrument, encryptEnvFile, fillHashToBytes32, fillOidToBytes32, getAllStrategyTemplates, getNextNonce, getStrategyTemplate, loadConfig, loadSecureEnv, loadStrategy, orderIdToBytes32, tradeIdToBytes32, validateConfig, validateStrategy };
|
|
3350
|
+
export { AGENT_VERSION, type AccountSummary, type AgentConfig, AgentConfigSchema, type AgentMode, AgentRuntime, type AgentStatusPayload, AnthropicAdapter, BaseLLMAdapter, type BridgeResult, type BridgeStep, type CommandType, DeepSeekAdapter, FileStore, type FillCallback, type FundingCallback, type FundingPayment, GoogleAdapter, GroqAdapter, HYPERLIQUID_DOMAIN, HyperliquidClient, HyperliquidSigner, HyperliquidWebSocket, type InferredExit, type LLMAdapter, type LLMConfig, LLMConfigSchema, type LLMMessage, type LLMMetadata, type LLMProvider, LLMProviderSchema, type LLMResponse, type LiquidationCallback, type LocalPosition, MARKET_CATEGORIES, MarketBrowser, type MarketCategory, type MarketData, MarketDataService, type MessageLevel, type MessageType, MistralAdapter, OllamaAdapter, type OnboardingStatus, OpenAIAdapter, OrderManager, type OrderResult, type PerpAction, type PerpConfig$1 as PerpConfig, PerpConfigSchema, type PerpFill, type PerpMarketData, PerpOnboarding, type PerpPosition, type PerpStrategyFunction, PerpTradeRecorder, type PerpTradeSignal, type PerpConfig as PerpTradingConfig, PolymarketClient, PositionManager, type PositionSummary, PositionTracker, type PredictionAccountSummary, type PredictionAction, type PredictionConfig$1 as PredictionConfig, PredictionConfigSchema, type PredictionFill, PredictionFunding, type PredictionFundingConfig, type PredictionMarket, PredictionOrderManager, type PredictionPosition, PredictionPositionManager, type PredictionStrategyFunction, PredictionTradeRecorder, type PredictionTradeSignal, type PredictionConfig as PredictionTradingConfig, type RecordPerpTradeParams, type RecordPredictionTradeParams, RelayClient, type RelayCommand, type RelayConfig$1 as RelayConfig, RelayConfigSchema, RiskManager, type RiskState, type RiskUniverse, RiskUniverseSchema, type RuntimeConfig, STRATEGY_TEMPLATES, type StrategyContext, type StrategyFunction, type StrategyStore, type StrategyTemplate, type StuckPosition, TogetherAdapter, type TrackedPosition, TradeExecutor, type TradeRecord, type TradeSignal, type TradingConfig, TradingConfigSchema, type VaultConfig, VaultConfigSchema, VaultManager, type VaultManagerConfig, type VaultPolicy, VaultPolicySchema, type VaultStatus, calculatePredictionFee, createLLMAdapter, createSampleConfig, decodePredictionInstrument, decryptEnvFile, encodePredictionInstrument, encryptEnvFile, fillHashToBytes32, fillOidToBytes32, getAllStrategyTemplates, getNextNonce, getStrategyTemplate, loadConfig, loadSecureEnv, loadStrategy, orderIdToBytes32, tradeIdToBytes32, validateConfig, validateStrategy };
|
package/dist/index.js
CHANGED
|
@@ -884,16 +884,20 @@ var TOKEN_SYMBOLS = {
|
|
|
884
884
|
var KEY_POSITIONS = "__positions";
|
|
885
885
|
var KEY_TRADE_HISTORY = "__trade_history";
|
|
886
886
|
var KEY_RISK_STATE = "__risk_state";
|
|
887
|
+
var KEY_SELL_FAILURES = "__sell_failures";
|
|
888
|
+
var STUCK_FAILURE_THRESHOLD = 3;
|
|
887
889
|
var PositionTracker = class {
|
|
888
890
|
store;
|
|
889
891
|
positions;
|
|
890
892
|
tradeHistory;
|
|
893
|
+
sellFailures;
|
|
891
894
|
maxTradeHistory;
|
|
892
895
|
constructor(store, options) {
|
|
893
896
|
this.store = store;
|
|
894
897
|
this.maxTradeHistory = options?.maxTradeHistory ?? 50;
|
|
895
898
|
this.positions = store.get(KEY_POSITIONS) || {};
|
|
896
899
|
this.tradeHistory = store.get(KEY_TRADE_HISTORY) || [];
|
|
900
|
+
this.sellFailures = store.get(KEY_SELL_FAILURES) || {};
|
|
897
901
|
const posCount = Object.keys(this.positions).length;
|
|
898
902
|
if (posCount > 0 || this.tradeHistory.length > 0) {
|
|
899
903
|
console.log(`Position tracker loaded: ${posCount} positions, ${this.tradeHistory.length} trade records`);
|
|
@@ -929,10 +933,10 @@ var PositionTracker = class {
|
|
|
929
933
|
const realizedPnL = this.handleSell(tokenIn.toLowerCase(), tradeValueUSD);
|
|
930
934
|
record.realizedPnL = realizedPnL;
|
|
931
935
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
+
this.tradeHistory.unshift(record);
|
|
937
|
+
if (this.tradeHistory.length > this.maxTradeHistory) {
|
|
938
|
+
this.tradeHistory = this.tradeHistory.slice(0, this.maxTradeHistory);
|
|
939
|
+
}
|
|
936
940
|
}
|
|
937
941
|
this.persist();
|
|
938
942
|
}
|
|
@@ -993,26 +997,58 @@ var PositionTracker = class {
|
|
|
993
997
|
// ============================================================
|
|
994
998
|
/**
|
|
995
999
|
* Sync tracked positions with on-chain balances.
|
|
996
|
-
* Updates currentAmount, detects new tokens (airdrops),
|
|
1000
|
+
* Updates currentAmount, detects new tokens (airdrops), and handles position exits.
|
|
1001
|
+
*
|
|
1002
|
+
* CRITICAL: When a tracked position's balance decreases without a corresponding
|
|
1003
|
+
* sell trade, we infer a manual exit and record it with realized PnL calculated
|
|
1004
|
+
* at current market price. This prevents PnL manipulation via off-router sells.
|
|
1005
|
+
*
|
|
1006
|
+
* Returns array of inferred exits for the runtime to report to Command Center.
|
|
997
1007
|
*/
|
|
998
1008
|
syncBalances(balances, prices) {
|
|
999
1009
|
let changed = false;
|
|
1010
|
+
const inferredExits = [];
|
|
1000
1011
|
for (const [address, balance] of Object.entries(balances)) {
|
|
1001
1012
|
const token = address.toLowerCase();
|
|
1002
1013
|
if (BASE_ASSETS.has(token)) continue;
|
|
1003
1014
|
const decimals = getTokenDecimals(token);
|
|
1004
1015
|
const amount = Number(balance) / Math.pow(10, decimals);
|
|
1016
|
+
const position = this.positions[token];
|
|
1005
1017
|
if (amount > 0) {
|
|
1006
|
-
if (
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1018
|
+
if (position) {
|
|
1019
|
+
const previousAmount = position.currentAmount;
|
|
1020
|
+
if (amount < previousAmount && position.totalAmountAcquired > 0) {
|
|
1021
|
+
const exitedAmount = previousAmount - amount;
|
|
1022
|
+
const currentPrice = prices[token] || position.averageEntryPrice || 0;
|
|
1023
|
+
const exitValueUSD = exitedAmount * currentPrice;
|
|
1024
|
+
const costBasisOfExited = exitedAmount * position.averageEntryPrice;
|
|
1025
|
+
const realizedPnL = exitValueUSD - costBasisOfExited;
|
|
1026
|
+
const symbol = position.symbol || TOKEN_SYMBOLS[token] || getTokenSymbol(token) || token.slice(0, 10);
|
|
1027
|
+
this.recordInferredExit(token, exitedAmount, exitValueUSD, realizedPnL, symbol);
|
|
1028
|
+
inferredExits.push({
|
|
1029
|
+
token,
|
|
1030
|
+
symbol,
|
|
1031
|
+
amountExited: exitedAmount,
|
|
1032
|
+
exitValueUSD,
|
|
1033
|
+
realizedPnL,
|
|
1034
|
+
costBasis: costBasisOfExited
|
|
1035
|
+
});
|
|
1036
|
+
const exitRatio = exitedAmount / position.totalAmountAcquired;
|
|
1037
|
+
position.totalCostBasis = Math.max(0, position.totalCostBasis * (1 - exitRatio));
|
|
1038
|
+
position.totalAmountAcquired = Math.max(0, position.totalAmountAcquired - exitedAmount);
|
|
1039
|
+
console.log(
|
|
1040
|
+
`Position tracker: inferred exit of ${exitedAmount.toFixed(4)} ${symbol} ($${exitValueUSD.toFixed(2)} value, PnL: $${realizedPnL >= 0 ? "+" : ""}${realizedPnL.toFixed(2)})`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
if (position.currentAmount !== amount) {
|
|
1044
|
+
position.currentAmount = amount;
|
|
1045
|
+
position.lastUpdateTimestamp = Date.now();
|
|
1010
1046
|
changed = true;
|
|
1011
1047
|
}
|
|
1012
|
-
if (!
|
|
1048
|
+
if (!position.symbol) {
|
|
1013
1049
|
const resolved = TOKEN_SYMBOLS[token] || getTokenSymbol(token);
|
|
1014
1050
|
if (resolved) {
|
|
1015
|
-
|
|
1051
|
+
position.symbol = resolved;
|
|
1016
1052
|
changed = true;
|
|
1017
1053
|
}
|
|
1018
1054
|
}
|
|
@@ -1035,14 +1071,60 @@ var PositionTracker = class {
|
|
|
1035
1071
|
}
|
|
1036
1072
|
changed = true;
|
|
1037
1073
|
}
|
|
1038
|
-
} else if (
|
|
1074
|
+
} else if (position) {
|
|
1075
|
+
if (position.totalAmountAcquired > 0 && position.currentAmount > 0) {
|
|
1076
|
+
const currentPrice = prices[token] || position.averageEntryPrice || 0;
|
|
1077
|
+
const exitValueUSD = position.currentAmount * currentPrice;
|
|
1078
|
+
const costBasisOfExited = position.currentAmount * position.averageEntryPrice;
|
|
1079
|
+
const realizedPnL = exitValueUSD - costBasisOfExited;
|
|
1080
|
+
const symbol = position.symbol || TOKEN_SYMBOLS[token] || getTokenSymbol(token) || token.slice(0, 10);
|
|
1081
|
+
this.recordInferredExit(token, position.currentAmount, exitValueUSD, realizedPnL, symbol);
|
|
1082
|
+
inferredExits.push({
|
|
1083
|
+
token,
|
|
1084
|
+
symbol,
|
|
1085
|
+
amountExited: position.currentAmount,
|
|
1086
|
+
exitValueUSD,
|
|
1087
|
+
realizedPnL,
|
|
1088
|
+
costBasis: costBasisOfExited
|
|
1089
|
+
});
|
|
1090
|
+
console.log(
|
|
1091
|
+
`Position tracker: inferred FULL exit of ${position.currentAmount.toFixed(4)} ${symbol} ($${exitValueUSD.toFixed(2)} value, PnL: $${realizedPnL >= 0 ? "+" : ""}${realizedPnL.toFixed(2)})`
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1039
1094
|
delete this.positions[token];
|
|
1095
|
+
if (this.sellFailures[token]) {
|
|
1096
|
+
delete this.sellFailures[token];
|
|
1097
|
+
}
|
|
1040
1098
|
changed = true;
|
|
1041
1099
|
}
|
|
1042
1100
|
}
|
|
1043
|
-
if (changed) {
|
|
1101
|
+
if (changed || inferredExits.length > 0) {
|
|
1044
1102
|
this.persist();
|
|
1045
1103
|
}
|
|
1104
|
+
return inferredExits;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Record an inferred exit in trade history.
|
|
1108
|
+
* These are exits detected from balance changes, not from tracked sell executions.
|
|
1109
|
+
*/
|
|
1110
|
+
recordInferredExit(token, amount, valueUSD, realizedPnL, symbol) {
|
|
1111
|
+
const record = {
|
|
1112
|
+
timestamp: Date.now(),
|
|
1113
|
+
action: "sell",
|
|
1114
|
+
tokenIn: token,
|
|
1115
|
+
tokenOut: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
1116
|
+
// USDC (assumed exit to stablecoin)
|
|
1117
|
+
amountIn: amount.toString(),
|
|
1118
|
+
priceUSD: valueUSD,
|
|
1119
|
+
reasoning: `Inferred exit: balance decreased without tracked sell. Exited ${amount.toFixed(4)} ${symbol} at ~$${(valueUSD / amount).toFixed(4)}/token.`,
|
|
1120
|
+
realizedPnL,
|
|
1121
|
+
success: true,
|
|
1122
|
+
inferred: true
|
|
1123
|
+
};
|
|
1124
|
+
this.tradeHistory.unshift(record);
|
|
1125
|
+
if (this.tradeHistory.length > this.maxTradeHistory) {
|
|
1126
|
+
this.tradeHistory = this.tradeHistory.slice(0, this.maxTradeHistory);
|
|
1127
|
+
}
|
|
1046
1128
|
}
|
|
1047
1129
|
// ============================================================
|
|
1048
1130
|
// QUERY METHODS (for strategies)
|
|
@@ -1083,6 +1165,86 @@ var PositionTracker = class {
|
|
|
1083
1165
|
return Object.values(pnl).reduce((sum, v) => sum + v, 0);
|
|
1084
1166
|
}
|
|
1085
1167
|
// ============================================================
|
|
1168
|
+
// STUCK POSITION DETECTION
|
|
1169
|
+
// ============================================================
|
|
1170
|
+
/**
|
|
1171
|
+
* Record a sell failure for a token. Called when a sell attempt fails
|
|
1172
|
+
* due to liquidity issues, route simulation failure, or excessive price impact.
|
|
1173
|
+
*/
|
|
1174
|
+
recordSellFailure(token, reason) {
|
|
1175
|
+
const key = token.toLowerCase();
|
|
1176
|
+
const existing = this.sellFailures[key];
|
|
1177
|
+
if (existing) {
|
|
1178
|
+
existing.consecutiveFailures += 1;
|
|
1179
|
+
existing.lastFailureReason = reason;
|
|
1180
|
+
existing.lastFailureTimestamp = Date.now();
|
|
1181
|
+
} else {
|
|
1182
|
+
this.sellFailures[key] = {
|
|
1183
|
+
consecutiveFailures: 1,
|
|
1184
|
+
lastFailureReason: reason,
|
|
1185
|
+
lastFailureTimestamp: Date.now()
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
this.store.set(KEY_SELL_FAILURES, this.sellFailures);
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Clear sell failure tracking for a token (called after successful sell).
|
|
1192
|
+
*/
|
|
1193
|
+
clearSellFailure(token) {
|
|
1194
|
+
const key = token.toLowerCase();
|
|
1195
|
+
if (this.sellFailures[key]) {
|
|
1196
|
+
delete this.sellFailures[key];
|
|
1197
|
+
this.store.set(KEY_SELL_FAILURES, this.sellFailures);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Detect positions that are "stuck" — we've tried and failed to sell them
|
|
1202
|
+
* multiple times, likely due to insufficient liquidity.
|
|
1203
|
+
*
|
|
1204
|
+
* Returns positions that have failed to sell >= STUCK_FAILURE_THRESHOLD times.
|
|
1205
|
+
*/
|
|
1206
|
+
getStuckPositions(prices) {
|
|
1207
|
+
const stuck = [];
|
|
1208
|
+
const now = Date.now();
|
|
1209
|
+
for (const [token, failure] of Object.entries(this.sellFailures)) {
|
|
1210
|
+
if (failure.consecutiveFailures < STUCK_FAILURE_THRESHOLD) continue;
|
|
1211
|
+
const position = this.positions[token];
|
|
1212
|
+
if (!position || position.currentAmount <= 0) continue;
|
|
1213
|
+
const price = prices[token] || position.averageEntryPrice || 0;
|
|
1214
|
+
const estimatedValueUSD = position.currentAmount * price;
|
|
1215
|
+
stuck.push({
|
|
1216
|
+
token,
|
|
1217
|
+
symbol: position.symbol || TOKEN_SYMBOLS[token] || token.slice(0, 10),
|
|
1218
|
+
consecutiveFailures: failure.consecutiveFailures,
|
|
1219
|
+
lastFailureReason: failure.lastFailureReason,
|
|
1220
|
+
holdingDurationMs: now - position.entryTimestamp,
|
|
1221
|
+
estimatedValueUSD
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
return stuck;
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Check if a specific position is stuck.
|
|
1228
|
+
*/
|
|
1229
|
+
isPositionStuck(token) {
|
|
1230
|
+
const failure = this.sellFailures[token.toLowerCase()];
|
|
1231
|
+
return !!failure && failure.consecutiveFailures >= STUCK_FAILURE_THRESHOLD;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Check if we should alert about a stuck position (rate-limited to once per 24h).
|
|
1235
|
+
* Returns true if we should alert, and marks the alert as sent.
|
|
1236
|
+
*/
|
|
1237
|
+
shouldAlertStuckPosition(token) {
|
|
1238
|
+
const alertKey = `__stuck_alert_${token.toLowerCase()}`;
|
|
1239
|
+
const lastAlert = this.store.get(alertKey) || 0;
|
|
1240
|
+
const hoursSinceAlert = (Date.now() - lastAlert) / (1e3 * 60 * 60);
|
|
1241
|
+
if (hoursSinceAlert >= 24) {
|
|
1242
|
+
this.store.set(alertKey, Date.now());
|
|
1243
|
+
return true;
|
|
1244
|
+
}
|
|
1245
|
+
return false;
|
|
1246
|
+
}
|
|
1247
|
+
// ============================================================
|
|
1086
1248
|
// RISK STATE PERSISTENCE
|
|
1087
1249
|
// ============================================================
|
|
1088
1250
|
/** Load persisted risk state */
|
|
@@ -2146,7 +2308,9 @@ function validateConfig(config) {
|
|
|
2146
2308
|
throw new Error("Endpoint required for custom LLM provider");
|
|
2147
2309
|
}
|
|
2148
2310
|
if (!config.agentId || Number(config.agentId) <= 0) {
|
|
2149
|
-
throw new Error(
|
|
2311
|
+
throw new Error(
|
|
2312
|
+
'Valid agent ID required in agent-config.json.\n\n Register your agent at https://exagent.io first:\n 1. Connect your wallet\n 2. Click "Launch Agent" to register\n 3. Copy your agent ID into agent-config.json as "agentId"\n'
|
|
2313
|
+
);
|
|
2150
2314
|
}
|
|
2151
2315
|
}
|
|
2152
2316
|
var DEFAULT_RPC_URL = "https://mainnet.base.org";
|
|
@@ -4748,7 +4912,7 @@ var HyperliquidWebSocket = class {
|
|
|
4748
4912
|
var import_viem5 = require("viem");
|
|
4749
4913
|
var import_chains3 = require("viem/chains");
|
|
4750
4914
|
var import_accounts3 = require("viem/accounts");
|
|
4751
|
-
var ROUTER_ADDRESS = "
|
|
4915
|
+
var ROUTER_ADDRESS = "0x20feB3054750970773Fe7370c391732EE0559743";
|
|
4752
4916
|
var ROUTER_ABI = [
|
|
4753
4917
|
{
|
|
4754
4918
|
type: "function",
|
|
@@ -5821,7 +5985,7 @@ var PredictionPositionManager = class {
|
|
|
5821
5985
|
var import_viem8 = require("viem");
|
|
5822
5986
|
var import_chains5 = require("viem/chains");
|
|
5823
5987
|
var import_accounts5 = require("viem/accounts");
|
|
5824
|
-
var ROUTER_ADDRESS2 = "
|
|
5988
|
+
var ROUTER_ADDRESS2 = "0x20feB3054750970773Fe7370c391732EE0559743";
|
|
5825
5989
|
var ROUTER_ABI2 = [
|
|
5826
5990
|
{
|
|
5827
5991
|
type: "function",
|
|
@@ -7056,7 +7220,18 @@ var AgentRuntime = class {
|
|
|
7056
7220
|
console.log(`Wallet: ${this.client.address}`);
|
|
7057
7221
|
const agent = await this.client.registry.getAgent(BigInt(this.config.agentId));
|
|
7058
7222
|
if (!agent) {
|
|
7059
|
-
throw new Error(
|
|
7223
|
+
throw new Error(
|
|
7224
|
+
`Agent ID ${this.config.agentId} not found on-chain.
|
|
7225
|
+
|
|
7226
|
+
Register your agent at https://exagent.io first:
|
|
7227
|
+
1. Connect your wallet at https://exagent.io
|
|
7228
|
+
2. Click "Launch Agent" to register (sets name, risk universe, limits)
|
|
7229
|
+
3. Copy your agent ID into agent-config.json
|
|
7230
|
+
4. Run your agent again
|
|
7231
|
+
|
|
7232
|
+
The website handles the full registration flow including risk parameters.
|
|
7233
|
+
Your agent ID will appear on your agent's profile page after registration.`
|
|
7234
|
+
);
|
|
7060
7235
|
}
|
|
7061
7236
|
console.log(`Agent verified: ${agent.name}`);
|
|
7062
7237
|
await this.ensureWalletLinked();
|
|
@@ -7580,7 +7755,7 @@ var AgentRuntime = class {
|
|
|
7580
7755
|
try {
|
|
7581
7756
|
const tokens = this.getTokensToTrack();
|
|
7582
7757
|
const initData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
7583
|
-
this.positionTracker.syncBalances(initData.balances, initData.prices);
|
|
7758
|
+
void this.positionTracker.syncBalances(initData.balances, initData.prices);
|
|
7584
7759
|
this.lastPortfolioValue = initData.portfolioValue;
|
|
7585
7760
|
this.lastPrices = initData.prices;
|
|
7586
7761
|
const nativeEthBal = initData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
@@ -7741,6 +7916,9 @@ var AgentRuntime = class {
|
|
|
7741
7916
|
}
|
|
7742
7917
|
case "create_vault": {
|
|
7743
7918
|
const result = await this.createVault();
|
|
7919
|
+
if (!result.success) {
|
|
7920
|
+
console.warn(`[vault] create_vault failed: ${result.error}`);
|
|
7921
|
+
}
|
|
7744
7922
|
this.relay?.sendCommandResult(
|
|
7745
7923
|
cmd.id,
|
|
7746
7924
|
result.success,
|
|
@@ -8432,7 +8610,31 @@ var AgentRuntime = class {
|
|
|
8432
8610
|
this.lastPrices = marketData.prices;
|
|
8433
8611
|
const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
8434
8612
|
this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
|
|
8435
|
-
this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
8613
|
+
const inferredExits = this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
8614
|
+
for (const exit of inferredExits) {
|
|
8615
|
+
const pnlSign = exit.realizedPnL >= 0 ? "+" : "";
|
|
8616
|
+
this.relay?.sendMessage(
|
|
8617
|
+
"position_inferred_exit",
|
|
8618
|
+
exit.realizedPnL >= 0 ? "info" : "warning",
|
|
8619
|
+
`Inferred Exit: ${exit.symbol}`,
|
|
8620
|
+
`Detected manual sell of ${exit.amountExited.toFixed(4)} ${exit.symbol} outside the ExagentRouter.
|
|
8621
|
+
|
|
8622
|
+
Exit value: $${exit.exitValueUSD.toFixed(2)}
|
|
8623
|
+
Cost basis: $${exit.costBasis.toFixed(2)}
|
|
8624
|
+
Realized PnL: ${pnlSign}$${exit.realizedPnL.toFixed(2)}
|
|
8625
|
+
|
|
8626
|
+
This exit has been recorded in your trade history and counts toward your total PnL.`,
|
|
8627
|
+
{
|
|
8628
|
+
token: exit.token,
|
|
8629
|
+
symbol: exit.symbol,
|
|
8630
|
+
amountExited: exit.amountExited,
|
|
8631
|
+
exitValueUSD: exit.exitValueUSD,
|
|
8632
|
+
realizedPnL: exit.realizedPnL,
|
|
8633
|
+
costBasis: exit.costBasis,
|
|
8634
|
+
inferred: true
|
|
8635
|
+
}
|
|
8636
|
+
);
|
|
8637
|
+
}
|
|
8436
8638
|
if (!isPaper) {
|
|
8437
8639
|
const fundsOk = this.checkFundsLow(marketData);
|
|
8438
8640
|
if (!fundsOk) {
|
|
@@ -8525,7 +8727,7 @@ var AgentRuntime = class {
|
|
|
8525
8727
|
console.log(` [PAPER] Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
8526
8728
|
}
|
|
8527
8729
|
this.paperPortfolio.recordEquityPoint(marketData.prices);
|
|
8528
|
-
this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
8730
|
+
void this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
8529
8731
|
this.lastPortfolioValue = postValue;
|
|
8530
8732
|
this.paperPortfolio.save();
|
|
8531
8733
|
} else {
|
|
@@ -8556,6 +8758,9 @@ var AgentRuntime = class {
|
|
|
8556
8758
|
tokenOut: result.signal.tokenOut
|
|
8557
8759
|
}
|
|
8558
8760
|
);
|
|
8761
|
+
if (result.signal.action === "sell") {
|
|
8762
|
+
this.positionTracker.clearSellFailure(result.signal.tokenIn);
|
|
8763
|
+
}
|
|
8559
8764
|
} else {
|
|
8560
8765
|
console.warn(`Trade failed: ${result.error}`);
|
|
8561
8766
|
this.relay?.sendMessage(
|
|
@@ -8565,6 +8770,41 @@ var AgentRuntime = class {
|
|
|
8565
8770
|
result.error || "Unknown error",
|
|
8566
8771
|
{ action: result.signal.action }
|
|
8567
8772
|
);
|
|
8773
|
+
if (result.signal.action === "sell") {
|
|
8774
|
+
const failedToken = result.signal.tokenIn.toLowerCase();
|
|
8775
|
+
this.positionTracker.recordSellFailure(failedToken, result.error || "Unknown error");
|
|
8776
|
+
}
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8779
|
+
const stuckPositions = this.positionTracker.getStuckPositions(marketData.prices);
|
|
8780
|
+
for (const stuck of stuckPositions) {
|
|
8781
|
+
if (this.positionTracker.shouldAlertStuckPosition(stuck.token)) {
|
|
8782
|
+
const holdingDays = Math.floor(stuck.holdingDurationMs / (1e3 * 60 * 60 * 24));
|
|
8783
|
+
this.relay?.sendMessage(
|
|
8784
|
+
"position_stuck",
|
|
8785
|
+
"warning",
|
|
8786
|
+
`Position Stuck: ${stuck.symbol}`,
|
|
8787
|
+
`Unable to sell ${stuck.symbol} after ${stuck.consecutiveFailures} attempts. This position has been held for ${holdingDays} days with ~$${stuck.estimatedValueUSD.toFixed(2)} value.
|
|
8788
|
+
|
|
8789
|
+
Last error: ${stuck.lastFailureReason}
|
|
8790
|
+
|
|
8791
|
+
MANUAL EXIT REQUIRED:
|
|
8792
|
+
1. Export your wallet key: npx @exagent/agent export-key
|
|
8793
|
+
2. Import into MetaMask or Rabby
|
|
8794
|
+
3. Go to Uniswap/Aerodrome and swap ${stuck.symbol} \u2192 ETH directly
|
|
8795
|
+
4. Use high slippage (10-20%) for illiquid tokens
|
|
8796
|
+
|
|
8797
|
+
Note: Manual sells won't be tracked in your agent's PnL. The position will be removed from tracking once your balance is synced.
|
|
8798
|
+
|
|
8799
|
+
See: https://exagent.io/docs/guides/manual-exit`,
|
|
8800
|
+
{
|
|
8801
|
+
token: stuck.token,
|
|
8802
|
+
symbol: stuck.symbol,
|
|
8803
|
+
consecutiveFailures: stuck.consecutiveFailures,
|
|
8804
|
+
estimatedValueUSD: stuck.estimatedValueUSD,
|
|
8805
|
+
holdingDurationMs: stuck.holdingDurationMs
|
|
8806
|
+
}
|
|
8807
|
+
);
|
|
8568
8808
|
}
|
|
8569
8809
|
}
|
|
8570
8810
|
for (const result of results) {
|
|
@@ -8590,7 +8830,7 @@ var AgentRuntime = class {
|
|
|
8590
8830
|
if (marketPnL !== 0) {
|
|
8591
8831
|
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
8592
8832
|
}
|
|
8593
|
-
this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
8833
|
+
void this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
8594
8834
|
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
8595
8835
|
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
8596
8836
|
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|
|
@@ -9206,7 +9446,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
9206
9446
|
}
|
|
9207
9447
|
|
|
9208
9448
|
// src/index.ts
|
|
9209
|
-
var AGENT_VERSION = "0.1.
|
|
9449
|
+
var AGENT_VERSION = "0.1.43";
|
|
9210
9450
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9211
9451
|
0 && (module.exports = {
|
|
9212
9452
|
AGENT_VERSION,
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exagent/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "Autonomous trading agent runtime for Exagent",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"clean": "rm -rf dist"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@exagent/sdk": "^0.1.
|
|
32
|
+
"@exagent/sdk": "^0.1.19",
|
|
33
33
|
"@nktkas/hyperliquid": "^0.31.0",
|
|
34
34
|
"@polymarket/clob-client": "^4.0.0",
|
|
35
35
|
"chalk": "^5.3.0",
|