@exagent/agent 0.1.11 → 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/index.js CHANGED
@@ -69,7 +69,7 @@ __export(index_exports, {
69
69
  module.exports = __toCommonJS(index_exports);
70
70
 
71
71
  // src/runtime.ts
72
- var import_sdk = require("@exagent/sdk");
72
+ var import_sdk2 = require("@exagent/sdk");
73
73
  var import_viem3 = require("viem");
74
74
  var import_chains2 = require("viem/chains");
75
75
 
@@ -882,9 +882,13 @@ function getAllStrategyTemplates() {
882
882
  var TradeExecutor = class {
883
883
  client;
884
884
  config;
885
+ allowedTokens;
885
886
  constructor(client, config) {
886
887
  this.client = client;
887
888
  this.config = config;
889
+ this.allowedTokens = new Set(
890
+ (config.allowedTokens || []).map((t) => t.toLowerCase())
891
+ );
888
892
  }
889
893
  /**
890
894
  * Execute a single trade signal
@@ -903,8 +907,7 @@ var TradeExecutor = class {
903
907
  tokenIn: signal.tokenIn,
904
908
  tokenOut: signal.tokenOut,
905
909
  amountIn: signal.amountIn,
906
- maxSlippageBps: 100
907
- // 1% default slippage
910
+ maxSlippageBps: this.config.trading?.maxSlippageBps ?? 100
908
911
  });
909
912
  console.log(`Trade executed: ${result.hash}`);
910
913
  return { success: true, txHash: result.hash };
@@ -930,13 +933,25 @@ var TradeExecutor = class {
930
933
  return results;
931
934
  }
932
935
  /**
933
- * Validate a signal against config limits
936
+ * Validate a signal against config limits and token restrictions
934
937
  */
935
938
  validateSignal(signal) {
936
939
  if (signal.confidence < 0.5) {
937
940
  console.warn(`Signal confidence ${signal.confidence} below threshold (0.5)`);
938
941
  return false;
939
942
  }
943
+ if (this.allowedTokens.size > 0) {
944
+ const tokenInAllowed = this.allowedTokens.has(signal.tokenIn.toLowerCase());
945
+ const tokenOutAllowed = this.allowedTokens.has(signal.tokenOut.toLowerCase());
946
+ if (!tokenInAllowed) {
947
+ console.warn(`Token ${signal.tokenIn} not in allowed list for this agent's risk universe \u2014 skipping`);
948
+ return false;
949
+ }
950
+ if (!tokenOutAllowed) {
951
+ console.warn(`Token ${signal.tokenOut} not in allowed list for this agent's risk universe \u2014 skipping`);
952
+ return false;
953
+ }
954
+ }
940
955
  return true;
941
956
  }
942
957
  delay(ms) {
@@ -944,103 +959,102 @@ var TradeExecutor = class {
944
959
  }
945
960
  };
946
961
 
947
- // src/trading/risk.ts
948
- var RiskManager = class {
949
- config;
950
- dailyPnL = 0;
951
- lastResetDate = "";
952
- constructor(config) {
953
- this.config = config;
954
- }
955
- /**
956
- * Filter signals through risk checks
957
- * Returns only signals that pass all guardrails
958
- */
959
- filterSignals(signals, marketData) {
960
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
961
- if (today !== this.lastResetDate) {
962
- this.dailyPnL = 0;
963
- this.lastResetDate = today;
964
- }
965
- if (this.isDailyLossLimitHit(marketData.portfolioValue)) {
966
- console.warn("Daily loss limit reached - no new trades");
967
- return [];
968
- }
969
- return signals.filter((signal) => this.validateSignal(signal, marketData));
970
- }
971
- /**
972
- * Validate individual signal against risk limits
973
- */
974
- validateSignal(signal, marketData) {
975
- if (signal.action === "hold") {
976
- return true;
977
- }
978
- const signalValue = this.estimateSignalValue(signal, marketData);
979
- const maxPositionValue = marketData.portfolioValue * this.config.maxPositionSizeBps / 1e4;
980
- if (signalValue > maxPositionValue) {
981
- console.warn(
982
- `Signal exceeds position limit: ${signalValue.toFixed(2)} > ${maxPositionValue.toFixed(2)}`
983
- );
984
- return false;
985
- }
986
- if (signal.confidence < 0.5) {
987
- console.warn(`Signal confidence too low: ${signal.confidence}`);
988
- return false;
989
- }
990
- return true;
991
- }
992
- /**
993
- * Check if daily loss limit has been hit
994
- */
995
- isDailyLossLimitHit(portfolioValue) {
996
- const maxLoss = portfolioValue * this.config.maxDailyLossBps / 1e4;
997
- return this.dailyPnL < -maxLoss;
998
- }
999
- /**
1000
- * Estimate USD value of a trade signal
1001
- */
1002
- estimateSignalValue(signal, marketData) {
1003
- const price = marketData.prices[signal.tokenIn] || 0;
1004
- const amount = Number(signal.amountIn) / 1e18;
1005
- return amount * price;
1006
- }
1007
- /**
1008
- * Update daily PnL after a trade
1009
- */
1010
- updatePnL(pnl) {
1011
- this.dailyPnL += pnl;
1012
- }
1013
- /**
1014
- * Get current risk status
1015
- */
1016
- getStatus() {
1017
- return {
1018
- dailyPnL: this.dailyPnL,
1019
- dailyLossLimit: this.config.maxDailyLossBps / 100,
1020
- // As percentage
1021
- isLimitHit: this.dailyPnL < -(this.config.maxDailyLossBps / 100)
1022
- };
1023
- }
1024
- };
1025
-
1026
962
  // src/trading/market.ts
1027
963
  var import_viem = require("viem");
1028
964
  var NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
1029
- var USDC_DECIMALS = {
965
+ var TOKEN_DECIMALS = {
966
+ // Base Mainnet — Core tokens
1030
967
  "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 6,
1031
- // USDC on Base
1032
- "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": 6
1033
- // USDbC on Base
968
+ // USDC
969
+ "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": 6,
970
+ // USDbC
971
+ "0x4200000000000000000000000000000000000006": 18,
972
+ // WETH
973
+ "0x50c5725949a6f0c72e6c4a641f24049a917db0cb": 18,
974
+ // DAI
975
+ "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22": 18,
976
+ // cbETH
977
+ [NATIVE_ETH.toLowerCase()]: 18,
978
+ // Native ETH
979
+ // Base Mainnet — Established tokens
980
+ "0x940181a94a35a4569e4529a3cdfb74e38fd98631": 18,
981
+ // AERO (Aerodrome)
982
+ "0x532f27101965dd16442e59d40670faf5ebb142e4": 18,
983
+ // BRETT
984
+ "0x4ed4e862860bed51a9570b96d89af5e1b0efefed": 18,
985
+ // DEGEN
986
+ "0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": 18,
987
+ // VIRTUAL
988
+ "0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4": 18,
989
+ // TOSHI
990
+ "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": 8,
991
+ // cbBTC
992
+ "0x2416092f143378750bb29b79ed961ab195cceea5": 18,
993
+ // ezETH (Renzo)
994
+ "0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": 18,
995
+ // wstETH (Lido)
996
+ // Base Sepolia
997
+ "0x036cbd53842c5426634e7929541ec2318f3dcf7e": 6
998
+ // USDC testnet
1034
999
  };
1000
+ function getTokenDecimals(address) {
1001
+ const decimals = TOKEN_DECIMALS[address.toLowerCase()];
1002
+ if (decimals === void 0) {
1003
+ console.warn(`Unknown token decimals for ${address}, defaulting to 18. THIS MAY BE WRONG.`);
1004
+ return 18;
1005
+ }
1006
+ return decimals;
1007
+ }
1008
+ var TOKEN_TO_COINGECKO = {
1009
+ // Core
1010
+ "0x4200000000000000000000000000000000000006": "ethereum",
1011
+ // WETH
1012
+ [NATIVE_ETH.toLowerCase()]: "ethereum",
1013
+ "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": "usd-coin",
1014
+ // USDC
1015
+ "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": "usd-coin",
1016
+ // USDbC
1017
+ "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22": "coinbase-wrapped-staked-eth",
1018
+ // cbETH
1019
+ "0x50c5725949a6f0c72e6c4a641f24049a917db0cb": "dai",
1020
+ // DAI
1021
+ // Established
1022
+ "0x940181a94a35a4569e4529a3cdfb74e38fd98631": "aerodrome-finance",
1023
+ // AERO
1024
+ "0x532f27101965dd16442e59d40670faf5ebb142e4": "brett",
1025
+ // BRETT
1026
+ "0x4ed4e862860bed51a9570b96d89af5e1b0efefed": "degen-base",
1027
+ // DEGEN
1028
+ "0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": "virtual-protocol",
1029
+ // VIRTUAL
1030
+ "0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4": "toshi",
1031
+ // TOSHI
1032
+ "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": "coinbase-wrapped-btc",
1033
+ // cbBTC
1034
+ "0x2416092f143378750bb29b79ed961ab195cceea5": "renzo-restaked-eth",
1035
+ // ezETH
1036
+ "0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": "wrapped-steth"
1037
+ // wstETH
1038
+ };
1039
+ var STABLECOIN_IDS = /* @__PURE__ */ new Set(["usd-coin", "dai"]);
1040
+ var PRICE_STALENESS_MS = 6e4;
1035
1041
  var MarketDataService = class {
1036
1042
  rpcUrl;
1037
1043
  client;
1044
+ /** Cached prices from last fetch */
1045
+ cachedPrices = {};
1046
+ /** Timestamp of last successful price fetch */
1047
+ lastPriceFetchAt = 0;
1038
1048
  constructor(rpcUrl) {
1039
1049
  this.rpcUrl = rpcUrl;
1040
1050
  this.client = (0, import_viem.createPublicClient)({
1041
1051
  transport: (0, import_viem.http)(rpcUrl)
1042
1052
  });
1043
1053
  }
1054
+ /** Cached volume data */
1055
+ cachedVolume24h = {};
1056
+ /** Cached price change data */
1057
+ cachedPriceChange24h = {};
1044
1058
  /**
1045
1059
  * Fetch current market data for the agent
1046
1060
  */
@@ -1048,31 +1062,126 @@ var MarketDataService = class {
1048
1062
  const prices = await this.fetchPrices(tokenAddresses);
1049
1063
  const balances = await this.fetchBalances(walletAddress, tokenAddresses);
1050
1064
  const portfolioValue = this.calculatePortfolioValue(balances, prices);
1065
+ let gasPrice;
1066
+ try {
1067
+ gasPrice = await this.client.getGasPrice();
1068
+ } catch {
1069
+ }
1051
1070
  return {
1052
1071
  timestamp: Date.now(),
1053
1072
  prices,
1054
1073
  balances,
1055
- portfolioValue
1074
+ portfolioValue,
1075
+ volume24h: Object.keys(this.cachedVolume24h).length > 0 ? { ...this.cachedVolume24h } : void 0,
1076
+ priceChange24h: Object.keys(this.cachedPriceChange24h).length > 0 ? { ...this.cachedPriceChange24h } : void 0,
1077
+ gasPrice,
1078
+ network: {
1079
+ chainId: this.client.chain?.id ?? 8453
1080
+ }
1056
1081
  };
1057
1082
  }
1058
1083
  /**
1059
- * Fetch token prices from price oracle
1084
+ * Check if cached prices are still fresh
1085
+ */
1086
+ get pricesAreFresh() {
1087
+ return Date.now() - this.lastPriceFetchAt < PRICE_STALENESS_MS;
1088
+ }
1089
+ /**
1090
+ * Fetch token prices from CoinGecko free API
1091
+ * Returns cached prices if still fresh (<60s old)
1060
1092
  */
1061
1093
  async fetchPrices(tokenAddresses) {
1094
+ if (this.pricesAreFresh && Object.keys(this.cachedPrices).length > 0) {
1095
+ const prices2 = { ...this.cachedPrices };
1096
+ for (const addr of tokenAddresses) {
1097
+ const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
1098
+ if (cgId && STABLECOIN_IDS.has(cgId) && !prices2[addr.toLowerCase()]) {
1099
+ prices2[addr.toLowerCase()] = 1;
1100
+ }
1101
+ }
1102
+ return prices2;
1103
+ }
1062
1104
  const prices = {};
1063
- const knownPrices = {
1064
- // Native ETH (sentinel address)
1065
- [NATIVE_ETH.toLowerCase()]: 3500,
1066
- // WETH
1067
- "0x4200000000000000000000000000000000000006": 3500,
1068
- // USDC
1069
- "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 1,
1070
- // USDbC (bridged USDC)
1071
- "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": 1
1072
- };
1073
- prices[NATIVE_ETH.toLowerCase()] = knownPrices[NATIVE_ETH.toLowerCase()];
1074
- for (const address of tokenAddresses) {
1075
- prices[address.toLowerCase()] = knownPrices[address.toLowerCase()] || 0;
1105
+ const idsToFetch = /* @__PURE__ */ new Set();
1106
+ for (const addr of tokenAddresses) {
1107
+ const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
1108
+ if (cgId && !STABLECOIN_IDS.has(cgId)) {
1109
+ idsToFetch.add(cgId);
1110
+ }
1111
+ }
1112
+ idsToFetch.add("ethereum");
1113
+ if (idsToFetch.size > 0) {
1114
+ try {
1115
+ const ids = Array.from(idsToFetch).join(",");
1116
+ const response = await fetch(
1117
+ `https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true`,
1118
+ { signal: AbortSignal.timeout(5e3) }
1119
+ );
1120
+ if (response.ok) {
1121
+ const data = await response.json();
1122
+ for (const [cgId, priceData] of Object.entries(data)) {
1123
+ for (const [addr, id] of Object.entries(TOKEN_TO_COINGECKO)) {
1124
+ if (id === cgId) {
1125
+ const key = addr.toLowerCase();
1126
+ prices[key] = priceData.usd;
1127
+ if (priceData.usd_24h_vol !== void 0) {
1128
+ this.cachedVolume24h[key] = priceData.usd_24h_vol;
1129
+ }
1130
+ if (priceData.usd_24h_change !== void 0) {
1131
+ this.cachedPriceChange24h[key] = priceData.usd_24h_change;
1132
+ }
1133
+ }
1134
+ }
1135
+ }
1136
+ this.lastPriceFetchAt = Date.now();
1137
+ } else {
1138
+ console.warn(`CoinGecko API returned ${response.status}, using cached prices`);
1139
+ }
1140
+ } catch (error) {
1141
+ console.warn("Failed to fetch prices from CoinGecko:", error instanceof Error ? error.message : error);
1142
+ }
1143
+ }
1144
+ for (const addr of tokenAddresses) {
1145
+ const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
1146
+ if (cgId && STABLECOIN_IDS.has(cgId)) {
1147
+ prices[addr.toLowerCase()] = 1;
1148
+ }
1149
+ }
1150
+ const missingAddrs = tokenAddresses.filter(
1151
+ (addr) => !prices[addr.toLowerCase()] && !STABLECOIN_IDS.has(TOKEN_TO_COINGECKO[addr.toLowerCase()] || "")
1152
+ );
1153
+ if (missingAddrs.length > 0) {
1154
+ try {
1155
+ const coins = missingAddrs.map((a) => `base:${a}`).join(",");
1156
+ const llamaResponse = await fetch(
1157
+ `https://coins.llama.fi/prices/current/${coins}`,
1158
+ { signal: AbortSignal.timeout(5e3) }
1159
+ );
1160
+ if (llamaResponse.ok) {
1161
+ const llamaData = await llamaResponse.json();
1162
+ for (const [key, data] of Object.entries(llamaData.coins)) {
1163
+ const addr = key.replace("base:", "").toLowerCase();
1164
+ if (data.price && data.confidence > 0.5) {
1165
+ prices[addr] = data.price;
1166
+ }
1167
+ }
1168
+ if (!this.lastPriceFetchAt) this.lastPriceFetchAt = Date.now();
1169
+ }
1170
+ } catch {
1171
+ }
1172
+ }
1173
+ if (Object.keys(prices).length > 0) {
1174
+ this.cachedPrices = prices;
1175
+ }
1176
+ if (Object.keys(prices).length === 0 && Object.keys(this.cachedPrices).length > 0) {
1177
+ console.warn("Using cached prices (last successful fetch was stale)");
1178
+ return { ...this.cachedPrices };
1179
+ }
1180
+ for (const addr of tokenAddresses) {
1181
+ if (!prices[addr.toLowerCase()]) {
1182
+ console.warn(`No price available for ${addr}, using 0`);
1183
+ prices[addr.toLowerCase()] = 0;
1184
+ }
1076
1185
  }
1077
1186
  return prices;
1078
1187
  }
@@ -1118,7 +1227,7 @@ var MarketDataService = class {
1118
1227
  let total = 0;
1119
1228
  for (const [address, balance] of Object.entries(balances)) {
1120
1229
  const price = prices[address.toLowerCase()] || 0;
1121
- const decimals = USDC_DECIMALS[address.toLowerCase()] || 18;
1230
+ const decimals = getTokenDecimals(address);
1122
1231
  const amount = Number(balance) / Math.pow(10, decimals);
1123
1232
  total += amount * price;
1124
1233
  }
@@ -1126,6 +1235,129 @@ var MarketDataService = class {
1126
1235
  }
1127
1236
  };
1128
1237
 
1238
+ // src/trading/risk.ts
1239
+ var RiskManager = class {
1240
+ config;
1241
+ dailyPnL = 0;
1242
+ dailyFees = 0;
1243
+ lastResetDate = "";
1244
+ /** Minimum trade value in USD — trades below this are rejected as dust */
1245
+ minTradeValueUSD;
1246
+ constructor(config) {
1247
+ this.config = config;
1248
+ this.minTradeValueUSD = config.minTradeValueUSD ?? 1;
1249
+ }
1250
+ /**
1251
+ * Filter signals through risk checks
1252
+ * Returns only signals that pass all guardrails
1253
+ */
1254
+ filterSignals(signals, marketData) {
1255
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1256
+ if (today !== this.lastResetDate) {
1257
+ this.dailyPnL = 0;
1258
+ this.dailyFees = 0;
1259
+ this.lastResetDate = today;
1260
+ }
1261
+ if (this.isDailyLossLimitHit(marketData.portfolioValue)) {
1262
+ console.warn("Daily loss limit reached - no new trades");
1263
+ return [];
1264
+ }
1265
+ return signals.filter((signal) => this.validateSignal(signal, marketData));
1266
+ }
1267
+ /**
1268
+ * Validate individual signal against risk limits
1269
+ */
1270
+ validateSignal(signal, marketData) {
1271
+ if (signal.action === "hold") {
1272
+ return true;
1273
+ }
1274
+ const signalValue = this.estimateSignalValue(signal, marketData);
1275
+ const maxPositionValue = marketData.portfolioValue * this.config.maxPositionSizeBps / 1e4;
1276
+ if (signalValue > maxPositionValue) {
1277
+ console.warn(
1278
+ `Signal exceeds position limit: ${signalValue.toFixed(2)} > ${maxPositionValue.toFixed(2)}`
1279
+ );
1280
+ return false;
1281
+ }
1282
+ if (signal.confidence < 0.5) {
1283
+ console.warn(`Signal confidence too low: ${signal.confidence}`);
1284
+ return false;
1285
+ }
1286
+ if (signal.action === "buy" && this.config.maxConcurrentPositions) {
1287
+ const activePositions = this.countActivePositions(marketData);
1288
+ if (activePositions >= this.config.maxConcurrentPositions) {
1289
+ console.warn(
1290
+ `Max concurrent positions reached: ${activePositions}/${this.config.maxConcurrentPositions} \u2014 blocking new buy`
1291
+ );
1292
+ return false;
1293
+ }
1294
+ }
1295
+ if (signalValue < this.minTradeValueUSD) {
1296
+ console.warn(`Trade value $${signalValue.toFixed(2)} below minimum $${this.minTradeValueUSD} \u2014 skipping`);
1297
+ return false;
1298
+ }
1299
+ return true;
1300
+ }
1301
+ /**
1302
+ * Count non-zero token positions (excluding native ETH and stablecoins used as base currency)
1303
+ */
1304
+ countActivePositions(marketData) {
1305
+ const NATIVE_ETH_KEY = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
1306
+ let count = 0;
1307
+ for (const [address, balance] of Object.entries(marketData.balances)) {
1308
+ if (address.toLowerCase() === NATIVE_ETH_KEY) continue;
1309
+ if (balance > 0n) count++;
1310
+ }
1311
+ return count;
1312
+ }
1313
+ /**
1314
+ * Check if daily loss limit has been hit
1315
+ */
1316
+ isDailyLossLimitHit(portfolioValue) {
1317
+ const maxLoss = portfolioValue * this.config.maxDailyLossBps / 1e4;
1318
+ return this.dailyPnL < -maxLoss;
1319
+ }
1320
+ /**
1321
+ * Estimate USD value of a trade signal
1322
+ */
1323
+ estimateSignalValue(signal, marketData) {
1324
+ const price = marketData.prices[signal.tokenIn.toLowerCase()] || 0;
1325
+ const tokenDecimals = getTokenDecimals(signal.tokenIn);
1326
+ const amount = Number(signal.amountIn) / Math.pow(10, tokenDecimals);
1327
+ return amount * price;
1328
+ }
1329
+ /**
1330
+ * Update daily PnL after a trade (market gains/losses only)
1331
+ */
1332
+ updatePnL(pnl) {
1333
+ this.dailyPnL += pnl;
1334
+ }
1335
+ /**
1336
+ * Update daily fees (trading fees, gas costs, etc.)
1337
+ * Fees are tracked separately and do NOT count toward the daily loss limit.
1338
+ * This prevents protocol fees from triggering circuit breakers.
1339
+ */
1340
+ updateFees(fees) {
1341
+ this.dailyFees += fees;
1342
+ }
1343
+ /**
1344
+ * Get current risk status
1345
+ * @param portfolioValue - Current portfolio value in USD (needed for accurate loss limit)
1346
+ */
1347
+ getStatus(portfolioValue) {
1348
+ const pv = portfolioValue || 0;
1349
+ const maxLossUSD = pv * this.config.maxDailyLossBps / 1e4;
1350
+ return {
1351
+ dailyPnL: this.dailyPnL,
1352
+ dailyFees: this.dailyFees,
1353
+ dailyNetPnL: this.dailyPnL - this.dailyFees,
1354
+ dailyLossLimit: maxLossUSD,
1355
+ // Only market PnL triggers the limit — fees are excluded
1356
+ isLimitHit: pv > 0 ? this.dailyPnL < -maxLossUSD : false
1357
+ };
1358
+ }
1359
+ };
1360
+
1129
1361
  // src/vault/manager.ts
1130
1362
  var import_viem2 = require("viem");
1131
1363
  var import_accounts = require("viem/accounts");
@@ -1137,9 +1369,8 @@ var ADDRESSES = {
1137
1369
  usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
1138
1370
  },
1139
1371
  mainnet: {
1140
- vaultFactory: "0x0000000000000000000000000000000000000000",
1141
- // TODO: Deploy
1142
- registry: "0x0000000000000000000000000000000000000000",
1372
+ vaultFactory: process.env.EXAGENT_VAULT_FACTORY_ADDRESS || "0x0000000000000000000000000000000000000000",
1373
+ registry: process.env.EXAGENT_REGISTRY_ADDRESS || "0x0000000000000000000000000000000000000000",
1143
1374
  usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
1144
1375
  // Base mainnet USDC
1145
1376
  }
@@ -1165,6 +1396,7 @@ var VAULT_FACTORY_ABI = [
1165
1396
  inputs: [
1166
1397
  { name: "agentId", type: "uint256" },
1167
1398
  { name: "asset", type: "address" },
1399
+ { name: "seedAmount", type: "uint256" },
1168
1400
  { name: "name", type: "string" },
1169
1401
  { name: "symbol", type: "string" },
1170
1402
  { name: "feeRecipient", type: "address" }
@@ -1178,13 +1410,6 @@ var VAULT_FACTORY_ABI = [
1178
1410
  inputs: [],
1179
1411
  outputs: [{ type: "uint256" }],
1180
1412
  stateMutability: "view"
1181
- },
1182
- {
1183
- type: "function",
1184
- name: "eXABurnFee",
1185
- inputs: [],
1186
- outputs: [{ type: "uint256" }],
1187
- stateMutability: "view"
1188
1413
  }
1189
1414
  ];
1190
1415
  var VAULT_ABI = [
@@ -1224,6 +1449,7 @@ var VaultManager = class {
1224
1449
  lastVaultCheck = 0;
1225
1450
  VAULT_CACHE_TTL = 6e4;
1226
1451
  // 1 minute
1452
+ enabled = true;
1227
1453
  constructor(config) {
1228
1454
  this.config = config;
1229
1455
  this.addresses = ADDRESSES[config.network];
@@ -1239,6 +1465,10 @@ var VaultManager = class {
1239
1465
  chain: this.chain,
1240
1466
  transport: (0, import_viem2.http)(rpcUrl)
1241
1467
  });
1468
+ if (this.addresses.vaultFactory === "0x0000000000000000000000000000000000000000") {
1469
+ console.warn("VaultFactory address is zero \u2014 vault operations will be disabled");
1470
+ this.enabled = false;
1471
+ }
1242
1472
  }
1243
1473
  /**
1244
1474
  * Get the agent's vault policy
@@ -1256,6 +1486,17 @@ var VaultManager = class {
1256
1486
  * Get comprehensive vault status
1257
1487
  */
1258
1488
  async getVaultStatus() {
1489
+ if (!this.enabled) {
1490
+ return {
1491
+ hasVault: false,
1492
+ vaultAddress: null,
1493
+ totalAssets: BigInt(0),
1494
+ canCreateVault: false,
1495
+ cannotCreateReason: "Vault operations disabled (contract address not set)",
1496
+ requirementsMet: false,
1497
+ requirements: { veXARequired: BigInt(0), isBypassed: false }
1498
+ };
1499
+ }
1259
1500
  const vaultAddress = await this.getVaultAddress();
1260
1501
  const hasVault = vaultAddress !== null;
1261
1502
  let totalAssets = BigInt(0);
@@ -1290,22 +1531,16 @@ var VaultManager = class {
1290
1531
  }
1291
1532
  /**
1292
1533
  * Get vault creation requirements
1534
+ * Note: No burnFee on mainnet — vault creation requires USDC seed instead
1293
1535
  */
1294
1536
  async getRequirements() {
1295
- const [veXARequired, burnFee] = await Promise.all([
1296
- this.publicClient.readContract({
1297
- address: this.addresses.vaultFactory,
1298
- abi: VAULT_FACTORY_ABI,
1299
- functionName: "minimumVeEXARequired"
1300
- }),
1301
- this.publicClient.readContract({
1302
- address: this.addresses.vaultFactory,
1303
- abi: VAULT_FACTORY_ABI,
1304
- functionName: "eXABurnFee"
1305
- })
1306
- ]);
1307
- const isBypassed = veXARequired === BigInt(0) && burnFee === BigInt(0);
1308
- return { veXARequired, burnFee, isBypassed };
1537
+ const veXARequired = await this.publicClient.readContract({
1538
+ address: this.addresses.vaultFactory,
1539
+ abi: VAULT_FACTORY_ABI,
1540
+ functionName: "minimumVeEXARequired"
1541
+ });
1542
+ const isBypassed = veXARequired === BigInt(0);
1543
+ return { veXARequired, isBypassed };
1309
1544
  }
1310
1545
  /**
1311
1546
  * Get the agent's vault address (cached)
@@ -1329,30 +1564,15 @@ var VaultManager = class {
1329
1564
  this.cachedVaultAddress = vaultAddress;
1330
1565
  return vaultAddress;
1331
1566
  }
1332
- /**
1333
- * Check if the agent should create a vault based on policy and qualification
1334
- */
1335
- async shouldCreateVault() {
1336
- if (this.policy === "disabled") {
1337
- return { should: false, reason: "Vault creation disabled by policy" };
1338
- }
1339
- if (this.policy === "manual") {
1340
- return { should: false, reason: "Vault creation set to manual - waiting for owner instruction" };
1341
- }
1342
- const status = await this.getVaultStatus();
1343
- if (status.hasVault) {
1344
- return { should: false, reason: "Vault already exists" };
1345
- }
1346
- if (!status.canCreateVault) {
1347
- return { should: false, reason: status.cannotCreateReason || "Requirements not met" };
1348
- }
1349
- return { should: true, reason: "Agent is qualified and auto-creation is enabled" };
1350
- }
1351
1567
  /**
1352
1568
  * Create a vault for the agent
1569
+ * @param seedAmount - USDC seed amount in raw units (default: 100e6 = 100 USDC)
1353
1570
  * @returns Vault address if successful
1354
1571
  */
1355
- async createVault() {
1572
+ async createVault(seedAmount) {
1573
+ if (!this.enabled) {
1574
+ return { success: false, error: "Vault operations disabled (contract address not set)" };
1575
+ }
1356
1576
  if (this.policy === "disabled") {
1357
1577
  return { success: false, error: "Vault creation disabled by policy" };
1358
1578
  }
@@ -1364,6 +1584,7 @@ var VaultManager = class {
1364
1584
  if (!status.canCreateVault) {
1365
1585
  return { success: false, error: status.cannotCreateReason || "Requirements not met" };
1366
1586
  }
1587
+ const seed = seedAmount || BigInt(1e8);
1367
1588
  const vaultName = this.config.vaultConfig.defaultName || `${this.config.agentName} Trading Vault`;
1368
1589
  const vaultSymbol = this.config.vaultConfig.defaultSymbol || `ex${this.config.agentName.replace(/[^a-zA-Z]/g, "").slice(0, 4).toUpperCase()}`;
1369
1590
  const feeRecipient = this.config.vaultConfig.feeRecipient || this.account.address;
@@ -1375,6 +1596,7 @@ var VaultManager = class {
1375
1596
  args: [
1376
1597
  this.config.agentId,
1377
1598
  this.addresses.usdc,
1599
+ seed,
1378
1600
  vaultName,
1379
1601
  vaultSymbol,
1380
1602
  feeRecipient
@@ -1438,37 +1660,12 @@ var VaultManager = class {
1438
1660
  };
1439
1661
  }
1440
1662
  }
1441
- /**
1442
- * Run the auto-creation check (call this periodically in the agent loop)
1443
- * Only creates vault if policy is 'auto_when_qualified'
1444
- */
1445
- async checkAndAutoCreateVault() {
1446
- const shouldCreate = await this.shouldCreateVault();
1447
- if (!shouldCreate.should) {
1448
- const status = await this.getVaultStatus();
1449
- if (status.hasVault) {
1450
- return { action: "already_exists", vaultAddress: status.vaultAddress, reason: "Vault already exists" };
1451
- }
1452
- if (this.policy !== "auto_when_qualified") {
1453
- return { action: "skipped", reason: shouldCreate.reason };
1454
- }
1455
- return { action: "not_qualified", reason: shouldCreate.reason };
1456
- }
1457
- const result = await this.createVault();
1458
- if (result.success) {
1459
- return {
1460
- action: "created",
1461
- vaultAddress: result.vaultAddress,
1462
- reason: "Vault created automatically"
1463
- };
1464
- }
1465
- return { action: "not_qualified", reason: result.error || "Creation failed" };
1466
- }
1467
1663
  };
1468
1664
 
1469
1665
  // src/relay.ts
1470
1666
  var import_ws = __toESM(require("ws"));
1471
1667
  var import_accounts2 = require("viem/accounts");
1668
+ var import_sdk = require("@exagent/sdk");
1472
1669
  var RelayClient = class {
1473
1670
  config;
1474
1671
  ws = null;
@@ -1571,7 +1768,8 @@ var RelayClient = class {
1571
1768
  agentId: this.config.agentId,
1572
1769
  wallet: account.address,
1573
1770
  timestamp,
1574
- signature
1771
+ signature,
1772
+ sdkVersion: import_sdk.SDK_VERSION
1575
1773
  });
1576
1774
  }
1577
1775
  /**
@@ -1733,6 +1931,7 @@ function openBrowser(url) {
1733
1931
 
1734
1932
  // src/runtime.ts
1735
1933
  var FUNDS_LOW_THRESHOLD = 5e-3;
1934
+ var FUNDS_CRITICAL_THRESHOLD = 1e-3;
1736
1935
  var AgentRuntime = class {
1737
1936
  config;
1738
1937
  client;
@@ -1746,14 +1945,13 @@ var AgentRuntime = class {
1746
1945
  isRunning = false;
1747
1946
  mode = "idle";
1748
1947
  configHash;
1749
- lastVaultCheck = 0;
1750
1948
  cycleCount = 0;
1751
1949
  lastCycleAt = 0;
1752
1950
  lastPortfolioValue = 0;
1753
1951
  lastEthBalance = "0";
1754
1952
  processAlive = true;
1755
- VAULT_CHECK_INTERVAL = 3e5;
1756
- // Check vault status every 5 minutes
1953
+ riskUniverse = 0;
1954
+ allowedTokens = /* @__PURE__ */ new Set();
1757
1955
  constructor(config) {
1758
1956
  this.config = config;
1759
1957
  }
@@ -1762,7 +1960,7 @@ var AgentRuntime = class {
1762
1960
  */
1763
1961
  async initialize() {
1764
1962
  console.log(`Initializing agent: ${this.config.name} (ID: ${this.config.agentId})`);
1765
- this.client = new import_sdk.ExagentClient({
1963
+ this.client = new import_sdk2.ExagentClient({
1766
1964
  privateKey: this.config.privateKey,
1767
1965
  network: this.config.network
1768
1966
  });
@@ -1773,6 +1971,7 @@ var AgentRuntime = class {
1773
1971
  }
1774
1972
  console.log(`Agent verified: ${agent.name}`);
1775
1973
  await this.ensureWalletLinked();
1974
+ await this.loadTradingRestrictions();
1776
1975
  console.log(`Initializing LLM: ${this.config.llm.provider}`);
1777
1976
  this.llm = await createLLMAdapter(this.config.llm);
1778
1977
  const llmMeta = this.llm.getMetadata();
@@ -1840,12 +2039,8 @@ var AgentRuntime = class {
1840
2039
  console.log(`Vault TVL: ${Number(status.totalAssets) / 1e6} USDC`);
1841
2040
  } else {
1842
2041
  console.log("No vault exists for this agent");
1843
- if (vaultConfig.policy === "auto_when_qualified") {
1844
- if (status.canCreateVault) {
1845
- console.log("Agent is qualified to create vault - will attempt on next check");
1846
- } else {
1847
- console.log(`Cannot create vault yet: ${status.cannotCreateReason}`);
1848
- }
2042
+ if (vaultConfig.policy === "manual") {
2043
+ console.log("Vault creation is manual \u2014 use the command center to create one");
1849
2044
  }
1850
2045
  }
1851
2046
  }
@@ -1864,7 +2059,7 @@ var AgentRuntime = class {
1864
2059
  if (agent?.owner.toLowerCase() !== address.toLowerCase()) {
1865
2060
  const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
1866
2061
  const nonce = await this.client.registry.getNonce(address);
1867
- const linkMessage = import_sdk.ExagentRegistry.generateLinkMessage(
2062
+ const linkMessage = import_sdk2.ExagentRegistry.generateLinkMessage(
1868
2063
  address,
1869
2064
  agentId,
1870
2065
  nonce
@@ -1904,6 +2099,49 @@ var AgentRuntime = class {
1904
2099
  console.log("Wallet already linked");
1905
2100
  }
1906
2101
  }
2102
+ /**
2103
+ * Load risk universe and allowed tokens from on-chain registry.
2104
+ * This prevents the agent from wasting gas on trades that will revert.
2105
+ */
2106
+ async loadTradingRestrictions() {
2107
+ const agentId = BigInt(this.config.agentId);
2108
+ const RISK_UNIVERSE_NAMES = ["Core", "Established", "Derivatives", "Emerging", "Frontier"];
2109
+ try {
2110
+ this.riskUniverse = await this.client.registry.getRiskUniverse(agentId);
2111
+ console.log(`Risk universe: ${RISK_UNIVERSE_NAMES[this.riskUniverse] || this.riskUniverse}`);
2112
+ const configTokens = this.config.allowedTokens || this.getDefaultTokens();
2113
+ const verified = [];
2114
+ for (const token of configTokens) {
2115
+ try {
2116
+ const allowed = await this.client.registry.isTradeAllowed(
2117
+ agentId,
2118
+ token,
2119
+ "0x0000000000000000000000000000000000000000"
2120
+ // zero = check token only
2121
+ );
2122
+ if (allowed) {
2123
+ this.allowedTokens.add(token.toLowerCase());
2124
+ verified.push(token);
2125
+ } else {
2126
+ console.warn(`Token ${token} not allowed for this agent's risk universe \u2014 excluded`);
2127
+ }
2128
+ } catch {
2129
+ this.allowedTokens.add(token.toLowerCase());
2130
+ verified.push(token);
2131
+ }
2132
+ }
2133
+ this.config.allowedTokens = verified;
2134
+ console.log(`Allowed tokens loaded: ${verified.length} tokens verified`);
2135
+ if (this.riskUniverse === 4) {
2136
+ console.warn("Frontier risk universe: vault creation is disabled");
2137
+ }
2138
+ } catch (error) {
2139
+ console.warn(
2140
+ "Could not load trading restrictions from registry (using defaults):",
2141
+ error instanceof Error ? error.message : error
2142
+ );
2143
+ }
2144
+ }
1907
2145
  /**
1908
2146
  * Sync the LLM config hash to chain for epoch tracking.
1909
2147
  * If the wallet has insufficient gas, enters a recovery loop
@@ -1912,7 +2150,7 @@ var AgentRuntime = class {
1912
2150
  async syncConfigHash() {
1913
2151
  const agentId = BigInt(this.config.agentId);
1914
2152
  const llmMeta = this.llm.getMetadata();
1915
- this.configHash = import_sdk.ExagentRegistry.calculateConfigHash(llmMeta.provider, llmMeta.model);
2153
+ this.configHash = import_sdk2.ExagentRegistry.calculateConfigHash(llmMeta.provider, llmMeta.model);
1916
2154
  console.log(`Config hash: ${this.configHash}`);
1917
2155
  const onChainHash = await this.client.registry.getConfigHash(agentId);
1918
2156
  if (onChainHash !== this.configHash) {
@@ -2076,21 +2314,38 @@ var AgentRuntime = class {
2076
2314
  break;
2077
2315
  case "update_risk_params": {
2078
2316
  const params = cmd.params || {};
2317
+ let updated = false;
2079
2318
  if (params.maxPositionSizeBps !== void 0) {
2080
- this.config.trading.maxPositionSizeBps = Number(params.maxPositionSizeBps);
2319
+ const value = Number(params.maxPositionSizeBps);
2320
+ if (isNaN(value) || value < 100 || value > 1e4) {
2321
+ this.relay?.sendCommandResult(cmd.id, false, "maxPositionSizeBps must be 100-10000");
2322
+ break;
2323
+ }
2324
+ this.config.trading.maxPositionSizeBps = value;
2325
+ updated = true;
2081
2326
  }
2082
2327
  if (params.maxDailyLossBps !== void 0) {
2083
- this.config.trading.maxDailyLossBps = Number(params.maxDailyLossBps);
2328
+ const value = Number(params.maxDailyLossBps);
2329
+ if (isNaN(value) || value < 50 || value > 5e3) {
2330
+ this.relay?.sendCommandResult(cmd.id, false, "maxDailyLossBps must be 50-5000");
2331
+ break;
2332
+ }
2333
+ this.config.trading.maxDailyLossBps = value;
2334
+ updated = true;
2335
+ }
2336
+ if (updated) {
2337
+ this.riskManager = new RiskManager(this.config.trading);
2338
+ console.log("Risk params updated via command center");
2339
+ this.relay?.sendCommandResult(cmd.id, true, "Risk parameters updated");
2340
+ this.relay?.sendMessage(
2341
+ "config_updated",
2342
+ "info",
2343
+ "Risk Parameters Updated",
2344
+ `Max position: ${this.config.trading.maxPositionSizeBps / 100}%, Max daily loss: ${this.config.trading.maxDailyLossBps / 100}%`
2345
+ );
2346
+ } else {
2347
+ this.relay?.sendCommandResult(cmd.id, false, "No valid parameters provided");
2084
2348
  }
2085
- this.riskManager = new RiskManager(this.config.trading);
2086
- console.log("Risk params updated via command center");
2087
- this.relay?.sendCommandResult(cmd.id, true, "Risk params updated");
2088
- this.relay?.sendMessage(
2089
- "config_updated",
2090
- "info",
2091
- "Risk Parameters Updated",
2092
- `Max position: ${this.config.trading.maxPositionSizeBps / 100}%, Max daily loss: ${this.config.trading.maxDailyLossBps / 100}%`
2093
- );
2094
2349
  break;
2095
2350
  }
2096
2351
  case "update_trading_interval": {
@@ -2167,7 +2422,7 @@ var AgentRuntime = class {
2167
2422
  provider: this.config.llm.provider,
2168
2423
  model: this.config.llm.model || "default"
2169
2424
  },
2170
- risk: this.riskManager?.getStatus() || {
2425
+ risk: this.riskManager?.getStatus(this.lastPortfolioValue) || {
2171
2426
  dailyPnL: 0,
2172
2427
  dailyLossLimit: 0,
2173
2428
  isLimitHit: false
@@ -2188,14 +2443,18 @@ var AgentRuntime = class {
2188
2443
  --- Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
2189
2444
  this.cycleCount++;
2190
2445
  this.lastCycleAt = Date.now();
2191
- await this.checkVaultAutoCreation();
2192
2446
  const tokens = this.config.allowedTokens || this.getDefaultTokens();
2193
2447
  const marketData = await this.marketData.fetchMarketData(this.client.address, tokens);
2194
2448
  console.log(`Portfolio value: $${marketData.portfolioValue.toFixed(2)}`);
2195
2449
  this.lastPortfolioValue = marketData.portfolioValue;
2196
2450
  const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
2197
2451
  this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
2198
- this.checkFundsLow(marketData);
2452
+ const fundsOk = this.checkFundsLow(marketData);
2453
+ if (!fundsOk) {
2454
+ console.warn("Skipping trading cycle \u2014 ETH balance critically low");
2455
+ this.sendRelayStatus();
2456
+ return;
2457
+ }
2199
2458
  let signals;
2200
2459
  try {
2201
2460
  signals = await this.strategy(marketData, this.llm, this.config);
@@ -2213,19 +2472,31 @@ var AgentRuntime = class {
2213
2472
  console.log(`Strategy generated ${signals.length} signals`);
2214
2473
  const filteredSignals = this.riskManager.filterSignals(signals, marketData);
2215
2474
  console.log(`${filteredSignals.length} signals passed risk checks`);
2216
- if (this.riskManager.getStatus().isLimitHit) {
2475
+ if (this.riskManager.getStatus(marketData.portfolioValue).isLimitHit) {
2217
2476
  this.relay?.sendMessage(
2218
2477
  "risk_limit_hit",
2219
2478
  "warning",
2220
2479
  "Risk Limit Hit",
2221
- `Daily loss limit reached: ${this.riskManager.getStatus().dailyPnL.toFixed(2)}`
2480
+ `Daily loss limit reached: $${this.riskManager.getStatus(marketData.portfolioValue).dailyPnL.toFixed(2)}`
2222
2481
  );
2223
2482
  }
2224
2483
  if (filteredSignals.length > 0) {
2484
+ const vaultStatus = await this.vaultManager?.getVaultStatus();
2485
+ if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
2486
+ console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
2487
+ }
2488
+ const preTradePortfolioValue = marketData.portfolioValue;
2225
2489
  const results = await this.executor.executeAll(filteredSignals);
2490
+ let totalFeesUSD = 0;
2226
2491
  for (const result of results) {
2227
2492
  if (result.success) {
2228
2493
  console.log(`Trade executed: ${result.signal.action} - ${result.txHash}`);
2494
+ const feeCostBps = 20;
2495
+ const signalPrice = marketData.prices[result.signal.tokenIn.toLowerCase()] || 0;
2496
+ const amountUSD = Number(result.signal.amountIn) / Math.pow(10, getTokenDecimals(result.signal.tokenIn)) * signalPrice;
2497
+ const feeCostUSD = amountUSD * feeCostBps / 1e4;
2498
+ totalFeesUSD += feeCostUSD;
2499
+ this.riskManager.updateFees(feeCostUSD);
2229
2500
  this.relay?.sendMessage(
2230
2501
  "trade_executed",
2231
2502
  "success",
@@ -2249,18 +2520,43 @@ var AgentRuntime = class {
2249
2520
  );
2250
2521
  }
2251
2522
  }
2523
+ const postTokens = this.config.allowedTokens || this.getDefaultTokens();
2524
+ const postTradeData = await this.marketData.fetchMarketData(this.client.address, postTokens);
2525
+ const marketPnL = postTradeData.portfolioValue - preTradePortfolioValue + totalFeesUSD;
2526
+ this.riskManager.updatePnL(marketPnL);
2527
+ if (marketPnL !== 0) {
2528
+ console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
2529
+ }
2530
+ this.lastPortfolioValue = postTradeData.portfolioValue;
2531
+ const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
2532
+ this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
2252
2533
  }
2253
2534
  this.sendRelayStatus();
2254
2535
  }
2255
2536
  /**
2256
- * Check if ETH balance is below threshold and notify
2537
+ * Check if ETH balance is below threshold and notify.
2538
+ * Returns true if trading should continue, false if ETH is critically low.
2257
2539
  */
2258
2540
  checkFundsLow(marketData) {
2259
- if (!this.relay) return;
2260
2541
  const ethBalance = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
2261
2542
  const ethAmount = Number(ethBalance) / 1e18;
2543
+ if (ethAmount < FUNDS_CRITICAL_THRESHOLD) {
2544
+ console.error(`ETH balance critically low: ${ethAmount.toFixed(6)} ETH \u2014 halting trading`);
2545
+ this.relay?.sendMessage(
2546
+ "funds_low",
2547
+ "error",
2548
+ "Funds Critical",
2549
+ `ETH balance is ${ethAmount.toFixed(6)} ETH (below ${FUNDS_CRITICAL_THRESHOLD} ETH minimum). Trading halted \u2014 fund your wallet.`,
2550
+ {
2551
+ ethBalance: ethAmount.toFixed(6),
2552
+ wallet: this.client.address,
2553
+ threshold: FUNDS_CRITICAL_THRESHOLD
2554
+ }
2555
+ );
2556
+ return false;
2557
+ }
2262
2558
  if (ethAmount < FUNDS_LOW_THRESHOLD) {
2263
- this.relay.sendMessage(
2559
+ this.relay?.sendMessage(
2264
2560
  "funds_low",
2265
2561
  "warning",
2266
2562
  "Low Funds",
@@ -2272,36 +2568,7 @@ var AgentRuntime = class {
2272
2568
  }
2273
2569
  );
2274
2570
  }
2275
- }
2276
- /**
2277
- * Check for vault auto-creation based on policy
2278
- */
2279
- async checkVaultAutoCreation() {
2280
- const now = Date.now();
2281
- if (now - this.lastVaultCheck < this.VAULT_CHECK_INTERVAL) {
2282
- return;
2283
- }
2284
- this.lastVaultCheck = now;
2285
- const result = await this.vaultManager.checkAndAutoCreateVault();
2286
- switch (result.action) {
2287
- case "created":
2288
- console.log(`Vault created automatically: ${result.vaultAddress}`);
2289
- this.relay?.sendMessage(
2290
- "vault_created",
2291
- "success",
2292
- "Vault Auto-Created",
2293
- `Vault deployed at ${result.vaultAddress}`,
2294
- { vaultAddress: result.vaultAddress }
2295
- );
2296
- break;
2297
- case "already_exists":
2298
- break;
2299
- case "skipped":
2300
- break;
2301
- case "not_qualified":
2302
- console.log(`Vault auto-creation pending: ${result.reason}`);
2303
- break;
2304
- }
2571
+ return true;
2305
2572
  }
2306
2573
  /**
2307
2574
  * Stop the agent process completely
@@ -2325,15 +2592,42 @@ var AgentRuntime = class {
2325
2592
  return "https://sepolia.base.org";
2326
2593
  }
2327
2594
  /**
2328
- * Default tokens to track
2595
+ * Default tokens to track.
2596
+ * These are validated against the on-chain registry's isTradeAllowed() during init —
2597
+ * agents in restricted risk universes will have ineligible tokens filtered out.
2329
2598
  */
2330
2599
  getDefaultTokens() {
2331
2600
  if (this.config.network === "mainnet") {
2332
2601
  return [
2602
+ // Core
2333
2603
  "0x4200000000000000000000000000000000000006",
2334
2604
  // WETH
2335
- "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
2605
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2336
2606
  // USDC
2607
+ "0x2Ae3F1Ec7F1F5012CFEab0185bFC7aa3cf0DEC22",
2608
+ // cbETH
2609
+ "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
2610
+ // USDbC
2611
+ "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
2612
+ // DAI
2613
+ // Established
2614
+ "0x940181a94A35A4569E4529A3CDfB74e38FD98631",
2615
+ // AERO
2616
+ "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
2617
+ // cbBTC
2618
+ "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
2619
+ // wstETH
2620
+ "0x2416092f143378750bb29b79eD961ab195CcEea5",
2621
+ // ezETH
2622
+ // Emerging (filtered by risk universe at init)
2623
+ "0x532f27101965dd16442E59d40670FaF5eBB142E4",
2624
+ // BRETT
2625
+ "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed",
2626
+ // DEGEN
2627
+ "0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b",
2628
+ // VIRTUAL
2629
+ "0xAC1Bd2486Aaf3B5C0fc3Fd868558b082a531B2B4"
2630
+ // TOSHI
2337
2631
  ];
2338
2632
  }
2339
2633
  return [
@@ -2359,7 +2653,7 @@ var AgentRuntime = class {
2359
2653
  model: this.config.llm.model || "default"
2360
2654
  },
2361
2655
  configHash: this.configHash || "not initialized",
2362
- risk: this.riskManager?.getStatus() || { dailyPnL: 0, dailyLossLimit: 0, isLimitHit: false },
2656
+ risk: this.riskManager?.getStatus(this.lastPortfolioValue) || { dailyPnL: 0, dailyLossLimit: 0, isLimitHit: false },
2363
2657
  vault: {
2364
2658
  policy: vaultConfig.policy,
2365
2659
  hasVault: false,
@@ -2421,16 +2715,18 @@ var TradingConfigSchema = import_zod.z.object({
2421
2715
  maxDailyLossBps: import_zod.z.number().min(0).max(1e4).default(500),
2422
2716
  // 0-100%
2423
2717
  maxConcurrentPositions: import_zod.z.number().min(1).max(100).default(5),
2424
- tradingIntervalMs: import_zod.z.number().min(1e3).default(6e4)
2718
+ tradingIntervalMs: import_zod.z.number().min(1e3).default(6e4),
2425
2719
  // minimum 1 second
2720
+ maxSlippageBps: import_zod.z.number().min(10).max(1e3).default(100),
2721
+ // 0.1-10%, default 1%
2722
+ minTradeValueUSD: import_zod.z.number().min(0).default(1)
2723
+ // minimum trade value in USD
2426
2724
  });
2427
2725
  var VaultPolicySchema = import_zod.z.enum([
2428
2726
  "disabled",
2429
2727
  // Never create a vault - trade with agent's own capital only
2430
- "manual",
2728
+ "manual"
2431
2729
  // Only create vault when explicitly directed by owner
2432
- "auto_when_qualified"
2433
- // Automatically create vault when requirements are met
2434
2730
  ]);
2435
2731
  var VaultConfigSchema = import_zod.z.object({
2436
2732
  // Policy for vault creation (asked during deployment)
@@ -2558,7 +2854,9 @@ function createSampleConfig(agentId, name) {
2558
2854
  maxPositionSizeBps: 1e3,
2559
2855
  maxDailyLossBps: 500,
2560
2856
  maxConcurrentPositions: 5,
2561
- tradingIntervalMs: 6e4
2857
+ tradingIntervalMs: 6e4,
2858
+ maxSlippageBps: 100,
2859
+ minTradeValueUSD: 1
2562
2860
  },
2563
2861
  vault: {
2564
2862
  // Default to manual - user must explicitly enable auto-creation