@exagent/agent 0.1.40 → 0.1.42
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-34JBZQO6.mjs +9475 -0
- package/dist/chunk-AEASQNGT.mjs +9475 -0
- package/dist/chunk-CQBWIY4B.mjs +9699 -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 +370 -52
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +119 -57
- package/dist/index.d.ts +119 -57
- package/dist/index.js +370 -52
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
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";
|
|
@@ -2666,13 +2830,6 @@ var VAULT_FACTORY_ABI = [
|
|
|
2666
2830
|
outputs: [{ type: "address" }],
|
|
2667
2831
|
stateMutability: "view"
|
|
2668
2832
|
},
|
|
2669
|
-
{
|
|
2670
|
-
type: "function",
|
|
2671
|
-
name: "canCreateVault",
|
|
2672
|
-
inputs: [{ name: "creator", type: "address" }],
|
|
2673
|
-
outputs: [{ name: "canCreate", type: "bool" }, { name: "reason", type: "string" }],
|
|
2674
|
-
stateMutability: "view"
|
|
2675
|
-
},
|
|
2676
2833
|
{
|
|
2677
2834
|
type: "function",
|
|
2678
2835
|
name: "createVault",
|
|
@@ -2689,10 +2846,17 @@ var VAULT_FACTORY_ABI = [
|
|
|
2689
2846
|
},
|
|
2690
2847
|
{
|
|
2691
2848
|
type: "function",
|
|
2692
|
-
name: "
|
|
2849
|
+
name: "MIN_SEED_AMOUNT",
|
|
2693
2850
|
inputs: [],
|
|
2694
2851
|
outputs: [{ type: "uint256" }],
|
|
2695
2852
|
stateMutability: "view"
|
|
2853
|
+
},
|
|
2854
|
+
{
|
|
2855
|
+
type: "function",
|
|
2856
|
+
name: "allowedAssets",
|
|
2857
|
+
inputs: [{ name: "asset", type: "address" }],
|
|
2858
|
+
outputs: [{ type: "bool" }],
|
|
2859
|
+
stateMutability: "view"
|
|
2696
2860
|
}
|
|
2697
2861
|
];
|
|
2698
2862
|
var VAULT_ABI = [
|
|
@@ -2790,23 +2954,34 @@ var VaultManager = class {
|
|
|
2790
2954
|
} catch {
|
|
2791
2955
|
}
|
|
2792
2956
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2957
|
+
let canCreate = true;
|
|
2958
|
+
let cannotCreateReason = null;
|
|
2959
|
+
if (hasVault) {
|
|
2960
|
+
canCreate = false;
|
|
2961
|
+
cannotCreateReason = "Vault already exists for this agent";
|
|
2962
|
+
} else {
|
|
2963
|
+
try {
|
|
2964
|
+
const isAssetAllowed = await this.publicClient.readContract({
|
|
2965
|
+
address: this.addresses.vaultFactory,
|
|
2966
|
+
abi: VAULT_FACTORY_ABI,
|
|
2967
|
+
functionName: "allowedAssets",
|
|
2968
|
+
args: [this.addresses.usdc]
|
|
2969
|
+
});
|
|
2970
|
+
if (!isAssetAllowed) {
|
|
2971
|
+
canCreate = false;
|
|
2972
|
+
cannotCreateReason = "USDC is not an allowed asset on the vault factory";
|
|
2973
|
+
}
|
|
2974
|
+
} catch {
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2802
2977
|
return {
|
|
2803
2978
|
hasVault,
|
|
2804
2979
|
vaultAddress,
|
|
2805
2980
|
totalAssets,
|
|
2806
|
-
canCreateVault:
|
|
2807
|
-
cannotCreateReason
|
|
2808
|
-
requirementsMet:
|
|
2809
|
-
requirements
|
|
2981
|
+
canCreateVault: canCreate,
|
|
2982
|
+
cannotCreateReason,
|
|
2983
|
+
requirementsMet: canCreate,
|
|
2984
|
+
requirements: { isBypassed: true }
|
|
2810
2985
|
};
|
|
2811
2986
|
}
|
|
2812
2987
|
/**
|
|
@@ -2854,11 +3029,67 @@ var VaultManager = class {
|
|
|
2854
3029
|
if (existingVault) {
|
|
2855
3030
|
return { success: false, error: "Vault already exists", vaultAddress: existingVault };
|
|
2856
3031
|
}
|
|
2857
|
-
const status = await this.getVaultStatus();
|
|
2858
|
-
if (!status.canCreateVault) {
|
|
2859
|
-
return { success: false, error: status.cannotCreateReason || "Requirements not met" };
|
|
2860
|
-
}
|
|
2861
3032
|
const seed = seedAmount || BigInt(1e8);
|
|
3033
|
+
let usdcBalance;
|
|
3034
|
+
try {
|
|
3035
|
+
usdcBalance = await this.publicClient.readContract({
|
|
3036
|
+
address: this.addresses.usdc,
|
|
3037
|
+
abi: import_viem3.erc20Abi,
|
|
3038
|
+
functionName: "balanceOf",
|
|
3039
|
+
args: [this.account.address]
|
|
3040
|
+
});
|
|
3041
|
+
} catch {
|
|
3042
|
+
return { success: false, error: "Failed to read USDC balance" };
|
|
3043
|
+
}
|
|
3044
|
+
if (usdcBalance < seed) {
|
|
3045
|
+
const needed = Number(seed) / 1e6;
|
|
3046
|
+
const have = Number(usdcBalance) / 1e6;
|
|
3047
|
+
return {
|
|
3048
|
+
success: false,
|
|
3049
|
+
error: `Insufficient USDC: need ${needed} USDC seed but wallet has ${have} USDC. Fund ${this.account.address} with at least ${needed} USDC and retry.`
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
try {
|
|
3053
|
+
const isAllowed = await this.publicClient.readContract({
|
|
3054
|
+
address: this.addresses.vaultFactory,
|
|
3055
|
+
abi: VAULT_FACTORY_ABI,
|
|
3056
|
+
functionName: "allowedAssets",
|
|
3057
|
+
args: [this.addresses.usdc]
|
|
3058
|
+
});
|
|
3059
|
+
if (!isAllowed) {
|
|
3060
|
+
return { success: false, error: "USDC is not whitelisted on the vault factory. Contact protocol admin." };
|
|
3061
|
+
}
|
|
3062
|
+
} catch {
|
|
3063
|
+
}
|
|
3064
|
+
try {
|
|
3065
|
+
const currentAllowance = await this.publicClient.readContract({
|
|
3066
|
+
address: this.addresses.usdc,
|
|
3067
|
+
abi: import_viem3.erc20Abi,
|
|
3068
|
+
functionName: "allowance",
|
|
3069
|
+
args: [this.account.address, this.addresses.vaultFactory]
|
|
3070
|
+
});
|
|
3071
|
+
if (currentAllowance < seed) {
|
|
3072
|
+
console.log(`Approving ${Number(seed) / 1e6} USDC to VaultFactory...`);
|
|
3073
|
+
const approveHash = await this.walletClient.writeContract({
|
|
3074
|
+
address: this.addresses.usdc,
|
|
3075
|
+
abi: import_viem3.erc20Abi,
|
|
3076
|
+
functionName: "approve",
|
|
3077
|
+
args: [this.addresses.vaultFactory, seed],
|
|
3078
|
+
chain: import_chains2.base,
|
|
3079
|
+
account: this.account
|
|
3080
|
+
});
|
|
3081
|
+
const approveReceipt = await this.publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
3082
|
+
if (approveReceipt.status !== "success") {
|
|
3083
|
+
return { success: false, error: "USDC approval transaction failed" };
|
|
3084
|
+
}
|
|
3085
|
+
console.log("USDC approved successfully");
|
|
3086
|
+
}
|
|
3087
|
+
} catch (error) {
|
|
3088
|
+
return {
|
|
3089
|
+
success: false,
|
|
3090
|
+
error: `USDC approval failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
2862
3093
|
const vaultName = this.config.vaultConfig.defaultName || `${this.config.agentName} Trading Vault`;
|
|
2863
3094
|
const vaultSymbol = this.config.vaultConfig.defaultSymbol || `ex${this.config.agentName.replace(/[^a-zA-Z]/g, "").slice(0, 4).toUpperCase()}`;
|
|
2864
3095
|
const feeRecipient = this.config.vaultConfig.feeRecipient || this.account.address;
|
|
@@ -2880,8 +3111,9 @@ var VaultManager = class {
|
|
|
2880
3111
|
});
|
|
2881
3112
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
2882
3113
|
if (receipt.status !== "success") {
|
|
2883
|
-
return { success: false, error: "
|
|
3114
|
+
return { success: false, error: "createVault transaction reverted on-chain", txHash: hash };
|
|
2884
3115
|
}
|
|
3116
|
+
this.lastVaultCheck = 0;
|
|
2885
3117
|
const vaultAddress = await this.getVaultAddress();
|
|
2886
3118
|
this.cachedVaultAddress = vaultAddress;
|
|
2887
3119
|
return {
|
|
@@ -2890,10 +3122,23 @@ var VaultManager = class {
|
|
|
2890
3122
|
txHash: hash
|
|
2891
3123
|
};
|
|
2892
3124
|
} catch (error) {
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
}
|
|
3125
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
3126
|
+
if (msg.includes("NotAgentOwner")) {
|
|
3127
|
+
return { success: false, error: "This wallet is not the owner of the agent. Only the agent owner can create a fund." };
|
|
3128
|
+
}
|
|
3129
|
+
if (msg.includes("InsufficientStake")) {
|
|
3130
|
+
return { success: false, error: "Staking requirement not met. This should not happen with StakingStub \u2014 contact protocol admin." };
|
|
3131
|
+
}
|
|
3132
|
+
if (msg.includes("FrontierCannotCreateVault")) {
|
|
3133
|
+
return { success: false, error: "Frontier-risk agents cannot create funds. Change the agent risk universe first." };
|
|
3134
|
+
}
|
|
3135
|
+
if (msg.includes("VaultAlreadyExists")) {
|
|
3136
|
+
return { success: false, error: "A fund already exists for this agent and asset." };
|
|
3137
|
+
}
|
|
3138
|
+
if (msg.includes("InsufficientSeed")) {
|
|
3139
|
+
return { success: false, error: `Seed amount too low. Minimum is 100 USDC.` };
|
|
3140
|
+
}
|
|
3141
|
+
return { success: false, error: msg };
|
|
2897
3142
|
}
|
|
2898
3143
|
}
|
|
2899
3144
|
/**
|
|
@@ -4667,7 +4912,7 @@ var HyperliquidWebSocket = class {
|
|
|
4667
4912
|
var import_viem5 = require("viem");
|
|
4668
4913
|
var import_chains3 = require("viem/chains");
|
|
4669
4914
|
var import_accounts3 = require("viem/accounts");
|
|
4670
|
-
var ROUTER_ADDRESS = "
|
|
4915
|
+
var ROUTER_ADDRESS = "0x20feB3054750970773Fe7370c391732EE0559743";
|
|
4671
4916
|
var ROUTER_ABI = [
|
|
4672
4917
|
{
|
|
4673
4918
|
type: "function",
|
|
@@ -5740,7 +5985,7 @@ var PredictionPositionManager = class {
|
|
|
5740
5985
|
var import_viem8 = require("viem");
|
|
5741
5986
|
var import_chains5 = require("viem/chains");
|
|
5742
5987
|
var import_accounts5 = require("viem/accounts");
|
|
5743
|
-
var ROUTER_ADDRESS2 = "
|
|
5988
|
+
var ROUTER_ADDRESS2 = "0x20feB3054750970773Fe7370c391732EE0559743";
|
|
5744
5989
|
var ROUTER_ABI2 = [
|
|
5745
5990
|
{
|
|
5746
5991
|
type: "function",
|
|
@@ -6975,7 +7220,18 @@ var AgentRuntime = class {
|
|
|
6975
7220
|
console.log(`Wallet: ${this.client.address}`);
|
|
6976
7221
|
const agent = await this.client.registry.getAgent(BigInt(this.config.agentId));
|
|
6977
7222
|
if (!agent) {
|
|
6978
|
-
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
|
+
);
|
|
6979
7235
|
}
|
|
6980
7236
|
console.log(`Agent verified: ${agent.name}`);
|
|
6981
7237
|
await this.ensureWalletLinked();
|
|
@@ -7499,7 +7755,7 @@ var AgentRuntime = class {
|
|
|
7499
7755
|
try {
|
|
7500
7756
|
const tokens = this.getTokensToTrack();
|
|
7501
7757
|
const initData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
7502
|
-
this.positionTracker.syncBalances(initData.balances, initData.prices);
|
|
7758
|
+
void this.positionTracker.syncBalances(initData.balances, initData.prices);
|
|
7503
7759
|
this.lastPortfolioValue = initData.portfolioValue;
|
|
7504
7760
|
this.lastPrices = initData.prices;
|
|
7505
7761
|
const nativeEthBal = initData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
@@ -8351,7 +8607,31 @@ var AgentRuntime = class {
|
|
|
8351
8607
|
this.lastPrices = marketData.prices;
|
|
8352
8608
|
const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
8353
8609
|
this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
|
|
8354
|
-
this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
8610
|
+
const inferredExits = this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
8611
|
+
for (const exit of inferredExits) {
|
|
8612
|
+
const pnlSign = exit.realizedPnL >= 0 ? "+" : "";
|
|
8613
|
+
this.relay?.sendMessage(
|
|
8614
|
+
"position_inferred_exit",
|
|
8615
|
+
exit.realizedPnL >= 0 ? "info" : "warning",
|
|
8616
|
+
`Inferred Exit: ${exit.symbol}`,
|
|
8617
|
+
`Detected manual sell of ${exit.amountExited.toFixed(4)} ${exit.symbol} outside the ExagentRouter.
|
|
8618
|
+
|
|
8619
|
+
Exit value: $${exit.exitValueUSD.toFixed(2)}
|
|
8620
|
+
Cost basis: $${exit.costBasis.toFixed(2)}
|
|
8621
|
+
Realized PnL: ${pnlSign}$${exit.realizedPnL.toFixed(2)}
|
|
8622
|
+
|
|
8623
|
+
This exit has been recorded in your trade history and counts toward your total PnL.`,
|
|
8624
|
+
{
|
|
8625
|
+
token: exit.token,
|
|
8626
|
+
symbol: exit.symbol,
|
|
8627
|
+
amountExited: exit.amountExited,
|
|
8628
|
+
exitValueUSD: exit.exitValueUSD,
|
|
8629
|
+
realizedPnL: exit.realizedPnL,
|
|
8630
|
+
costBasis: exit.costBasis,
|
|
8631
|
+
inferred: true
|
|
8632
|
+
}
|
|
8633
|
+
);
|
|
8634
|
+
}
|
|
8355
8635
|
if (!isPaper) {
|
|
8356
8636
|
const fundsOk = this.checkFundsLow(marketData);
|
|
8357
8637
|
if (!fundsOk) {
|
|
@@ -8444,7 +8724,7 @@ var AgentRuntime = class {
|
|
|
8444
8724
|
console.log(` [PAPER] Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
8445
8725
|
}
|
|
8446
8726
|
this.paperPortfolio.recordEquityPoint(marketData.prices);
|
|
8447
|
-
this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
8727
|
+
void this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
8448
8728
|
this.lastPortfolioValue = postValue;
|
|
8449
8729
|
this.paperPortfolio.save();
|
|
8450
8730
|
} else {
|
|
@@ -8475,6 +8755,9 @@ var AgentRuntime = class {
|
|
|
8475
8755
|
tokenOut: result.signal.tokenOut
|
|
8476
8756
|
}
|
|
8477
8757
|
);
|
|
8758
|
+
if (result.signal.action === "sell") {
|
|
8759
|
+
this.positionTracker.clearSellFailure(result.signal.tokenIn);
|
|
8760
|
+
}
|
|
8478
8761
|
} else {
|
|
8479
8762
|
console.warn(`Trade failed: ${result.error}`);
|
|
8480
8763
|
this.relay?.sendMessage(
|
|
@@ -8484,6 +8767,41 @@ var AgentRuntime = class {
|
|
|
8484
8767
|
result.error || "Unknown error",
|
|
8485
8768
|
{ action: result.signal.action }
|
|
8486
8769
|
);
|
|
8770
|
+
if (result.signal.action === "sell") {
|
|
8771
|
+
const failedToken = result.signal.tokenIn.toLowerCase();
|
|
8772
|
+
this.positionTracker.recordSellFailure(failedToken, result.error || "Unknown error");
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8776
|
+
const stuckPositions = this.positionTracker.getStuckPositions(marketData.prices);
|
|
8777
|
+
for (const stuck of stuckPositions) {
|
|
8778
|
+
if (this.positionTracker.shouldAlertStuckPosition(stuck.token)) {
|
|
8779
|
+
const holdingDays = Math.floor(stuck.holdingDurationMs / (1e3 * 60 * 60 * 24));
|
|
8780
|
+
this.relay?.sendMessage(
|
|
8781
|
+
"position_stuck",
|
|
8782
|
+
"warning",
|
|
8783
|
+
`Position Stuck: ${stuck.symbol}`,
|
|
8784
|
+
`Unable to sell ${stuck.symbol} after ${stuck.consecutiveFailures} attempts. This position has been held for ${holdingDays} days with ~$${stuck.estimatedValueUSD.toFixed(2)} value.
|
|
8785
|
+
|
|
8786
|
+
Last error: ${stuck.lastFailureReason}
|
|
8787
|
+
|
|
8788
|
+
MANUAL EXIT REQUIRED:
|
|
8789
|
+
1. Export your wallet key: npx @exagent/agent export-key
|
|
8790
|
+
2. Import into MetaMask or Rabby
|
|
8791
|
+
3. Go to Uniswap/Aerodrome and swap ${stuck.symbol} \u2192 ETH directly
|
|
8792
|
+
4. Use high slippage (10-20%) for illiquid tokens
|
|
8793
|
+
|
|
8794
|
+
Note: Manual sells won't be tracked in your agent's PnL. The position will be removed from tracking once your balance is synced.
|
|
8795
|
+
|
|
8796
|
+
See: https://exagent.io/docs/guides/manual-exit`,
|
|
8797
|
+
{
|
|
8798
|
+
token: stuck.token,
|
|
8799
|
+
symbol: stuck.symbol,
|
|
8800
|
+
consecutiveFailures: stuck.consecutiveFailures,
|
|
8801
|
+
estimatedValueUSD: stuck.estimatedValueUSD,
|
|
8802
|
+
holdingDurationMs: stuck.holdingDurationMs
|
|
8803
|
+
}
|
|
8804
|
+
);
|
|
8487
8805
|
}
|
|
8488
8806
|
}
|
|
8489
8807
|
for (const result of results) {
|
|
@@ -8509,7 +8827,7 @@ var AgentRuntime = class {
|
|
|
8509
8827
|
if (marketPnL !== 0) {
|
|
8510
8828
|
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
8511
8829
|
}
|
|
8512
|
-
this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
8830
|
+
void this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
8513
8831
|
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
8514
8832
|
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
8515
8833
|
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|
|
@@ -9125,7 +9443,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
9125
9443
|
}
|
|
9126
9444
|
|
|
9127
9445
|
// src/index.ts
|
|
9128
|
-
var AGENT_VERSION = "0.1.
|
|
9446
|
+
var AGENT_VERSION = "0.1.42";
|
|
9129
9447
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9130
9448
|
0 && (module.exports = {
|
|
9131
9449
|
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.42",
|
|
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",
|