@exagent/agent 0.1.10 → 0.1.12

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/cli.js CHANGED
@@ -31,7 +31,7 @@ var fs2 = __toESM(require("fs"));
31
31
  var path2 = __toESM(require("path"));
32
32
 
33
33
  // src/runtime.ts
34
- var import_sdk = require("@exagent/sdk");
34
+ var import_sdk2 = require("@exagent/sdk");
35
35
  var import_viem3 = require("viem");
36
36
  var import_chains2 = require("viem/chains");
37
37
 
@@ -838,9 +838,13 @@ function getAllStrategyTemplates() {
838
838
  var TradeExecutor = class {
839
839
  client;
840
840
  config;
841
+ allowedTokens;
841
842
  constructor(client, config) {
842
843
  this.client = client;
843
844
  this.config = config;
845
+ this.allowedTokens = new Set(
846
+ (config.allowedTokens || []).map((t) => t.toLowerCase())
847
+ );
844
848
  }
845
849
  /**
846
850
  * Execute a single trade signal
@@ -859,8 +863,7 @@ var TradeExecutor = class {
859
863
  tokenIn: signal.tokenIn,
860
864
  tokenOut: signal.tokenOut,
861
865
  amountIn: signal.amountIn,
862
- maxSlippageBps: 100
863
- // 1% default slippage
866
+ maxSlippageBps: this.config.trading?.maxSlippageBps ?? 100
864
867
  });
865
868
  console.log(`Trade executed: ${result.hash}`);
866
869
  return { success: true, txHash: result.hash };
@@ -886,13 +889,25 @@ var TradeExecutor = class {
886
889
  return results;
887
890
  }
888
891
  /**
889
- * Validate a signal against config limits
892
+ * Validate a signal against config limits and token restrictions
890
893
  */
891
894
  validateSignal(signal) {
892
895
  if (signal.confidence < 0.5) {
893
896
  console.warn(`Signal confidence ${signal.confidence} below threshold (0.5)`);
894
897
  return false;
895
898
  }
899
+ if (this.allowedTokens.size > 0) {
900
+ const tokenInAllowed = this.allowedTokens.has(signal.tokenIn.toLowerCase());
901
+ const tokenOutAllowed = this.allowedTokens.has(signal.tokenOut.toLowerCase());
902
+ if (!tokenInAllowed) {
903
+ console.warn(`Token ${signal.tokenIn} not in allowed list for this agent's risk universe \u2014 skipping`);
904
+ return false;
905
+ }
906
+ if (!tokenOutAllowed) {
907
+ console.warn(`Token ${signal.tokenOut} not in allowed list for this agent's risk universe \u2014 skipping`);
908
+ return false;
909
+ }
910
+ }
896
911
  return true;
897
912
  }
898
913
  delay(ms) {
@@ -900,103 +915,102 @@ var TradeExecutor = class {
900
915
  }
901
916
  };
902
917
 
903
- // src/trading/risk.ts
904
- var RiskManager = class {
905
- config;
906
- dailyPnL = 0;
907
- lastResetDate = "";
908
- constructor(config) {
909
- this.config = config;
910
- }
911
- /**
912
- * Filter signals through risk checks
913
- * Returns only signals that pass all guardrails
914
- */
915
- filterSignals(signals, marketData) {
916
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
917
- if (today !== this.lastResetDate) {
918
- this.dailyPnL = 0;
919
- this.lastResetDate = today;
920
- }
921
- if (this.isDailyLossLimitHit(marketData.portfolioValue)) {
922
- console.warn("Daily loss limit reached - no new trades");
923
- return [];
924
- }
925
- return signals.filter((signal) => this.validateSignal(signal, marketData));
926
- }
927
- /**
928
- * Validate individual signal against risk limits
929
- */
930
- validateSignal(signal, marketData) {
931
- if (signal.action === "hold") {
932
- return true;
933
- }
934
- const signalValue = this.estimateSignalValue(signal, marketData);
935
- const maxPositionValue = marketData.portfolioValue * this.config.maxPositionSizeBps / 1e4;
936
- if (signalValue > maxPositionValue) {
937
- console.warn(
938
- `Signal exceeds position limit: ${signalValue.toFixed(2)} > ${maxPositionValue.toFixed(2)}`
939
- );
940
- return false;
941
- }
942
- if (signal.confidence < 0.5) {
943
- console.warn(`Signal confidence too low: ${signal.confidence}`);
944
- return false;
945
- }
946
- return true;
947
- }
948
- /**
949
- * Check if daily loss limit has been hit
950
- */
951
- isDailyLossLimitHit(portfolioValue) {
952
- const maxLoss = portfolioValue * this.config.maxDailyLossBps / 1e4;
953
- return this.dailyPnL < -maxLoss;
954
- }
955
- /**
956
- * Estimate USD value of a trade signal
957
- */
958
- estimateSignalValue(signal, marketData) {
959
- const price = marketData.prices[signal.tokenIn] || 0;
960
- const amount = Number(signal.amountIn) / 1e18;
961
- return amount * price;
962
- }
963
- /**
964
- * Update daily PnL after a trade
965
- */
966
- updatePnL(pnl) {
967
- this.dailyPnL += pnl;
968
- }
969
- /**
970
- * Get current risk status
971
- */
972
- getStatus() {
973
- return {
974
- dailyPnL: this.dailyPnL,
975
- dailyLossLimit: this.config.maxDailyLossBps / 100,
976
- // As percentage
977
- isLimitHit: this.dailyPnL < -(this.config.maxDailyLossBps / 100)
978
- };
979
- }
980
- };
981
-
982
918
  // src/trading/market.ts
983
919
  var import_viem = require("viem");
984
920
  var NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
985
- var USDC_DECIMALS = {
921
+ var TOKEN_DECIMALS = {
922
+ // Base Mainnet — Core tokens
986
923
  "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 6,
987
- // USDC on Base
988
- "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": 6
989
- // USDbC on Base
924
+ // USDC
925
+ "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": 6,
926
+ // USDbC
927
+ "0x4200000000000000000000000000000000000006": 18,
928
+ // WETH
929
+ "0x50c5725949a6f0c72e6c4a641f24049a917db0cb": 18,
930
+ // DAI
931
+ "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22": 18,
932
+ // cbETH
933
+ [NATIVE_ETH.toLowerCase()]: 18,
934
+ // Native ETH
935
+ // Base Mainnet — Established tokens
936
+ "0x940181a94a35a4569e4529a3cdfb74e38fd98631": 18,
937
+ // AERO (Aerodrome)
938
+ "0x532f27101965dd16442e59d40670faf5ebb142e4": 18,
939
+ // BRETT
940
+ "0x4ed4e862860bed51a9570b96d89af5e1b0efefed": 18,
941
+ // DEGEN
942
+ "0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": 18,
943
+ // VIRTUAL
944
+ "0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4": 18,
945
+ // TOSHI
946
+ "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": 8,
947
+ // cbBTC
948
+ "0x2416092f143378750bb29b79ed961ab195cceea5": 18,
949
+ // ezETH (Renzo)
950
+ "0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": 18,
951
+ // wstETH (Lido)
952
+ // Base Sepolia
953
+ "0x036cbd53842c5426634e7929541ec2318f3dcf7e": 6
954
+ // USDC testnet
990
955
  };
956
+ function getTokenDecimals(address) {
957
+ const decimals = TOKEN_DECIMALS[address.toLowerCase()];
958
+ if (decimals === void 0) {
959
+ console.warn(`Unknown token decimals for ${address}, defaulting to 18. THIS MAY BE WRONG.`);
960
+ return 18;
961
+ }
962
+ return decimals;
963
+ }
964
+ var TOKEN_TO_COINGECKO = {
965
+ // Core
966
+ "0x4200000000000000000000000000000000000006": "ethereum",
967
+ // WETH
968
+ [NATIVE_ETH.toLowerCase()]: "ethereum",
969
+ "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": "usd-coin",
970
+ // USDC
971
+ "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": "usd-coin",
972
+ // USDbC
973
+ "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22": "coinbase-wrapped-staked-eth",
974
+ // cbETH
975
+ "0x50c5725949a6f0c72e6c4a641f24049a917db0cb": "dai",
976
+ // DAI
977
+ // Established
978
+ "0x940181a94a35a4569e4529a3cdfb74e38fd98631": "aerodrome-finance",
979
+ // AERO
980
+ "0x532f27101965dd16442e59d40670faf5ebb142e4": "brett",
981
+ // BRETT
982
+ "0x4ed4e862860bed51a9570b96d89af5e1b0efefed": "degen-base",
983
+ // DEGEN
984
+ "0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": "virtual-protocol",
985
+ // VIRTUAL
986
+ "0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4": "toshi",
987
+ // TOSHI
988
+ "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": "coinbase-wrapped-btc",
989
+ // cbBTC
990
+ "0x2416092f143378750bb29b79ed961ab195cceea5": "renzo-restaked-eth",
991
+ // ezETH
992
+ "0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": "wrapped-steth"
993
+ // wstETH
994
+ };
995
+ var STABLECOIN_IDS = /* @__PURE__ */ new Set(["usd-coin", "dai"]);
996
+ var PRICE_STALENESS_MS = 6e4;
991
997
  var MarketDataService = class {
992
998
  rpcUrl;
993
999
  client;
1000
+ /** Cached prices from last fetch */
1001
+ cachedPrices = {};
1002
+ /** Timestamp of last successful price fetch */
1003
+ lastPriceFetchAt = 0;
994
1004
  constructor(rpcUrl) {
995
1005
  this.rpcUrl = rpcUrl;
996
1006
  this.client = (0, import_viem.createPublicClient)({
997
1007
  transport: (0, import_viem.http)(rpcUrl)
998
1008
  });
999
1009
  }
1010
+ /** Cached volume data */
1011
+ cachedVolume24h = {};
1012
+ /** Cached price change data */
1013
+ cachedPriceChange24h = {};
1000
1014
  /**
1001
1015
  * Fetch current market data for the agent
1002
1016
  */
@@ -1004,31 +1018,126 @@ var MarketDataService = class {
1004
1018
  const prices = await this.fetchPrices(tokenAddresses);
1005
1019
  const balances = await this.fetchBalances(walletAddress, tokenAddresses);
1006
1020
  const portfolioValue = this.calculatePortfolioValue(balances, prices);
1021
+ let gasPrice;
1022
+ try {
1023
+ gasPrice = await this.client.getGasPrice();
1024
+ } catch {
1025
+ }
1007
1026
  return {
1008
1027
  timestamp: Date.now(),
1009
1028
  prices,
1010
1029
  balances,
1011
- portfolioValue
1030
+ portfolioValue,
1031
+ volume24h: Object.keys(this.cachedVolume24h).length > 0 ? { ...this.cachedVolume24h } : void 0,
1032
+ priceChange24h: Object.keys(this.cachedPriceChange24h).length > 0 ? { ...this.cachedPriceChange24h } : void 0,
1033
+ gasPrice,
1034
+ network: {
1035
+ chainId: this.client.chain?.id ?? 8453
1036
+ }
1012
1037
  };
1013
1038
  }
1014
1039
  /**
1015
- * Fetch token prices from price oracle
1040
+ * Check if cached prices are still fresh
1041
+ */
1042
+ get pricesAreFresh() {
1043
+ return Date.now() - this.lastPriceFetchAt < PRICE_STALENESS_MS;
1044
+ }
1045
+ /**
1046
+ * Fetch token prices from CoinGecko free API
1047
+ * Returns cached prices if still fresh (<60s old)
1016
1048
  */
1017
1049
  async fetchPrices(tokenAddresses) {
1050
+ if (this.pricesAreFresh && Object.keys(this.cachedPrices).length > 0) {
1051
+ const prices2 = { ...this.cachedPrices };
1052
+ for (const addr of tokenAddresses) {
1053
+ const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
1054
+ if (cgId && STABLECOIN_IDS.has(cgId) && !prices2[addr.toLowerCase()]) {
1055
+ prices2[addr.toLowerCase()] = 1;
1056
+ }
1057
+ }
1058
+ return prices2;
1059
+ }
1018
1060
  const prices = {};
1019
- const knownPrices = {
1020
- // Native ETH (sentinel address)
1021
- [NATIVE_ETH.toLowerCase()]: 3500,
1022
- // WETH
1023
- "0x4200000000000000000000000000000000000006": 3500,
1024
- // USDC
1025
- "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 1,
1026
- // USDbC (bridged USDC)
1027
- "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": 1
1028
- };
1029
- prices[NATIVE_ETH.toLowerCase()] = knownPrices[NATIVE_ETH.toLowerCase()];
1030
- for (const address of tokenAddresses) {
1031
- prices[address.toLowerCase()] = knownPrices[address.toLowerCase()] || 0;
1061
+ const idsToFetch = /* @__PURE__ */ new Set();
1062
+ for (const addr of tokenAddresses) {
1063
+ const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
1064
+ if (cgId && !STABLECOIN_IDS.has(cgId)) {
1065
+ idsToFetch.add(cgId);
1066
+ }
1067
+ }
1068
+ idsToFetch.add("ethereum");
1069
+ if (idsToFetch.size > 0) {
1070
+ try {
1071
+ const ids = Array.from(idsToFetch).join(",");
1072
+ const response = await fetch(
1073
+ `https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true`,
1074
+ { signal: AbortSignal.timeout(5e3) }
1075
+ );
1076
+ if (response.ok) {
1077
+ const data = await response.json();
1078
+ for (const [cgId, priceData] of Object.entries(data)) {
1079
+ for (const [addr, id] of Object.entries(TOKEN_TO_COINGECKO)) {
1080
+ if (id === cgId) {
1081
+ const key = addr.toLowerCase();
1082
+ prices[key] = priceData.usd;
1083
+ if (priceData.usd_24h_vol !== void 0) {
1084
+ this.cachedVolume24h[key] = priceData.usd_24h_vol;
1085
+ }
1086
+ if (priceData.usd_24h_change !== void 0) {
1087
+ this.cachedPriceChange24h[key] = priceData.usd_24h_change;
1088
+ }
1089
+ }
1090
+ }
1091
+ }
1092
+ this.lastPriceFetchAt = Date.now();
1093
+ } else {
1094
+ console.warn(`CoinGecko API returned ${response.status}, using cached prices`);
1095
+ }
1096
+ } catch (error) {
1097
+ console.warn("Failed to fetch prices from CoinGecko:", error instanceof Error ? error.message : error);
1098
+ }
1099
+ }
1100
+ for (const addr of tokenAddresses) {
1101
+ const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
1102
+ if (cgId && STABLECOIN_IDS.has(cgId)) {
1103
+ prices[addr.toLowerCase()] = 1;
1104
+ }
1105
+ }
1106
+ const missingAddrs = tokenAddresses.filter(
1107
+ (addr) => !prices[addr.toLowerCase()] && !STABLECOIN_IDS.has(TOKEN_TO_COINGECKO[addr.toLowerCase()] || "")
1108
+ );
1109
+ if (missingAddrs.length > 0) {
1110
+ try {
1111
+ const coins = missingAddrs.map((a) => `base:${a}`).join(",");
1112
+ const llamaResponse = await fetch(
1113
+ `https://coins.llama.fi/prices/current/${coins}`,
1114
+ { signal: AbortSignal.timeout(5e3) }
1115
+ );
1116
+ if (llamaResponse.ok) {
1117
+ const llamaData = await llamaResponse.json();
1118
+ for (const [key, data] of Object.entries(llamaData.coins)) {
1119
+ const addr = key.replace("base:", "").toLowerCase();
1120
+ if (data.price && data.confidence > 0.5) {
1121
+ prices[addr] = data.price;
1122
+ }
1123
+ }
1124
+ if (!this.lastPriceFetchAt) this.lastPriceFetchAt = Date.now();
1125
+ }
1126
+ } catch {
1127
+ }
1128
+ }
1129
+ if (Object.keys(prices).length > 0) {
1130
+ this.cachedPrices = prices;
1131
+ }
1132
+ if (Object.keys(prices).length === 0 && Object.keys(this.cachedPrices).length > 0) {
1133
+ console.warn("Using cached prices (last successful fetch was stale)");
1134
+ return { ...this.cachedPrices };
1135
+ }
1136
+ for (const addr of tokenAddresses) {
1137
+ if (!prices[addr.toLowerCase()]) {
1138
+ console.warn(`No price available for ${addr}, using 0`);
1139
+ prices[addr.toLowerCase()] = 0;
1140
+ }
1032
1141
  }
1033
1142
  return prices;
1034
1143
  }
@@ -1074,7 +1183,7 @@ var MarketDataService = class {
1074
1183
  let total = 0;
1075
1184
  for (const [address, balance] of Object.entries(balances)) {
1076
1185
  const price = prices[address.toLowerCase()] || 0;
1077
- const decimals = USDC_DECIMALS[address.toLowerCase()] || 18;
1186
+ const decimals = getTokenDecimals(address);
1078
1187
  const amount = Number(balance) / Math.pow(10, decimals);
1079
1188
  total += amount * price;
1080
1189
  }
@@ -1082,6 +1191,129 @@ var MarketDataService = class {
1082
1191
  }
1083
1192
  };
1084
1193
 
1194
+ // src/trading/risk.ts
1195
+ var RiskManager = class {
1196
+ config;
1197
+ dailyPnL = 0;
1198
+ dailyFees = 0;
1199
+ lastResetDate = "";
1200
+ /** Minimum trade value in USD — trades below this are rejected as dust */
1201
+ minTradeValueUSD;
1202
+ constructor(config) {
1203
+ this.config = config;
1204
+ this.minTradeValueUSD = config.minTradeValueUSD ?? 1;
1205
+ }
1206
+ /**
1207
+ * Filter signals through risk checks
1208
+ * Returns only signals that pass all guardrails
1209
+ */
1210
+ filterSignals(signals, marketData) {
1211
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1212
+ if (today !== this.lastResetDate) {
1213
+ this.dailyPnL = 0;
1214
+ this.dailyFees = 0;
1215
+ this.lastResetDate = today;
1216
+ }
1217
+ if (this.isDailyLossLimitHit(marketData.portfolioValue)) {
1218
+ console.warn("Daily loss limit reached - no new trades");
1219
+ return [];
1220
+ }
1221
+ return signals.filter((signal) => this.validateSignal(signal, marketData));
1222
+ }
1223
+ /**
1224
+ * Validate individual signal against risk limits
1225
+ */
1226
+ validateSignal(signal, marketData) {
1227
+ if (signal.action === "hold") {
1228
+ return true;
1229
+ }
1230
+ const signalValue = this.estimateSignalValue(signal, marketData);
1231
+ const maxPositionValue = marketData.portfolioValue * this.config.maxPositionSizeBps / 1e4;
1232
+ if (signalValue > maxPositionValue) {
1233
+ console.warn(
1234
+ `Signal exceeds position limit: ${signalValue.toFixed(2)} > ${maxPositionValue.toFixed(2)}`
1235
+ );
1236
+ return false;
1237
+ }
1238
+ if (signal.confidence < 0.5) {
1239
+ console.warn(`Signal confidence too low: ${signal.confidence}`);
1240
+ return false;
1241
+ }
1242
+ if (signal.action === "buy" && this.config.maxConcurrentPositions) {
1243
+ const activePositions = this.countActivePositions(marketData);
1244
+ if (activePositions >= this.config.maxConcurrentPositions) {
1245
+ console.warn(
1246
+ `Max concurrent positions reached: ${activePositions}/${this.config.maxConcurrentPositions} \u2014 blocking new buy`
1247
+ );
1248
+ return false;
1249
+ }
1250
+ }
1251
+ if (signalValue < this.minTradeValueUSD) {
1252
+ console.warn(`Trade value $${signalValue.toFixed(2)} below minimum $${this.minTradeValueUSD} \u2014 skipping`);
1253
+ return false;
1254
+ }
1255
+ return true;
1256
+ }
1257
+ /**
1258
+ * Count non-zero token positions (excluding native ETH and stablecoins used as base currency)
1259
+ */
1260
+ countActivePositions(marketData) {
1261
+ const NATIVE_ETH_KEY = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
1262
+ let count = 0;
1263
+ for (const [address, balance] of Object.entries(marketData.balances)) {
1264
+ if (address.toLowerCase() === NATIVE_ETH_KEY) continue;
1265
+ if (balance > 0n) count++;
1266
+ }
1267
+ return count;
1268
+ }
1269
+ /**
1270
+ * Check if daily loss limit has been hit
1271
+ */
1272
+ isDailyLossLimitHit(portfolioValue) {
1273
+ const maxLoss = portfolioValue * this.config.maxDailyLossBps / 1e4;
1274
+ return this.dailyPnL < -maxLoss;
1275
+ }
1276
+ /**
1277
+ * Estimate USD value of a trade signal
1278
+ */
1279
+ estimateSignalValue(signal, marketData) {
1280
+ const price = marketData.prices[signal.tokenIn.toLowerCase()] || 0;
1281
+ const tokenDecimals = getTokenDecimals(signal.tokenIn);
1282
+ const amount = Number(signal.amountIn) / Math.pow(10, tokenDecimals);
1283
+ return amount * price;
1284
+ }
1285
+ /**
1286
+ * Update daily PnL after a trade (market gains/losses only)
1287
+ */
1288
+ updatePnL(pnl) {
1289
+ this.dailyPnL += pnl;
1290
+ }
1291
+ /**
1292
+ * Update daily fees (trading fees, gas costs, etc.)
1293
+ * Fees are tracked separately and do NOT count toward the daily loss limit.
1294
+ * This prevents protocol fees from triggering circuit breakers.
1295
+ */
1296
+ updateFees(fees) {
1297
+ this.dailyFees += fees;
1298
+ }
1299
+ /**
1300
+ * Get current risk status
1301
+ * @param portfolioValue - Current portfolio value in USD (needed for accurate loss limit)
1302
+ */
1303
+ getStatus(portfolioValue) {
1304
+ const pv = portfolioValue || 0;
1305
+ const maxLossUSD = pv * this.config.maxDailyLossBps / 1e4;
1306
+ return {
1307
+ dailyPnL: this.dailyPnL,
1308
+ dailyFees: this.dailyFees,
1309
+ dailyNetPnL: this.dailyPnL - this.dailyFees,
1310
+ dailyLossLimit: maxLossUSD,
1311
+ // Only market PnL triggers the limit — fees are excluded
1312
+ isLimitHit: pv > 0 ? this.dailyPnL < -maxLossUSD : false
1313
+ };
1314
+ }
1315
+ };
1316
+
1085
1317
  // src/vault/manager.ts
1086
1318
  var import_viem2 = require("viem");
1087
1319
  var import_accounts = require("viem/accounts");
@@ -1093,9 +1325,8 @@ var ADDRESSES = {
1093
1325
  usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
1094
1326
  },
1095
1327
  mainnet: {
1096
- vaultFactory: "0x0000000000000000000000000000000000000000",
1097
- // TODO: Deploy
1098
- registry: "0x0000000000000000000000000000000000000000",
1328
+ vaultFactory: process.env.EXAGENT_VAULT_FACTORY_ADDRESS || "0x0000000000000000000000000000000000000000",
1329
+ registry: process.env.EXAGENT_REGISTRY_ADDRESS || "0x0000000000000000000000000000000000000000",
1099
1330
  usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
1100
1331
  // Base mainnet USDC
1101
1332
  }
@@ -1121,6 +1352,7 @@ var VAULT_FACTORY_ABI = [
1121
1352
  inputs: [
1122
1353
  { name: "agentId", type: "uint256" },
1123
1354
  { name: "asset", type: "address" },
1355
+ { name: "seedAmount", type: "uint256" },
1124
1356
  { name: "name", type: "string" },
1125
1357
  { name: "symbol", type: "string" },
1126
1358
  { name: "feeRecipient", type: "address" }
@@ -1134,13 +1366,6 @@ var VAULT_FACTORY_ABI = [
1134
1366
  inputs: [],
1135
1367
  outputs: [{ type: "uint256" }],
1136
1368
  stateMutability: "view"
1137
- },
1138
- {
1139
- type: "function",
1140
- name: "eXABurnFee",
1141
- inputs: [],
1142
- outputs: [{ type: "uint256" }],
1143
- stateMutability: "view"
1144
1369
  }
1145
1370
  ];
1146
1371
  var VAULT_ABI = [
@@ -1180,6 +1405,7 @@ var VaultManager = class {
1180
1405
  lastVaultCheck = 0;
1181
1406
  VAULT_CACHE_TTL = 6e4;
1182
1407
  // 1 minute
1408
+ enabled = true;
1183
1409
  constructor(config) {
1184
1410
  this.config = config;
1185
1411
  this.addresses = ADDRESSES[config.network];
@@ -1195,6 +1421,10 @@ var VaultManager = class {
1195
1421
  chain: this.chain,
1196
1422
  transport: (0, import_viem2.http)(rpcUrl)
1197
1423
  });
1424
+ if (this.addresses.vaultFactory === "0x0000000000000000000000000000000000000000") {
1425
+ console.warn("VaultFactory address is zero \u2014 vault operations will be disabled");
1426
+ this.enabled = false;
1427
+ }
1198
1428
  }
1199
1429
  /**
1200
1430
  * Get the agent's vault policy
@@ -1212,6 +1442,17 @@ var VaultManager = class {
1212
1442
  * Get comprehensive vault status
1213
1443
  */
1214
1444
  async getVaultStatus() {
1445
+ if (!this.enabled) {
1446
+ return {
1447
+ hasVault: false,
1448
+ vaultAddress: null,
1449
+ totalAssets: BigInt(0),
1450
+ canCreateVault: false,
1451
+ cannotCreateReason: "Vault operations disabled (contract address not set)",
1452
+ requirementsMet: false,
1453
+ requirements: { veXARequired: BigInt(0), isBypassed: false }
1454
+ };
1455
+ }
1215
1456
  const vaultAddress = await this.getVaultAddress();
1216
1457
  const hasVault = vaultAddress !== null;
1217
1458
  let totalAssets = BigInt(0);
@@ -1246,22 +1487,16 @@ var VaultManager = class {
1246
1487
  }
1247
1488
  /**
1248
1489
  * Get vault creation requirements
1490
+ * Note: No burnFee on mainnet — vault creation requires USDC seed instead
1249
1491
  */
1250
1492
  async getRequirements() {
1251
- const [veXARequired, burnFee] = await Promise.all([
1252
- this.publicClient.readContract({
1253
- address: this.addresses.vaultFactory,
1254
- abi: VAULT_FACTORY_ABI,
1255
- functionName: "minimumVeEXARequired"
1256
- }),
1257
- this.publicClient.readContract({
1258
- address: this.addresses.vaultFactory,
1259
- abi: VAULT_FACTORY_ABI,
1260
- functionName: "eXABurnFee"
1261
- })
1262
- ]);
1263
- const isBypassed = veXARequired === BigInt(0) && burnFee === BigInt(0);
1264
- return { veXARequired, burnFee, isBypassed };
1493
+ const veXARequired = await this.publicClient.readContract({
1494
+ address: this.addresses.vaultFactory,
1495
+ abi: VAULT_FACTORY_ABI,
1496
+ functionName: "minimumVeEXARequired"
1497
+ });
1498
+ const isBypassed = veXARequired === BigInt(0);
1499
+ return { veXARequired, isBypassed };
1265
1500
  }
1266
1501
  /**
1267
1502
  * Get the agent's vault address (cached)
@@ -1285,30 +1520,15 @@ var VaultManager = class {
1285
1520
  this.cachedVaultAddress = vaultAddress;
1286
1521
  return vaultAddress;
1287
1522
  }
1288
- /**
1289
- * Check if the agent should create a vault based on policy and qualification
1290
- */
1291
- async shouldCreateVault() {
1292
- if (this.policy === "disabled") {
1293
- return { should: false, reason: "Vault creation disabled by policy" };
1294
- }
1295
- if (this.policy === "manual") {
1296
- return { should: false, reason: "Vault creation set to manual - waiting for owner instruction" };
1297
- }
1298
- const status = await this.getVaultStatus();
1299
- if (status.hasVault) {
1300
- return { should: false, reason: "Vault already exists" };
1301
- }
1302
- if (!status.canCreateVault) {
1303
- return { should: false, reason: status.cannotCreateReason || "Requirements not met" };
1304
- }
1305
- return { should: true, reason: "Agent is qualified and auto-creation is enabled" };
1306
- }
1307
1523
  /**
1308
1524
  * Create a vault for the agent
1525
+ * @param seedAmount - USDC seed amount in raw units (default: 100e6 = 100 USDC)
1309
1526
  * @returns Vault address if successful
1310
1527
  */
1311
- async createVault() {
1528
+ async createVault(seedAmount) {
1529
+ if (!this.enabled) {
1530
+ return { success: false, error: "Vault operations disabled (contract address not set)" };
1531
+ }
1312
1532
  if (this.policy === "disabled") {
1313
1533
  return { success: false, error: "Vault creation disabled by policy" };
1314
1534
  }
@@ -1320,6 +1540,7 @@ var VaultManager = class {
1320
1540
  if (!status.canCreateVault) {
1321
1541
  return { success: false, error: status.cannotCreateReason || "Requirements not met" };
1322
1542
  }
1543
+ const seed = seedAmount || BigInt(1e8);
1323
1544
  const vaultName = this.config.vaultConfig.defaultName || `${this.config.agentName} Trading Vault`;
1324
1545
  const vaultSymbol = this.config.vaultConfig.defaultSymbol || `ex${this.config.agentName.replace(/[^a-zA-Z]/g, "").slice(0, 4).toUpperCase()}`;
1325
1546
  const feeRecipient = this.config.vaultConfig.feeRecipient || this.account.address;
@@ -1331,6 +1552,7 @@ var VaultManager = class {
1331
1552
  args: [
1332
1553
  this.config.agentId,
1333
1554
  this.addresses.usdc,
1555
+ seed,
1334
1556
  vaultName,
1335
1557
  vaultSymbol,
1336
1558
  feeRecipient
@@ -1394,37 +1616,12 @@ var VaultManager = class {
1394
1616
  };
1395
1617
  }
1396
1618
  }
1397
- /**
1398
- * Run the auto-creation check (call this periodically in the agent loop)
1399
- * Only creates vault if policy is 'auto_when_qualified'
1400
- */
1401
- async checkAndAutoCreateVault() {
1402
- const shouldCreate = await this.shouldCreateVault();
1403
- if (!shouldCreate.should) {
1404
- const status = await this.getVaultStatus();
1405
- if (status.hasVault) {
1406
- return { action: "already_exists", vaultAddress: status.vaultAddress, reason: "Vault already exists" };
1407
- }
1408
- if (this.policy !== "auto_when_qualified") {
1409
- return { action: "skipped", reason: shouldCreate.reason };
1410
- }
1411
- return { action: "not_qualified", reason: shouldCreate.reason };
1412
- }
1413
- const result = await this.createVault();
1414
- if (result.success) {
1415
- return {
1416
- action: "created",
1417
- vaultAddress: result.vaultAddress,
1418
- reason: "Vault created automatically"
1419
- };
1420
- }
1421
- return { action: "not_qualified", reason: result.error || "Creation failed" };
1422
- }
1423
1619
  };
1424
1620
 
1425
1621
  // src/relay.ts
1426
1622
  var import_ws = __toESM(require("ws"));
1427
1623
  var import_accounts2 = require("viem/accounts");
1624
+ var import_sdk = require("@exagent/sdk");
1428
1625
  var RelayClient = class {
1429
1626
  config;
1430
1627
  ws = null;
@@ -1527,7 +1724,8 @@ var RelayClient = class {
1527
1724
  agentId: this.config.agentId,
1528
1725
  wallet: account.address,
1529
1726
  timestamp,
1530
- signature
1727
+ signature,
1728
+ sdkVersion: import_sdk.SDK_VERSION
1531
1729
  });
1532
1730
  }
1533
1731
  /**
@@ -1689,6 +1887,7 @@ function openBrowser(url) {
1689
1887
 
1690
1888
  // src/runtime.ts
1691
1889
  var FUNDS_LOW_THRESHOLD = 5e-3;
1890
+ var FUNDS_CRITICAL_THRESHOLD = 1e-3;
1692
1891
  var AgentRuntime = class {
1693
1892
  config;
1694
1893
  client;
@@ -1702,14 +1901,13 @@ var AgentRuntime = class {
1702
1901
  isRunning = false;
1703
1902
  mode = "idle";
1704
1903
  configHash;
1705
- lastVaultCheck = 0;
1706
1904
  cycleCount = 0;
1707
1905
  lastCycleAt = 0;
1708
1906
  lastPortfolioValue = 0;
1709
1907
  lastEthBalance = "0";
1710
1908
  processAlive = true;
1711
- VAULT_CHECK_INTERVAL = 3e5;
1712
- // Check vault status every 5 minutes
1909
+ riskUniverse = 0;
1910
+ allowedTokens = /* @__PURE__ */ new Set();
1713
1911
  constructor(config) {
1714
1912
  this.config = config;
1715
1913
  }
@@ -1718,7 +1916,7 @@ var AgentRuntime = class {
1718
1916
  */
1719
1917
  async initialize() {
1720
1918
  console.log(`Initializing agent: ${this.config.name} (ID: ${this.config.agentId})`);
1721
- this.client = new import_sdk.ExagentClient({
1919
+ this.client = new import_sdk2.ExagentClient({
1722
1920
  privateKey: this.config.privateKey,
1723
1921
  network: this.config.network
1724
1922
  });
@@ -1729,6 +1927,7 @@ var AgentRuntime = class {
1729
1927
  }
1730
1928
  console.log(`Agent verified: ${agent.name}`);
1731
1929
  await this.ensureWalletLinked();
1930
+ await this.loadTradingRestrictions();
1732
1931
  console.log(`Initializing LLM: ${this.config.llm.provider}`);
1733
1932
  this.llm = await createLLMAdapter(this.config.llm);
1734
1933
  const llmMeta = this.llm.getMetadata();
@@ -1796,12 +1995,8 @@ var AgentRuntime = class {
1796
1995
  console.log(`Vault TVL: ${Number(status.totalAssets) / 1e6} USDC`);
1797
1996
  } else {
1798
1997
  console.log("No vault exists for this agent");
1799
- if (vaultConfig.policy === "auto_when_qualified") {
1800
- if (status.canCreateVault) {
1801
- console.log("Agent is qualified to create vault - will attempt on next check");
1802
- } else {
1803
- console.log(`Cannot create vault yet: ${status.cannotCreateReason}`);
1804
- }
1998
+ if (vaultConfig.policy === "manual") {
1999
+ console.log("Vault creation is manual \u2014 use the command center to create one");
1805
2000
  }
1806
2001
  }
1807
2002
  }
@@ -1820,7 +2015,7 @@ var AgentRuntime = class {
1820
2015
  if (agent?.owner.toLowerCase() !== address.toLowerCase()) {
1821
2016
  const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
1822
2017
  const nonce = await this.client.registry.getNonce(address);
1823
- const linkMessage = import_sdk.ExagentRegistry.generateLinkMessage(
2018
+ const linkMessage = import_sdk2.ExagentRegistry.generateLinkMessage(
1824
2019
  address,
1825
2020
  agentId,
1826
2021
  nonce
@@ -1860,6 +2055,49 @@ var AgentRuntime = class {
1860
2055
  console.log("Wallet already linked");
1861
2056
  }
1862
2057
  }
2058
+ /**
2059
+ * Load risk universe and allowed tokens from on-chain registry.
2060
+ * This prevents the agent from wasting gas on trades that will revert.
2061
+ */
2062
+ async loadTradingRestrictions() {
2063
+ const agentId = BigInt(this.config.agentId);
2064
+ const RISK_UNIVERSE_NAMES = ["Core", "Established", "Derivatives", "Emerging", "Frontier"];
2065
+ try {
2066
+ this.riskUniverse = await this.client.registry.getRiskUniverse(agentId);
2067
+ console.log(`Risk universe: ${RISK_UNIVERSE_NAMES[this.riskUniverse] || this.riskUniverse}`);
2068
+ const configTokens = this.config.allowedTokens || this.getDefaultTokens();
2069
+ const verified = [];
2070
+ for (const token of configTokens) {
2071
+ try {
2072
+ const allowed = await this.client.registry.isTradeAllowed(
2073
+ agentId,
2074
+ token,
2075
+ "0x0000000000000000000000000000000000000000"
2076
+ // zero = check token only
2077
+ );
2078
+ if (allowed) {
2079
+ this.allowedTokens.add(token.toLowerCase());
2080
+ verified.push(token);
2081
+ } else {
2082
+ console.warn(`Token ${token} not allowed for this agent's risk universe \u2014 excluded`);
2083
+ }
2084
+ } catch {
2085
+ this.allowedTokens.add(token.toLowerCase());
2086
+ verified.push(token);
2087
+ }
2088
+ }
2089
+ this.config.allowedTokens = verified;
2090
+ console.log(`Allowed tokens loaded: ${verified.length} tokens verified`);
2091
+ if (this.riskUniverse === 4) {
2092
+ console.warn("Frontier risk universe: vault creation is disabled");
2093
+ }
2094
+ } catch (error) {
2095
+ console.warn(
2096
+ "Could not load trading restrictions from registry (using defaults):",
2097
+ error instanceof Error ? error.message : error
2098
+ );
2099
+ }
2100
+ }
1863
2101
  /**
1864
2102
  * Sync the LLM config hash to chain for epoch tracking.
1865
2103
  * If the wallet has insufficient gas, enters a recovery loop
@@ -1868,7 +2106,7 @@ var AgentRuntime = class {
1868
2106
  async syncConfigHash() {
1869
2107
  const agentId = BigInt(this.config.agentId);
1870
2108
  const llmMeta = this.llm.getMetadata();
1871
- this.configHash = import_sdk.ExagentRegistry.calculateConfigHash(llmMeta.provider, llmMeta.model);
2109
+ this.configHash = import_sdk2.ExagentRegistry.calculateConfigHash(llmMeta.provider, llmMeta.model);
1872
2110
  console.log(`Config hash: ${this.configHash}`);
1873
2111
  const onChainHash = await this.client.registry.getConfigHash(agentId);
1874
2112
  if (onChainHash !== this.configHash) {
@@ -2032,21 +2270,38 @@ var AgentRuntime = class {
2032
2270
  break;
2033
2271
  case "update_risk_params": {
2034
2272
  const params = cmd.params || {};
2273
+ let updated = false;
2035
2274
  if (params.maxPositionSizeBps !== void 0) {
2036
- this.config.trading.maxPositionSizeBps = Number(params.maxPositionSizeBps);
2275
+ const value = Number(params.maxPositionSizeBps);
2276
+ if (isNaN(value) || value < 100 || value > 1e4) {
2277
+ this.relay?.sendCommandResult(cmd.id, false, "maxPositionSizeBps must be 100-10000");
2278
+ break;
2279
+ }
2280
+ this.config.trading.maxPositionSizeBps = value;
2281
+ updated = true;
2037
2282
  }
2038
2283
  if (params.maxDailyLossBps !== void 0) {
2039
- this.config.trading.maxDailyLossBps = Number(params.maxDailyLossBps);
2284
+ const value = Number(params.maxDailyLossBps);
2285
+ if (isNaN(value) || value < 50 || value > 5e3) {
2286
+ this.relay?.sendCommandResult(cmd.id, false, "maxDailyLossBps must be 50-5000");
2287
+ break;
2288
+ }
2289
+ this.config.trading.maxDailyLossBps = value;
2290
+ updated = true;
2291
+ }
2292
+ if (updated) {
2293
+ this.riskManager = new RiskManager(this.config.trading);
2294
+ console.log("Risk params updated via command center");
2295
+ this.relay?.sendCommandResult(cmd.id, true, "Risk parameters updated");
2296
+ this.relay?.sendMessage(
2297
+ "config_updated",
2298
+ "info",
2299
+ "Risk Parameters Updated",
2300
+ `Max position: ${this.config.trading.maxPositionSizeBps / 100}%, Max daily loss: ${this.config.trading.maxDailyLossBps / 100}%`
2301
+ );
2302
+ } else {
2303
+ this.relay?.sendCommandResult(cmd.id, false, "No valid parameters provided");
2040
2304
  }
2041
- this.riskManager = new RiskManager(this.config.trading);
2042
- console.log("Risk params updated via command center");
2043
- this.relay?.sendCommandResult(cmd.id, true, "Risk params updated");
2044
- this.relay?.sendMessage(
2045
- "config_updated",
2046
- "info",
2047
- "Risk Parameters Updated",
2048
- `Max position: ${this.config.trading.maxPositionSizeBps / 100}%, Max daily loss: ${this.config.trading.maxDailyLossBps / 100}%`
2049
- );
2050
2305
  break;
2051
2306
  }
2052
2307
  case "update_trading_interval": {
@@ -2123,7 +2378,7 @@ var AgentRuntime = class {
2123
2378
  provider: this.config.llm.provider,
2124
2379
  model: this.config.llm.model || "default"
2125
2380
  },
2126
- risk: this.riskManager?.getStatus() || {
2381
+ risk: this.riskManager?.getStatus(this.lastPortfolioValue) || {
2127
2382
  dailyPnL: 0,
2128
2383
  dailyLossLimit: 0,
2129
2384
  isLimitHit: false
@@ -2144,14 +2399,18 @@ var AgentRuntime = class {
2144
2399
  --- Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
2145
2400
  this.cycleCount++;
2146
2401
  this.lastCycleAt = Date.now();
2147
- await this.checkVaultAutoCreation();
2148
2402
  const tokens = this.config.allowedTokens || this.getDefaultTokens();
2149
2403
  const marketData = await this.marketData.fetchMarketData(this.client.address, tokens);
2150
2404
  console.log(`Portfolio value: $${marketData.portfolioValue.toFixed(2)}`);
2151
2405
  this.lastPortfolioValue = marketData.portfolioValue;
2152
2406
  const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
2153
2407
  this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
2154
- this.checkFundsLow(marketData);
2408
+ const fundsOk = this.checkFundsLow(marketData);
2409
+ if (!fundsOk) {
2410
+ console.warn("Skipping trading cycle \u2014 ETH balance critically low");
2411
+ this.sendRelayStatus();
2412
+ return;
2413
+ }
2155
2414
  let signals;
2156
2415
  try {
2157
2416
  signals = await this.strategy(marketData, this.llm, this.config);
@@ -2169,19 +2428,31 @@ var AgentRuntime = class {
2169
2428
  console.log(`Strategy generated ${signals.length} signals`);
2170
2429
  const filteredSignals = this.riskManager.filterSignals(signals, marketData);
2171
2430
  console.log(`${filteredSignals.length} signals passed risk checks`);
2172
- if (this.riskManager.getStatus().isLimitHit) {
2431
+ if (this.riskManager.getStatus(marketData.portfolioValue).isLimitHit) {
2173
2432
  this.relay?.sendMessage(
2174
2433
  "risk_limit_hit",
2175
2434
  "warning",
2176
2435
  "Risk Limit Hit",
2177
- `Daily loss limit reached: ${this.riskManager.getStatus().dailyPnL.toFixed(2)}`
2436
+ `Daily loss limit reached: $${this.riskManager.getStatus(marketData.portfolioValue).dailyPnL.toFixed(2)}`
2178
2437
  );
2179
2438
  }
2180
2439
  if (filteredSignals.length > 0) {
2440
+ const vaultStatus = await this.vaultManager?.getVaultStatus();
2441
+ if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
2442
+ console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
2443
+ }
2444
+ const preTradePortfolioValue = marketData.portfolioValue;
2181
2445
  const results = await this.executor.executeAll(filteredSignals);
2446
+ let totalFeesUSD = 0;
2182
2447
  for (const result of results) {
2183
2448
  if (result.success) {
2184
2449
  console.log(`Trade executed: ${result.signal.action} - ${result.txHash}`);
2450
+ const feeCostBps = 20;
2451
+ const signalPrice = marketData.prices[result.signal.tokenIn.toLowerCase()] || 0;
2452
+ const amountUSD = Number(result.signal.amountIn) / Math.pow(10, getTokenDecimals(result.signal.tokenIn)) * signalPrice;
2453
+ const feeCostUSD = amountUSD * feeCostBps / 1e4;
2454
+ totalFeesUSD += feeCostUSD;
2455
+ this.riskManager.updateFees(feeCostUSD);
2185
2456
  this.relay?.sendMessage(
2186
2457
  "trade_executed",
2187
2458
  "success",
@@ -2205,18 +2476,43 @@ var AgentRuntime = class {
2205
2476
  );
2206
2477
  }
2207
2478
  }
2479
+ const postTokens = this.config.allowedTokens || this.getDefaultTokens();
2480
+ const postTradeData = await this.marketData.fetchMarketData(this.client.address, postTokens);
2481
+ const marketPnL = postTradeData.portfolioValue - preTradePortfolioValue + totalFeesUSD;
2482
+ this.riskManager.updatePnL(marketPnL);
2483
+ if (marketPnL !== 0) {
2484
+ console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
2485
+ }
2486
+ this.lastPortfolioValue = postTradeData.portfolioValue;
2487
+ const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
2488
+ this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
2208
2489
  }
2209
2490
  this.sendRelayStatus();
2210
2491
  }
2211
2492
  /**
2212
- * Check if ETH balance is below threshold and notify
2493
+ * Check if ETH balance is below threshold and notify.
2494
+ * Returns true if trading should continue, false if ETH is critically low.
2213
2495
  */
2214
2496
  checkFundsLow(marketData) {
2215
- if (!this.relay) return;
2216
2497
  const ethBalance = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
2217
2498
  const ethAmount = Number(ethBalance) / 1e18;
2499
+ if (ethAmount < FUNDS_CRITICAL_THRESHOLD) {
2500
+ console.error(`ETH balance critically low: ${ethAmount.toFixed(6)} ETH \u2014 halting trading`);
2501
+ this.relay?.sendMessage(
2502
+ "funds_low",
2503
+ "error",
2504
+ "Funds Critical",
2505
+ `ETH balance is ${ethAmount.toFixed(6)} ETH (below ${FUNDS_CRITICAL_THRESHOLD} ETH minimum). Trading halted \u2014 fund your wallet.`,
2506
+ {
2507
+ ethBalance: ethAmount.toFixed(6),
2508
+ wallet: this.client.address,
2509
+ threshold: FUNDS_CRITICAL_THRESHOLD
2510
+ }
2511
+ );
2512
+ return false;
2513
+ }
2218
2514
  if (ethAmount < FUNDS_LOW_THRESHOLD) {
2219
- this.relay.sendMessage(
2515
+ this.relay?.sendMessage(
2220
2516
  "funds_low",
2221
2517
  "warning",
2222
2518
  "Low Funds",
@@ -2228,36 +2524,7 @@ var AgentRuntime = class {
2228
2524
  }
2229
2525
  );
2230
2526
  }
2231
- }
2232
- /**
2233
- * Check for vault auto-creation based on policy
2234
- */
2235
- async checkVaultAutoCreation() {
2236
- const now = Date.now();
2237
- if (now - this.lastVaultCheck < this.VAULT_CHECK_INTERVAL) {
2238
- return;
2239
- }
2240
- this.lastVaultCheck = now;
2241
- const result = await this.vaultManager.checkAndAutoCreateVault();
2242
- switch (result.action) {
2243
- case "created":
2244
- console.log(`Vault created automatically: ${result.vaultAddress}`);
2245
- this.relay?.sendMessage(
2246
- "vault_created",
2247
- "success",
2248
- "Vault Auto-Created",
2249
- `Vault deployed at ${result.vaultAddress}`,
2250
- { vaultAddress: result.vaultAddress }
2251
- );
2252
- break;
2253
- case "already_exists":
2254
- break;
2255
- case "skipped":
2256
- break;
2257
- case "not_qualified":
2258
- console.log(`Vault auto-creation pending: ${result.reason}`);
2259
- break;
2260
- }
2527
+ return true;
2261
2528
  }
2262
2529
  /**
2263
2530
  * Stop the agent process completely
@@ -2281,15 +2548,42 @@ var AgentRuntime = class {
2281
2548
  return "https://sepolia.base.org";
2282
2549
  }
2283
2550
  /**
2284
- * Default tokens to track
2551
+ * Default tokens to track.
2552
+ * These are validated against the on-chain registry's isTradeAllowed() during init —
2553
+ * agents in restricted risk universes will have ineligible tokens filtered out.
2285
2554
  */
2286
2555
  getDefaultTokens() {
2287
2556
  if (this.config.network === "mainnet") {
2288
2557
  return [
2558
+ // Core
2289
2559
  "0x4200000000000000000000000000000000000006",
2290
2560
  // WETH
2291
- "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
2561
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2292
2562
  // USDC
2563
+ "0x2Ae3F1Ec7F1F5012CFEab0185bFC7aa3cf0DEC22",
2564
+ // cbETH
2565
+ "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
2566
+ // USDbC
2567
+ "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
2568
+ // DAI
2569
+ // Established
2570
+ "0x940181a94A35A4569E4529A3CDfB74e38FD98631",
2571
+ // AERO
2572
+ "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
2573
+ // cbBTC
2574
+ "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
2575
+ // wstETH
2576
+ "0x2416092f143378750bb29b79eD961ab195CcEea5",
2577
+ // ezETH
2578
+ // Emerging (filtered by risk universe at init)
2579
+ "0x532f27101965dd16442E59d40670FaF5eBB142E4",
2580
+ // BRETT
2581
+ "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed",
2582
+ // DEGEN
2583
+ "0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b",
2584
+ // VIRTUAL
2585
+ "0xAC1Bd2486Aaf3B5C0fc3Fd868558b082a531B2B4"
2586
+ // TOSHI
2293
2587
  ];
2294
2588
  }
2295
2589
  return [
@@ -2315,7 +2609,7 @@ var AgentRuntime = class {
2315
2609
  model: this.config.llm.model || "default"
2316
2610
  },
2317
2611
  configHash: this.configHash || "not initialized",
2318
- risk: this.riskManager?.getStatus() || { dailyPnL: 0, dailyLossLimit: 0, isLimitHit: false },
2612
+ risk: this.riskManager?.getStatus(this.lastPortfolioValue) || { dailyPnL: 0, dailyLossLimit: 0, isLimitHit: false },
2319
2613
  vault: {
2320
2614
  policy: vaultConfig.policy,
2321
2615
  hasVault: false,
@@ -2377,16 +2671,18 @@ var TradingConfigSchema = import_zod.z.object({
2377
2671
  maxDailyLossBps: import_zod.z.number().min(0).max(1e4).default(500),
2378
2672
  // 0-100%
2379
2673
  maxConcurrentPositions: import_zod.z.number().min(1).max(100).default(5),
2380
- tradingIntervalMs: import_zod.z.number().min(1e3).default(6e4)
2674
+ tradingIntervalMs: import_zod.z.number().min(1e3).default(6e4),
2381
2675
  // minimum 1 second
2676
+ maxSlippageBps: import_zod.z.number().min(10).max(1e3).default(100),
2677
+ // 0.1-10%, default 1%
2678
+ minTradeValueUSD: import_zod.z.number().min(0).default(1)
2679
+ // minimum trade value in USD
2382
2680
  });
2383
2681
  var VaultPolicySchema = import_zod.z.enum([
2384
2682
  "disabled",
2385
2683
  // Never create a vault - trade with agent's own capital only
2386
- "manual",
2684
+ "manual"
2387
2685
  // Only create vault when explicitly directed by owner
2388
- "auto_when_qualified"
2389
- // Automatically create vault when requirements are met
2390
2686
  ]);
2391
2687
  var VaultConfigSchema = import_zod.z.object({
2392
2688
  // Policy for vault creation (asked during deployment)
@@ -2921,7 +3217,16 @@ program.command("run").description("Start the trading agent").option("-c, --conf
2921
3217
  const configDir = path2.dirname(
2922
3218
  options.config.startsWith("/") ? options.config : path2.join(process.cwd(), options.config)
2923
3219
  );
2924
- const usedEncrypted = loadSecureEnv(configDir, options.passphrase);
3220
+ let passphrase = options.passphrase || process.env.EXAGENT_PASSPHRASE;
3221
+ const encPath = path2.join(configDir, ".env.enc");
3222
+ if (fs2.existsSync(encPath) && !passphrase) {
3223
+ console.log("");
3224
+ console.log(" Encrypted config found (.env.enc)");
3225
+ console.log("");
3226
+ passphrase = await prompt(" Enter passphrase: ", true);
3227
+ console.log("");
3228
+ }
3229
+ const usedEncrypted = loadSecureEnv(configDir, passphrase);
2925
3230
  if (usedEncrypted) {
2926
3231
  console.log("Loaded encrypted environment (.env.enc)");
2927
3232
  }