@exagent/agent 0.1.11 → 0.1.13
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-5NE2K3WU.mjs +2852 -0
- package/dist/chunk-BSNYL2DK.mjs +3005 -0
- package/dist/chunk-BZLZQCKQ.mjs +2853 -0
- package/dist/chunk-DZMU25QF.mjs +3021 -0
- package/dist/chunk-TBITVAFT.mjs +2780 -0
- package/dist/chunk-ZY3ZSYNN.mjs +3023 -0
- package/dist/cli.js +545 -267
- package/dist/cli.mjs +3 -3
- package/dist/index.d.mts +99 -47
- package/dist/index.d.ts +99 -47
- package/dist/index.js +547 -267
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/templates/.env.template +3 -4
- package/templates/docker-compose.yml +1 -1
- package/templates/strategy.template.ts +1 -1
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
|
|
72
|
+
var import_sdk2 = require("@exagent/sdk");
|
|
73
73
|
var import_viem3 = require("viem");
|
|
74
74
|
var import_chains2 = require("viem/chains");
|
|
75
75
|
|
|
@@ -820,7 +820,7 @@ export const generateSignals: StrategyFunction = async (marketData, llm, config)
|
|
|
820
820
|
riskWarnings: [
|
|
821
821
|
"Custom strategies have no guardrails - you are fully responsible",
|
|
822
822
|
"LLMs can hallucinate or make errors - always validate outputs",
|
|
823
|
-
"
|
|
823
|
+
"Start with small amounts before scaling up",
|
|
824
824
|
"Consider edge cases: what happens if the LLM returns invalid JSON?",
|
|
825
825
|
"Your prompts and strategy logic are your competitive advantage - protect them",
|
|
826
826
|
"Agents may not behave exactly as expected based on your prompts"
|
|
@@ -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,99 @@ 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
|
|
965
|
+
var TOKEN_DECIMALS = {
|
|
966
|
+
// Base Mainnet — Core tokens
|
|
1030
967
|
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 6,
|
|
1031
|
-
// USDC
|
|
1032
|
-
"0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": 6
|
|
1033
|
-
// USDbC
|
|
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)
|
|
1034
996
|
};
|
|
997
|
+
function getTokenDecimals(address) {
|
|
998
|
+
const decimals = TOKEN_DECIMALS[address.toLowerCase()];
|
|
999
|
+
if (decimals === void 0) {
|
|
1000
|
+
console.warn(`Unknown token decimals for ${address}, defaulting to 18. THIS MAY BE WRONG.`);
|
|
1001
|
+
return 18;
|
|
1002
|
+
}
|
|
1003
|
+
return decimals;
|
|
1004
|
+
}
|
|
1005
|
+
var TOKEN_TO_COINGECKO = {
|
|
1006
|
+
// Core
|
|
1007
|
+
"0x4200000000000000000000000000000000000006": "ethereum",
|
|
1008
|
+
// WETH
|
|
1009
|
+
[NATIVE_ETH.toLowerCase()]: "ethereum",
|
|
1010
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": "usd-coin",
|
|
1011
|
+
// USDC
|
|
1012
|
+
"0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": "usd-coin",
|
|
1013
|
+
// USDbC
|
|
1014
|
+
"0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22": "coinbase-wrapped-staked-eth",
|
|
1015
|
+
// cbETH
|
|
1016
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": "dai",
|
|
1017
|
+
// DAI
|
|
1018
|
+
// Established
|
|
1019
|
+
"0x940181a94a35a4569e4529a3cdfb74e38fd98631": "aerodrome-finance",
|
|
1020
|
+
// AERO
|
|
1021
|
+
"0x532f27101965dd16442e59d40670faf5ebb142e4": "brett",
|
|
1022
|
+
// BRETT
|
|
1023
|
+
"0x4ed4e862860bed51a9570b96d89af5e1b0efefed": "degen-base",
|
|
1024
|
+
// DEGEN
|
|
1025
|
+
"0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": "virtual-protocol",
|
|
1026
|
+
// VIRTUAL
|
|
1027
|
+
"0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4": "toshi",
|
|
1028
|
+
// TOSHI
|
|
1029
|
+
"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": "coinbase-wrapped-btc",
|
|
1030
|
+
// cbBTC
|
|
1031
|
+
"0x2416092f143378750bb29b79ed961ab195cceea5": "renzo-restaked-eth",
|
|
1032
|
+
// ezETH
|
|
1033
|
+
"0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": "wrapped-steth"
|
|
1034
|
+
// wstETH
|
|
1035
|
+
};
|
|
1036
|
+
var STABLECOIN_IDS = /* @__PURE__ */ new Set(["usd-coin", "dai"]);
|
|
1037
|
+
var PRICE_STALENESS_MS = 6e4;
|
|
1035
1038
|
var MarketDataService = class {
|
|
1036
1039
|
rpcUrl;
|
|
1037
1040
|
client;
|
|
1041
|
+
/** Cached prices from last fetch */
|
|
1042
|
+
cachedPrices = {};
|
|
1043
|
+
/** Timestamp of last successful price fetch */
|
|
1044
|
+
lastPriceFetchAt = 0;
|
|
1038
1045
|
constructor(rpcUrl) {
|
|
1039
1046
|
this.rpcUrl = rpcUrl;
|
|
1040
1047
|
this.client = (0, import_viem.createPublicClient)({
|
|
1041
1048
|
transport: (0, import_viem.http)(rpcUrl)
|
|
1042
1049
|
});
|
|
1043
1050
|
}
|
|
1051
|
+
/** Cached volume data */
|
|
1052
|
+
cachedVolume24h = {};
|
|
1053
|
+
/** Cached price change data */
|
|
1054
|
+
cachedPriceChange24h = {};
|
|
1044
1055
|
/**
|
|
1045
1056
|
* Fetch current market data for the agent
|
|
1046
1057
|
*/
|
|
@@ -1048,31 +1059,126 @@ var MarketDataService = class {
|
|
|
1048
1059
|
const prices = await this.fetchPrices(tokenAddresses);
|
|
1049
1060
|
const balances = await this.fetchBalances(walletAddress, tokenAddresses);
|
|
1050
1061
|
const portfolioValue = this.calculatePortfolioValue(balances, prices);
|
|
1062
|
+
let gasPrice;
|
|
1063
|
+
try {
|
|
1064
|
+
gasPrice = await this.client.getGasPrice();
|
|
1065
|
+
} catch {
|
|
1066
|
+
}
|
|
1051
1067
|
return {
|
|
1052
1068
|
timestamp: Date.now(),
|
|
1053
1069
|
prices,
|
|
1054
1070
|
balances,
|
|
1055
|
-
portfolioValue
|
|
1071
|
+
portfolioValue,
|
|
1072
|
+
volume24h: Object.keys(this.cachedVolume24h).length > 0 ? { ...this.cachedVolume24h } : void 0,
|
|
1073
|
+
priceChange24h: Object.keys(this.cachedPriceChange24h).length > 0 ? { ...this.cachedPriceChange24h } : void 0,
|
|
1074
|
+
gasPrice,
|
|
1075
|
+
network: {
|
|
1076
|
+
chainId: this.client.chain?.id ?? 8453
|
|
1077
|
+
}
|
|
1056
1078
|
};
|
|
1057
1079
|
}
|
|
1058
1080
|
/**
|
|
1059
|
-
*
|
|
1081
|
+
* Check if cached prices are still fresh
|
|
1082
|
+
*/
|
|
1083
|
+
get pricesAreFresh() {
|
|
1084
|
+
return Date.now() - this.lastPriceFetchAt < PRICE_STALENESS_MS;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Fetch token prices from CoinGecko free API
|
|
1088
|
+
* Returns cached prices if still fresh (<60s old)
|
|
1060
1089
|
*/
|
|
1061
1090
|
async fetchPrices(tokenAddresses) {
|
|
1091
|
+
if (this.pricesAreFresh && Object.keys(this.cachedPrices).length > 0) {
|
|
1092
|
+
const prices2 = { ...this.cachedPrices };
|
|
1093
|
+
for (const addr of tokenAddresses) {
|
|
1094
|
+
const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
|
|
1095
|
+
if (cgId && STABLECOIN_IDS.has(cgId) && !prices2[addr.toLowerCase()]) {
|
|
1096
|
+
prices2[addr.toLowerCase()] = 1;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return prices2;
|
|
1100
|
+
}
|
|
1062
1101
|
const prices = {};
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
[
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1102
|
+
const idsToFetch = /* @__PURE__ */ new Set();
|
|
1103
|
+
for (const addr of tokenAddresses) {
|
|
1104
|
+
const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
|
|
1105
|
+
if (cgId && !STABLECOIN_IDS.has(cgId)) {
|
|
1106
|
+
idsToFetch.add(cgId);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
idsToFetch.add("ethereum");
|
|
1110
|
+
if (idsToFetch.size > 0) {
|
|
1111
|
+
try {
|
|
1112
|
+
const ids = Array.from(idsToFetch).join(",");
|
|
1113
|
+
const response = await fetch(
|
|
1114
|
+
`https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true`,
|
|
1115
|
+
{ signal: AbortSignal.timeout(5e3) }
|
|
1116
|
+
);
|
|
1117
|
+
if (response.ok) {
|
|
1118
|
+
const data = await response.json();
|
|
1119
|
+
for (const [cgId, priceData] of Object.entries(data)) {
|
|
1120
|
+
for (const [addr, id] of Object.entries(TOKEN_TO_COINGECKO)) {
|
|
1121
|
+
if (id === cgId) {
|
|
1122
|
+
const key = addr.toLowerCase();
|
|
1123
|
+
prices[key] = priceData.usd;
|
|
1124
|
+
if (priceData.usd_24h_vol !== void 0) {
|
|
1125
|
+
this.cachedVolume24h[key] = priceData.usd_24h_vol;
|
|
1126
|
+
}
|
|
1127
|
+
if (priceData.usd_24h_change !== void 0) {
|
|
1128
|
+
this.cachedPriceChange24h[key] = priceData.usd_24h_change;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
this.lastPriceFetchAt = Date.now();
|
|
1134
|
+
} else {
|
|
1135
|
+
console.warn(`CoinGecko API returned ${response.status}, using cached prices`);
|
|
1136
|
+
}
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
console.warn("Failed to fetch prices from CoinGecko:", error instanceof Error ? error.message : error);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
for (const addr of tokenAddresses) {
|
|
1142
|
+
const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
|
|
1143
|
+
if (cgId && STABLECOIN_IDS.has(cgId)) {
|
|
1144
|
+
prices[addr.toLowerCase()] = 1;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const missingAddrs = tokenAddresses.filter(
|
|
1148
|
+
(addr) => !prices[addr.toLowerCase()] && !STABLECOIN_IDS.has(TOKEN_TO_COINGECKO[addr.toLowerCase()] || "")
|
|
1149
|
+
);
|
|
1150
|
+
if (missingAddrs.length > 0) {
|
|
1151
|
+
try {
|
|
1152
|
+
const coins = missingAddrs.map((a) => `base:${a}`).join(",");
|
|
1153
|
+
const llamaResponse = await fetch(
|
|
1154
|
+
`https://coins.llama.fi/prices/current/${coins}`,
|
|
1155
|
+
{ signal: AbortSignal.timeout(5e3) }
|
|
1156
|
+
);
|
|
1157
|
+
if (llamaResponse.ok) {
|
|
1158
|
+
const llamaData = await llamaResponse.json();
|
|
1159
|
+
for (const [key, data] of Object.entries(llamaData.coins)) {
|
|
1160
|
+
const addr = key.replace("base:", "").toLowerCase();
|
|
1161
|
+
if (data.price && data.confidence > 0.5) {
|
|
1162
|
+
prices[addr] = data.price;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (!this.lastPriceFetchAt) this.lastPriceFetchAt = Date.now();
|
|
1166
|
+
}
|
|
1167
|
+
} catch {
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (Object.keys(prices).length > 0) {
|
|
1171
|
+
this.cachedPrices = prices;
|
|
1172
|
+
}
|
|
1173
|
+
if (Object.keys(prices).length === 0 && Object.keys(this.cachedPrices).length > 0) {
|
|
1174
|
+
console.warn("Using cached prices (last successful fetch was stale)");
|
|
1175
|
+
return { ...this.cachedPrices };
|
|
1176
|
+
}
|
|
1177
|
+
for (const addr of tokenAddresses) {
|
|
1178
|
+
if (!prices[addr.toLowerCase()]) {
|
|
1179
|
+
console.warn(`No price available for ${addr}, using 0`);
|
|
1180
|
+
prices[addr.toLowerCase()] = 0;
|
|
1181
|
+
}
|
|
1076
1182
|
}
|
|
1077
1183
|
return prices;
|
|
1078
1184
|
}
|
|
@@ -1118,7 +1224,7 @@ var MarketDataService = class {
|
|
|
1118
1224
|
let total = 0;
|
|
1119
1225
|
for (const [address, balance] of Object.entries(balances)) {
|
|
1120
1226
|
const price = prices[address.toLowerCase()] || 0;
|
|
1121
|
-
const decimals =
|
|
1227
|
+
const decimals = getTokenDecimals(address);
|
|
1122
1228
|
const amount = Number(balance) / Math.pow(10, decimals);
|
|
1123
1229
|
total += amount * price;
|
|
1124
1230
|
}
|
|
@@ -1126,22 +1232,138 @@ var MarketDataService = class {
|
|
|
1126
1232
|
}
|
|
1127
1233
|
};
|
|
1128
1234
|
|
|
1235
|
+
// src/trading/risk.ts
|
|
1236
|
+
var RiskManager = class {
|
|
1237
|
+
config;
|
|
1238
|
+
dailyPnL = 0;
|
|
1239
|
+
dailyFees = 0;
|
|
1240
|
+
lastResetDate = "";
|
|
1241
|
+
/** Minimum trade value in USD — trades below this are rejected as dust */
|
|
1242
|
+
minTradeValueUSD;
|
|
1243
|
+
constructor(config) {
|
|
1244
|
+
this.config = config;
|
|
1245
|
+
this.minTradeValueUSD = config.minTradeValueUSD ?? 1;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Filter signals through risk checks
|
|
1249
|
+
* Returns only signals that pass all guardrails
|
|
1250
|
+
*/
|
|
1251
|
+
filterSignals(signals, marketData) {
|
|
1252
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1253
|
+
if (today !== this.lastResetDate) {
|
|
1254
|
+
this.dailyPnL = 0;
|
|
1255
|
+
this.dailyFees = 0;
|
|
1256
|
+
this.lastResetDate = today;
|
|
1257
|
+
}
|
|
1258
|
+
if (this.isDailyLossLimitHit(marketData.portfolioValue)) {
|
|
1259
|
+
console.warn("Daily loss limit reached - no new trades");
|
|
1260
|
+
return [];
|
|
1261
|
+
}
|
|
1262
|
+
return signals.filter((signal) => this.validateSignal(signal, marketData));
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Validate individual signal against risk limits
|
|
1266
|
+
*/
|
|
1267
|
+
validateSignal(signal, marketData) {
|
|
1268
|
+
if (signal.action === "hold") {
|
|
1269
|
+
return true;
|
|
1270
|
+
}
|
|
1271
|
+
const signalValue = this.estimateSignalValue(signal, marketData);
|
|
1272
|
+
const maxPositionValue = marketData.portfolioValue * this.config.maxPositionSizeBps / 1e4;
|
|
1273
|
+
if (signalValue > maxPositionValue) {
|
|
1274
|
+
console.warn(
|
|
1275
|
+
`Signal exceeds position limit: ${signalValue.toFixed(2)} > ${maxPositionValue.toFixed(2)}`
|
|
1276
|
+
);
|
|
1277
|
+
return false;
|
|
1278
|
+
}
|
|
1279
|
+
if (signal.confidence < 0.5) {
|
|
1280
|
+
console.warn(`Signal confidence too low: ${signal.confidence}`);
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
if (signal.action === "buy" && this.config.maxConcurrentPositions) {
|
|
1284
|
+
const activePositions = this.countActivePositions(marketData);
|
|
1285
|
+
if (activePositions >= this.config.maxConcurrentPositions) {
|
|
1286
|
+
console.warn(
|
|
1287
|
+
`Max concurrent positions reached: ${activePositions}/${this.config.maxConcurrentPositions} \u2014 blocking new buy`
|
|
1288
|
+
);
|
|
1289
|
+
return false;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (signalValue < this.minTradeValueUSD) {
|
|
1293
|
+
console.warn(`Trade value $${signalValue.toFixed(2)} below minimum $${this.minTradeValueUSD} \u2014 skipping`);
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Count non-zero token positions (excluding native ETH and stablecoins used as base currency)
|
|
1300
|
+
*/
|
|
1301
|
+
countActivePositions(marketData) {
|
|
1302
|
+
const NATIVE_ETH_KEY = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
1303
|
+
let count = 0;
|
|
1304
|
+
for (const [address, balance] of Object.entries(marketData.balances)) {
|
|
1305
|
+
if (address.toLowerCase() === NATIVE_ETH_KEY) continue;
|
|
1306
|
+
if (balance > 0n) count++;
|
|
1307
|
+
}
|
|
1308
|
+
return count;
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Check if daily loss limit has been hit
|
|
1312
|
+
*/
|
|
1313
|
+
isDailyLossLimitHit(portfolioValue) {
|
|
1314
|
+
const maxLoss = portfolioValue * this.config.maxDailyLossBps / 1e4;
|
|
1315
|
+
return this.dailyPnL < -maxLoss;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Estimate USD value of a trade signal
|
|
1319
|
+
*/
|
|
1320
|
+
estimateSignalValue(signal, marketData) {
|
|
1321
|
+
const price = marketData.prices[signal.tokenIn.toLowerCase()] || 0;
|
|
1322
|
+
const tokenDecimals = getTokenDecimals(signal.tokenIn);
|
|
1323
|
+
const amount = Number(signal.amountIn) / Math.pow(10, tokenDecimals);
|
|
1324
|
+
return amount * price;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Update daily PnL after a trade (market gains/losses only)
|
|
1328
|
+
*/
|
|
1329
|
+
updatePnL(pnl) {
|
|
1330
|
+
this.dailyPnL += pnl;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Update daily fees (trading fees, gas costs, etc.)
|
|
1334
|
+
* Fees are tracked separately and do NOT count toward the daily loss limit.
|
|
1335
|
+
* This prevents protocol fees from triggering circuit breakers.
|
|
1336
|
+
*/
|
|
1337
|
+
updateFees(fees) {
|
|
1338
|
+
this.dailyFees += fees;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Get current risk status
|
|
1342
|
+
* @param portfolioValue - Current portfolio value in USD (needed for accurate loss limit)
|
|
1343
|
+
*/
|
|
1344
|
+
getStatus(portfolioValue) {
|
|
1345
|
+
const pv = portfolioValue || 0;
|
|
1346
|
+
const maxLossUSD = pv * this.config.maxDailyLossBps / 1e4;
|
|
1347
|
+
return {
|
|
1348
|
+
dailyPnL: this.dailyPnL,
|
|
1349
|
+
dailyFees: this.dailyFees,
|
|
1350
|
+
dailyNetPnL: this.dailyPnL - this.dailyFees,
|
|
1351
|
+
dailyLossLimit: maxLossUSD,
|
|
1352
|
+
// Only market PnL triggers the limit — fees are excluded
|
|
1353
|
+
isLimitHit: pv > 0 ? this.dailyPnL < -maxLossUSD : false
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1129
1358
|
// src/vault/manager.ts
|
|
1130
1359
|
var import_viem2 = require("viem");
|
|
1131
1360
|
var import_accounts = require("viem/accounts");
|
|
1132
1361
|
var import_chains = require("viem/chains");
|
|
1133
1362
|
var ADDRESSES = {
|
|
1134
|
-
testnet: {
|
|
1135
|
-
vaultFactory: "0x5c099daaE33801a907Bb57011c6749655b55dc75",
|
|
1136
|
-
registry: "0xCF48C341e3FebeCA5ECB7eb2535f61A2Ba855d9C",
|
|
1137
|
-
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
1138
|
-
},
|
|
1139
1363
|
mainnet: {
|
|
1140
|
-
vaultFactory: "0x0000000000000000000000000000000000000000",
|
|
1141
|
-
|
|
1142
|
-
registry: "0x0000000000000000000000000000000000000000",
|
|
1364
|
+
vaultFactory: process.env.EXAGENT_VAULT_FACTORY_ADDRESS || "0x0000000000000000000000000000000000000000",
|
|
1365
|
+
registry: process.env.EXAGENT_REGISTRY_ADDRESS || "0x0000000000000000000000000000000000000000",
|
|
1143
1366
|
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
1144
|
-
// Base mainnet USDC
|
|
1145
1367
|
}
|
|
1146
1368
|
};
|
|
1147
1369
|
var VAULT_FACTORY_ABI = [
|
|
@@ -1165,6 +1387,7 @@ var VAULT_FACTORY_ABI = [
|
|
|
1165
1387
|
inputs: [
|
|
1166
1388
|
{ name: "agentId", type: "uint256" },
|
|
1167
1389
|
{ name: "asset", type: "address" },
|
|
1390
|
+
{ name: "seedAmount", type: "uint256" },
|
|
1168
1391
|
{ name: "name", type: "string" },
|
|
1169
1392
|
{ name: "symbol", type: "string" },
|
|
1170
1393
|
{ name: "feeRecipient", type: "address" }
|
|
@@ -1178,13 +1401,6 @@ var VAULT_FACTORY_ABI = [
|
|
|
1178
1401
|
inputs: [],
|
|
1179
1402
|
outputs: [{ type: "uint256" }],
|
|
1180
1403
|
stateMutability: "view"
|
|
1181
|
-
},
|
|
1182
|
-
{
|
|
1183
|
-
type: "function",
|
|
1184
|
-
name: "eXABurnFee",
|
|
1185
|
-
inputs: [],
|
|
1186
|
-
outputs: [{ type: "uint256" }],
|
|
1187
|
-
stateMutability: "view"
|
|
1188
1404
|
}
|
|
1189
1405
|
];
|
|
1190
1406
|
var VAULT_ABI = [
|
|
@@ -1224,12 +1440,13 @@ var VaultManager = class {
|
|
|
1224
1440
|
lastVaultCheck = 0;
|
|
1225
1441
|
VAULT_CACHE_TTL = 6e4;
|
|
1226
1442
|
// 1 minute
|
|
1443
|
+
enabled = true;
|
|
1227
1444
|
constructor(config) {
|
|
1228
1445
|
this.config = config;
|
|
1229
1446
|
this.addresses = ADDRESSES[config.network];
|
|
1230
1447
|
this.account = (0, import_accounts.privateKeyToAccount)(config.walletKey);
|
|
1231
|
-
this.chain =
|
|
1232
|
-
const rpcUrl =
|
|
1448
|
+
this.chain = import_chains.base;
|
|
1449
|
+
const rpcUrl = "https://mainnet.base.org";
|
|
1233
1450
|
this.publicClient = (0, import_viem2.createPublicClient)({
|
|
1234
1451
|
chain: this.chain,
|
|
1235
1452
|
transport: (0, import_viem2.http)(rpcUrl)
|
|
@@ -1239,6 +1456,10 @@ var VaultManager = class {
|
|
|
1239
1456
|
chain: this.chain,
|
|
1240
1457
|
transport: (0, import_viem2.http)(rpcUrl)
|
|
1241
1458
|
});
|
|
1459
|
+
if (this.addresses.vaultFactory === "0x0000000000000000000000000000000000000000") {
|
|
1460
|
+
console.warn("VaultFactory address is zero \u2014 vault operations will be disabled");
|
|
1461
|
+
this.enabled = false;
|
|
1462
|
+
}
|
|
1242
1463
|
}
|
|
1243
1464
|
/**
|
|
1244
1465
|
* Get the agent's vault policy
|
|
@@ -1256,6 +1477,17 @@ var VaultManager = class {
|
|
|
1256
1477
|
* Get comprehensive vault status
|
|
1257
1478
|
*/
|
|
1258
1479
|
async getVaultStatus() {
|
|
1480
|
+
if (!this.enabled) {
|
|
1481
|
+
return {
|
|
1482
|
+
hasVault: false,
|
|
1483
|
+
vaultAddress: null,
|
|
1484
|
+
totalAssets: BigInt(0),
|
|
1485
|
+
canCreateVault: false,
|
|
1486
|
+
cannotCreateReason: "Vault operations disabled (contract address not set)",
|
|
1487
|
+
requirementsMet: false,
|
|
1488
|
+
requirements: { veXARequired: BigInt(0), isBypassed: false }
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1259
1491
|
const vaultAddress = await this.getVaultAddress();
|
|
1260
1492
|
const hasVault = vaultAddress !== null;
|
|
1261
1493
|
let totalAssets = BigInt(0);
|
|
@@ -1290,22 +1522,16 @@ var VaultManager = class {
|
|
|
1290
1522
|
}
|
|
1291
1523
|
/**
|
|
1292
1524
|
* Get vault creation requirements
|
|
1525
|
+
* Note: No burnFee on mainnet — vault creation requires USDC seed instead
|
|
1293
1526
|
*/
|
|
1294
1527
|
async getRequirements() {
|
|
1295
|
-
const
|
|
1296
|
-
this.
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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 };
|
|
1528
|
+
const veXARequired = await this.publicClient.readContract({
|
|
1529
|
+
address: this.addresses.vaultFactory,
|
|
1530
|
+
abi: VAULT_FACTORY_ABI,
|
|
1531
|
+
functionName: "minimumVeEXARequired"
|
|
1532
|
+
});
|
|
1533
|
+
const isBypassed = veXARequired === BigInt(0);
|
|
1534
|
+
return { veXARequired, isBypassed };
|
|
1309
1535
|
}
|
|
1310
1536
|
/**
|
|
1311
1537
|
* Get the agent's vault address (cached)
|
|
@@ -1329,30 +1555,15 @@ var VaultManager = class {
|
|
|
1329
1555
|
this.cachedVaultAddress = vaultAddress;
|
|
1330
1556
|
return vaultAddress;
|
|
1331
1557
|
}
|
|
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
1558
|
/**
|
|
1352
1559
|
* Create a vault for the agent
|
|
1560
|
+
* @param seedAmount - USDC seed amount in raw units (default: 100e6 = 100 USDC)
|
|
1353
1561
|
* @returns Vault address if successful
|
|
1354
1562
|
*/
|
|
1355
|
-
async createVault() {
|
|
1563
|
+
async createVault(seedAmount) {
|
|
1564
|
+
if (!this.enabled) {
|
|
1565
|
+
return { success: false, error: "Vault operations disabled (contract address not set)" };
|
|
1566
|
+
}
|
|
1356
1567
|
if (this.policy === "disabled") {
|
|
1357
1568
|
return { success: false, error: "Vault creation disabled by policy" };
|
|
1358
1569
|
}
|
|
@@ -1364,6 +1575,7 @@ var VaultManager = class {
|
|
|
1364
1575
|
if (!status.canCreateVault) {
|
|
1365
1576
|
return { success: false, error: status.cannotCreateReason || "Requirements not met" };
|
|
1366
1577
|
}
|
|
1578
|
+
const seed = seedAmount || BigInt(1e8);
|
|
1367
1579
|
const vaultName = this.config.vaultConfig.defaultName || `${this.config.agentName} Trading Vault`;
|
|
1368
1580
|
const vaultSymbol = this.config.vaultConfig.defaultSymbol || `ex${this.config.agentName.replace(/[^a-zA-Z]/g, "").slice(0, 4).toUpperCase()}`;
|
|
1369
1581
|
const feeRecipient = this.config.vaultConfig.feeRecipient || this.account.address;
|
|
@@ -1375,6 +1587,7 @@ var VaultManager = class {
|
|
|
1375
1587
|
args: [
|
|
1376
1588
|
this.config.agentId,
|
|
1377
1589
|
this.addresses.usdc,
|
|
1590
|
+
seed,
|
|
1378
1591
|
vaultName,
|
|
1379
1592
|
vaultSymbol,
|
|
1380
1593
|
feeRecipient
|
|
@@ -1438,37 +1651,12 @@ var VaultManager = class {
|
|
|
1438
1651
|
};
|
|
1439
1652
|
}
|
|
1440
1653
|
}
|
|
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
1654
|
};
|
|
1468
1655
|
|
|
1469
1656
|
// src/relay.ts
|
|
1470
1657
|
var import_ws = __toESM(require("ws"));
|
|
1471
1658
|
var import_accounts2 = require("viem/accounts");
|
|
1659
|
+
var import_sdk = require("@exagent/sdk");
|
|
1472
1660
|
var RelayClient = class {
|
|
1473
1661
|
config;
|
|
1474
1662
|
ws = null;
|
|
@@ -1571,7 +1759,8 @@ var RelayClient = class {
|
|
|
1571
1759
|
agentId: this.config.agentId,
|
|
1572
1760
|
wallet: account.address,
|
|
1573
1761
|
timestamp,
|
|
1574
|
-
signature
|
|
1762
|
+
signature,
|
|
1763
|
+
sdkVersion: import_sdk.SDK_VERSION
|
|
1575
1764
|
});
|
|
1576
1765
|
}
|
|
1577
1766
|
/**
|
|
@@ -1733,6 +1922,7 @@ function openBrowser(url) {
|
|
|
1733
1922
|
|
|
1734
1923
|
// src/runtime.ts
|
|
1735
1924
|
var FUNDS_LOW_THRESHOLD = 5e-3;
|
|
1925
|
+
var FUNDS_CRITICAL_THRESHOLD = 1e-3;
|
|
1736
1926
|
var AgentRuntime = class {
|
|
1737
1927
|
config;
|
|
1738
1928
|
client;
|
|
@@ -1746,14 +1936,13 @@ var AgentRuntime = class {
|
|
|
1746
1936
|
isRunning = false;
|
|
1747
1937
|
mode = "idle";
|
|
1748
1938
|
configHash;
|
|
1749
|
-
lastVaultCheck = 0;
|
|
1750
1939
|
cycleCount = 0;
|
|
1751
1940
|
lastCycleAt = 0;
|
|
1752
1941
|
lastPortfolioValue = 0;
|
|
1753
1942
|
lastEthBalance = "0";
|
|
1754
1943
|
processAlive = true;
|
|
1755
|
-
|
|
1756
|
-
|
|
1944
|
+
riskUniverse = 0;
|
|
1945
|
+
allowedTokens = /* @__PURE__ */ new Set();
|
|
1757
1946
|
constructor(config) {
|
|
1758
1947
|
this.config = config;
|
|
1759
1948
|
}
|
|
@@ -1762,7 +1951,7 @@ var AgentRuntime = class {
|
|
|
1762
1951
|
*/
|
|
1763
1952
|
async initialize() {
|
|
1764
1953
|
console.log(`Initializing agent: ${this.config.name} (ID: ${this.config.agentId})`);
|
|
1765
|
-
this.client = new
|
|
1954
|
+
this.client = new import_sdk2.ExagentClient({
|
|
1766
1955
|
privateKey: this.config.privateKey,
|
|
1767
1956
|
network: this.config.network
|
|
1768
1957
|
});
|
|
@@ -1773,6 +1962,7 @@ var AgentRuntime = class {
|
|
|
1773
1962
|
}
|
|
1774
1963
|
console.log(`Agent verified: ${agent.name}`);
|
|
1775
1964
|
await this.ensureWalletLinked();
|
|
1965
|
+
await this.loadTradingRestrictions();
|
|
1776
1966
|
console.log(`Initializing LLM: ${this.config.llm.provider}`);
|
|
1777
1967
|
this.llm = await createLLMAdapter(this.config.llm);
|
|
1778
1968
|
const llmMeta = this.llm.getMetadata();
|
|
@@ -1840,12 +2030,8 @@ var AgentRuntime = class {
|
|
|
1840
2030
|
console.log(`Vault TVL: ${Number(status.totalAssets) / 1e6} USDC`);
|
|
1841
2031
|
} else {
|
|
1842
2032
|
console.log("No vault exists for this agent");
|
|
1843
|
-
if (vaultConfig.policy === "
|
|
1844
|
-
|
|
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
|
-
}
|
|
2033
|
+
if (vaultConfig.policy === "manual") {
|
|
2034
|
+
console.log("Vault creation is manual \u2014 use the command center to create one");
|
|
1849
2035
|
}
|
|
1850
2036
|
}
|
|
1851
2037
|
}
|
|
@@ -1864,7 +2050,7 @@ var AgentRuntime = class {
|
|
|
1864
2050
|
if (agent?.owner.toLowerCase() !== address.toLowerCase()) {
|
|
1865
2051
|
const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
|
|
1866
2052
|
const nonce = await this.client.registry.getNonce(address);
|
|
1867
|
-
const linkMessage =
|
|
2053
|
+
const linkMessage = import_sdk2.ExagentRegistry.generateLinkMessage(
|
|
1868
2054
|
address,
|
|
1869
2055
|
agentId,
|
|
1870
2056
|
nonce
|
|
@@ -1904,6 +2090,49 @@ var AgentRuntime = class {
|
|
|
1904
2090
|
console.log("Wallet already linked");
|
|
1905
2091
|
}
|
|
1906
2092
|
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Load risk universe and allowed tokens from on-chain registry.
|
|
2095
|
+
* This prevents the agent from wasting gas on trades that will revert.
|
|
2096
|
+
*/
|
|
2097
|
+
async loadTradingRestrictions() {
|
|
2098
|
+
const agentId = BigInt(this.config.agentId);
|
|
2099
|
+
const RISK_UNIVERSE_NAMES = ["Core", "Established", "Derivatives", "Emerging", "Frontier"];
|
|
2100
|
+
try {
|
|
2101
|
+
this.riskUniverse = await this.client.registry.getRiskUniverse(agentId);
|
|
2102
|
+
console.log(`Risk universe: ${RISK_UNIVERSE_NAMES[this.riskUniverse] || this.riskUniverse}`);
|
|
2103
|
+
const configTokens = this.config.allowedTokens || this.getDefaultTokens();
|
|
2104
|
+
const verified = [];
|
|
2105
|
+
for (const token of configTokens) {
|
|
2106
|
+
try {
|
|
2107
|
+
const allowed = await this.client.registry.isTradeAllowed(
|
|
2108
|
+
agentId,
|
|
2109
|
+
token,
|
|
2110
|
+
"0x0000000000000000000000000000000000000000"
|
|
2111
|
+
// zero = check token only
|
|
2112
|
+
);
|
|
2113
|
+
if (allowed) {
|
|
2114
|
+
this.allowedTokens.add(token.toLowerCase());
|
|
2115
|
+
verified.push(token);
|
|
2116
|
+
} else {
|
|
2117
|
+
console.warn(`Token ${token} not allowed for this agent's risk universe \u2014 excluded`);
|
|
2118
|
+
}
|
|
2119
|
+
} catch {
|
|
2120
|
+
this.allowedTokens.add(token.toLowerCase());
|
|
2121
|
+
verified.push(token);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
this.config.allowedTokens = verified;
|
|
2125
|
+
console.log(`Allowed tokens loaded: ${verified.length} tokens verified`);
|
|
2126
|
+
if (this.riskUniverse === 4) {
|
|
2127
|
+
console.warn("Frontier risk universe: vault creation is disabled");
|
|
2128
|
+
}
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
console.warn(
|
|
2131
|
+
"Could not load trading restrictions from registry (using defaults):",
|
|
2132
|
+
error instanceof Error ? error.message : error
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
1907
2136
|
/**
|
|
1908
2137
|
* Sync the LLM config hash to chain for epoch tracking.
|
|
1909
2138
|
* If the wallet has insufficient gas, enters a recovery loop
|
|
@@ -1912,7 +2141,7 @@ var AgentRuntime = class {
|
|
|
1912
2141
|
async syncConfigHash() {
|
|
1913
2142
|
const agentId = BigInt(this.config.agentId);
|
|
1914
2143
|
const llmMeta = this.llm.getMetadata();
|
|
1915
|
-
this.configHash =
|
|
2144
|
+
this.configHash = import_sdk2.ExagentRegistry.calculateConfigHash(llmMeta.provider, llmMeta.model);
|
|
1916
2145
|
console.log(`Config hash: ${this.configHash}`);
|
|
1917
2146
|
const onChainHash = await this.client.registry.getConfigHash(agentId);
|
|
1918
2147
|
if (onChainHash !== this.configHash) {
|
|
@@ -1925,7 +2154,7 @@ var AgentRuntime = class {
|
|
|
1925
2154
|
const message = error instanceof Error ? error.message : String(error);
|
|
1926
2155
|
if (message.includes("insufficient funds") || message.includes("gas") || message.includes("intrinsic gas too low") || message.includes("exceeds the balance")) {
|
|
1927
2156
|
const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
|
|
1928
|
-
const chain =
|
|
2157
|
+
const chain = import_chains2.base;
|
|
1929
2158
|
const publicClientInstance = (0, import_viem3.createPublicClient)({
|
|
1930
2159
|
chain,
|
|
1931
2160
|
transport: (0, import_viem3.http)(this.getRpcUrl())
|
|
@@ -2076,21 +2305,38 @@ var AgentRuntime = class {
|
|
|
2076
2305
|
break;
|
|
2077
2306
|
case "update_risk_params": {
|
|
2078
2307
|
const params = cmd.params || {};
|
|
2308
|
+
let updated = false;
|
|
2079
2309
|
if (params.maxPositionSizeBps !== void 0) {
|
|
2080
|
-
|
|
2310
|
+
const value = Number(params.maxPositionSizeBps);
|
|
2311
|
+
if (isNaN(value) || value < 100 || value > 1e4) {
|
|
2312
|
+
this.relay?.sendCommandResult(cmd.id, false, "maxPositionSizeBps must be 100-10000");
|
|
2313
|
+
break;
|
|
2314
|
+
}
|
|
2315
|
+
this.config.trading.maxPositionSizeBps = value;
|
|
2316
|
+
updated = true;
|
|
2081
2317
|
}
|
|
2082
2318
|
if (params.maxDailyLossBps !== void 0) {
|
|
2083
|
-
|
|
2319
|
+
const value = Number(params.maxDailyLossBps);
|
|
2320
|
+
if (isNaN(value) || value < 50 || value > 5e3) {
|
|
2321
|
+
this.relay?.sendCommandResult(cmd.id, false, "maxDailyLossBps must be 50-5000");
|
|
2322
|
+
break;
|
|
2323
|
+
}
|
|
2324
|
+
this.config.trading.maxDailyLossBps = value;
|
|
2325
|
+
updated = true;
|
|
2326
|
+
}
|
|
2327
|
+
if (updated) {
|
|
2328
|
+
this.riskManager = new RiskManager(this.config.trading);
|
|
2329
|
+
console.log("Risk params updated via command center");
|
|
2330
|
+
this.relay?.sendCommandResult(cmd.id, true, "Risk parameters updated");
|
|
2331
|
+
this.relay?.sendMessage(
|
|
2332
|
+
"config_updated",
|
|
2333
|
+
"info",
|
|
2334
|
+
"Risk Parameters Updated",
|
|
2335
|
+
`Max position: ${this.config.trading.maxPositionSizeBps / 100}%, Max daily loss: ${this.config.trading.maxDailyLossBps / 100}%`
|
|
2336
|
+
);
|
|
2337
|
+
} else {
|
|
2338
|
+
this.relay?.sendCommandResult(cmd.id, false, "No valid parameters provided");
|
|
2084
2339
|
}
|
|
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
2340
|
break;
|
|
2095
2341
|
}
|
|
2096
2342
|
case "update_trading_interval": {
|
|
@@ -2167,7 +2413,7 @@ var AgentRuntime = class {
|
|
|
2167
2413
|
provider: this.config.llm.provider,
|
|
2168
2414
|
model: this.config.llm.model || "default"
|
|
2169
2415
|
},
|
|
2170
|
-
risk: this.riskManager?.getStatus() || {
|
|
2416
|
+
risk: this.riskManager?.getStatus(this.lastPortfolioValue) || {
|
|
2171
2417
|
dailyPnL: 0,
|
|
2172
2418
|
dailyLossLimit: 0,
|
|
2173
2419
|
isLimitHit: false
|
|
@@ -2188,14 +2434,18 @@ var AgentRuntime = class {
|
|
|
2188
2434
|
--- Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
|
|
2189
2435
|
this.cycleCount++;
|
|
2190
2436
|
this.lastCycleAt = Date.now();
|
|
2191
|
-
await this.checkVaultAutoCreation();
|
|
2192
2437
|
const tokens = this.config.allowedTokens || this.getDefaultTokens();
|
|
2193
2438
|
const marketData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
2194
2439
|
console.log(`Portfolio value: $${marketData.portfolioValue.toFixed(2)}`);
|
|
2195
2440
|
this.lastPortfolioValue = marketData.portfolioValue;
|
|
2196
2441
|
const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
2197
2442
|
this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
|
|
2198
|
-
this.checkFundsLow(marketData);
|
|
2443
|
+
const fundsOk = this.checkFundsLow(marketData);
|
|
2444
|
+
if (!fundsOk) {
|
|
2445
|
+
console.warn("Skipping trading cycle \u2014 ETH balance critically low");
|
|
2446
|
+
this.sendRelayStatus();
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2199
2449
|
let signals;
|
|
2200
2450
|
try {
|
|
2201
2451
|
signals = await this.strategy(marketData, this.llm, this.config);
|
|
@@ -2213,19 +2463,31 @@ var AgentRuntime = class {
|
|
|
2213
2463
|
console.log(`Strategy generated ${signals.length} signals`);
|
|
2214
2464
|
const filteredSignals = this.riskManager.filterSignals(signals, marketData);
|
|
2215
2465
|
console.log(`${filteredSignals.length} signals passed risk checks`);
|
|
2216
|
-
if (this.riskManager.getStatus().isLimitHit) {
|
|
2466
|
+
if (this.riskManager.getStatus(marketData.portfolioValue).isLimitHit) {
|
|
2217
2467
|
this.relay?.sendMessage(
|
|
2218
2468
|
"risk_limit_hit",
|
|
2219
2469
|
"warning",
|
|
2220
2470
|
"Risk Limit Hit",
|
|
2221
|
-
`Daily loss limit reached:
|
|
2471
|
+
`Daily loss limit reached: $${this.riskManager.getStatus(marketData.portfolioValue).dailyPnL.toFixed(2)}`
|
|
2222
2472
|
);
|
|
2223
2473
|
}
|
|
2224
2474
|
if (filteredSignals.length > 0) {
|
|
2475
|
+
const vaultStatus = await this.vaultManager?.getVaultStatus();
|
|
2476
|
+
if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
|
|
2477
|
+
console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
|
|
2478
|
+
}
|
|
2479
|
+
const preTradePortfolioValue = marketData.portfolioValue;
|
|
2225
2480
|
const results = await this.executor.executeAll(filteredSignals);
|
|
2481
|
+
let totalFeesUSD = 0;
|
|
2226
2482
|
for (const result of results) {
|
|
2227
2483
|
if (result.success) {
|
|
2228
2484
|
console.log(`Trade executed: ${result.signal.action} - ${result.txHash}`);
|
|
2485
|
+
const feeCostBps = 20;
|
|
2486
|
+
const signalPrice = marketData.prices[result.signal.tokenIn.toLowerCase()] || 0;
|
|
2487
|
+
const amountUSD = Number(result.signal.amountIn) / Math.pow(10, getTokenDecimals(result.signal.tokenIn)) * signalPrice;
|
|
2488
|
+
const feeCostUSD = amountUSD * feeCostBps / 1e4;
|
|
2489
|
+
totalFeesUSD += feeCostUSD;
|
|
2490
|
+
this.riskManager.updateFees(feeCostUSD);
|
|
2229
2491
|
this.relay?.sendMessage(
|
|
2230
2492
|
"trade_executed",
|
|
2231
2493
|
"success",
|
|
@@ -2249,18 +2511,43 @@ var AgentRuntime = class {
|
|
|
2249
2511
|
);
|
|
2250
2512
|
}
|
|
2251
2513
|
}
|
|
2514
|
+
const postTokens = this.config.allowedTokens || this.getDefaultTokens();
|
|
2515
|
+
const postTradeData = await this.marketData.fetchMarketData(this.client.address, postTokens);
|
|
2516
|
+
const marketPnL = postTradeData.portfolioValue - preTradePortfolioValue + totalFeesUSD;
|
|
2517
|
+
this.riskManager.updatePnL(marketPnL);
|
|
2518
|
+
if (marketPnL !== 0) {
|
|
2519
|
+
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
2520
|
+
}
|
|
2521
|
+
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
2522
|
+
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
2523
|
+
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|
|
2252
2524
|
}
|
|
2253
2525
|
this.sendRelayStatus();
|
|
2254
2526
|
}
|
|
2255
2527
|
/**
|
|
2256
|
-
* Check if ETH balance is below threshold and notify
|
|
2528
|
+
* Check if ETH balance is below threshold and notify.
|
|
2529
|
+
* Returns true if trading should continue, false if ETH is critically low.
|
|
2257
2530
|
*/
|
|
2258
2531
|
checkFundsLow(marketData) {
|
|
2259
|
-
if (!this.relay) return;
|
|
2260
2532
|
const ethBalance = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
2261
2533
|
const ethAmount = Number(ethBalance) / 1e18;
|
|
2534
|
+
if (ethAmount < FUNDS_CRITICAL_THRESHOLD) {
|
|
2535
|
+
console.error(`ETH balance critically low: ${ethAmount.toFixed(6)} ETH \u2014 halting trading`);
|
|
2536
|
+
this.relay?.sendMessage(
|
|
2537
|
+
"funds_low",
|
|
2538
|
+
"error",
|
|
2539
|
+
"Funds Critical",
|
|
2540
|
+
`ETH balance is ${ethAmount.toFixed(6)} ETH (below ${FUNDS_CRITICAL_THRESHOLD} ETH minimum). Trading halted \u2014 fund your wallet.`,
|
|
2541
|
+
{
|
|
2542
|
+
ethBalance: ethAmount.toFixed(6),
|
|
2543
|
+
wallet: this.client.address,
|
|
2544
|
+
threshold: FUNDS_CRITICAL_THRESHOLD
|
|
2545
|
+
}
|
|
2546
|
+
);
|
|
2547
|
+
return false;
|
|
2548
|
+
}
|
|
2262
2549
|
if (ethAmount < FUNDS_LOW_THRESHOLD) {
|
|
2263
|
-
this.relay
|
|
2550
|
+
this.relay?.sendMessage(
|
|
2264
2551
|
"funds_low",
|
|
2265
2552
|
"warning",
|
|
2266
2553
|
"Low Funds",
|
|
@@ -2272,36 +2559,7 @@ var AgentRuntime = class {
|
|
|
2272
2559
|
}
|
|
2273
2560
|
);
|
|
2274
2561
|
}
|
|
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
|
-
}
|
|
2562
|
+
return true;
|
|
2305
2563
|
}
|
|
2306
2564
|
/**
|
|
2307
2565
|
* Stop the agent process completely
|
|
@@ -2319,26 +2577,44 @@ var AgentRuntime = class {
|
|
|
2319
2577
|
* Get RPC URL based on network
|
|
2320
2578
|
*/
|
|
2321
2579
|
getRpcUrl() {
|
|
2322
|
-
|
|
2323
|
-
return "https://mainnet.base.org";
|
|
2324
|
-
}
|
|
2325
|
-
return "https://sepolia.base.org";
|
|
2580
|
+
return "https://mainnet.base.org";
|
|
2326
2581
|
}
|
|
2327
2582
|
/**
|
|
2328
|
-
* Default tokens to track
|
|
2583
|
+
* Default tokens to track.
|
|
2584
|
+
* These are validated against the on-chain registry's isTradeAllowed() during init —
|
|
2585
|
+
* agents in restricted risk universes will have ineligible tokens filtered out.
|
|
2329
2586
|
*/
|
|
2330
2587
|
getDefaultTokens() {
|
|
2331
|
-
if (this.config.network === "mainnet") {
|
|
2332
|
-
return [
|
|
2333
|
-
"0x4200000000000000000000000000000000000006",
|
|
2334
|
-
// WETH
|
|
2335
|
-
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
2336
|
-
// USDC
|
|
2337
|
-
];
|
|
2338
|
-
}
|
|
2339
2588
|
return [
|
|
2340
|
-
|
|
2589
|
+
// Core
|
|
2590
|
+
"0x4200000000000000000000000000000000000006",
|
|
2341
2591
|
// WETH
|
|
2592
|
+
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
2593
|
+
// USDC
|
|
2594
|
+
"0x2Ae3F1Ec7F1F5012CFEab0185bFC7aa3cf0DEC22",
|
|
2595
|
+
// cbETH
|
|
2596
|
+
"0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
|
|
2597
|
+
// USDbC
|
|
2598
|
+
"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
|
2599
|
+
// DAI
|
|
2600
|
+
// Established
|
|
2601
|
+
"0x940181a94A35A4569E4529A3CDfB74e38FD98631",
|
|
2602
|
+
// AERO
|
|
2603
|
+
"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
|
2604
|
+
// cbBTC
|
|
2605
|
+
"0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
|
|
2606
|
+
// wstETH
|
|
2607
|
+
"0x2416092f143378750bb29b79eD961ab195CcEea5",
|
|
2608
|
+
// ezETH
|
|
2609
|
+
// Emerging (filtered by risk universe at init)
|
|
2610
|
+
"0x532f27101965dd16442E59d40670FaF5eBB142E4",
|
|
2611
|
+
// BRETT
|
|
2612
|
+
"0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed",
|
|
2613
|
+
// DEGEN
|
|
2614
|
+
"0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b",
|
|
2615
|
+
// VIRTUAL
|
|
2616
|
+
"0xAC1Bd2486Aaf3B5C0fc3Fd868558b082a531B2B4"
|
|
2617
|
+
// TOSHI
|
|
2342
2618
|
];
|
|
2343
2619
|
}
|
|
2344
2620
|
sleep(ms) {
|
|
@@ -2359,7 +2635,7 @@ var AgentRuntime = class {
|
|
|
2359
2635
|
model: this.config.llm.model || "default"
|
|
2360
2636
|
},
|
|
2361
2637
|
configHash: this.configHash || "not initialized",
|
|
2362
|
-
risk: this.riskManager?.getStatus() || { dailyPnL: 0, dailyLossLimit: 0, isLimitHit: false },
|
|
2638
|
+
risk: this.riskManager?.getStatus(this.lastPortfolioValue) || { dailyPnL: 0, dailyLossLimit: 0, isLimitHit: false },
|
|
2363
2639
|
vault: {
|
|
2364
2640
|
policy: vaultConfig.policy,
|
|
2365
2641
|
hasVault: false,
|
|
@@ -2421,16 +2697,18 @@ var TradingConfigSchema = import_zod.z.object({
|
|
|
2421
2697
|
maxDailyLossBps: import_zod.z.number().min(0).max(1e4).default(500),
|
|
2422
2698
|
// 0-100%
|
|
2423
2699
|
maxConcurrentPositions: import_zod.z.number().min(1).max(100).default(5),
|
|
2424
|
-
tradingIntervalMs: import_zod.z.number().min(1e3).default(6e4)
|
|
2700
|
+
tradingIntervalMs: import_zod.z.number().min(1e3).default(6e4),
|
|
2425
2701
|
// minimum 1 second
|
|
2702
|
+
maxSlippageBps: import_zod.z.number().min(10).max(1e3).default(100),
|
|
2703
|
+
// 0.1-10%, default 1%
|
|
2704
|
+
minTradeValueUSD: import_zod.z.number().min(0).default(1)
|
|
2705
|
+
// minimum trade value in USD
|
|
2426
2706
|
});
|
|
2427
2707
|
var VaultPolicySchema = import_zod.z.enum([
|
|
2428
2708
|
"disabled",
|
|
2429
2709
|
// Never create a vault - trade with agent's own capital only
|
|
2430
|
-
"manual"
|
|
2710
|
+
"manual"
|
|
2431
2711
|
// Only create vault when explicitly directed by owner
|
|
2432
|
-
"auto_when_qualified"
|
|
2433
|
-
// Automatically create vault when requirements are met
|
|
2434
2712
|
]);
|
|
2435
2713
|
var VaultConfigSchema = import_zod.z.object({
|
|
2436
2714
|
// Policy for vault creation (asked during deployment)
|
|
@@ -2458,7 +2736,7 @@ var AgentConfigSchema = import_zod.z.object({
|
|
|
2458
2736
|
agentId: import_zod.z.union([import_zod.z.number().positive(), import_zod.z.string()]),
|
|
2459
2737
|
name: import_zod.z.string().min(3).max(32),
|
|
2460
2738
|
// Network
|
|
2461
|
-
network: import_zod.z.
|
|
2739
|
+
network: import_zod.z.literal("mainnet").default("mainnet"),
|
|
2462
2740
|
// Wallet setup preference
|
|
2463
2741
|
wallet: WalletConfigSchema,
|
|
2464
2742
|
// LLM
|
|
@@ -2545,7 +2823,7 @@ function createSampleConfig(agentId, name) {
|
|
|
2545
2823
|
return {
|
|
2546
2824
|
agentId,
|
|
2547
2825
|
name,
|
|
2548
|
-
network: "
|
|
2826
|
+
network: "mainnet",
|
|
2549
2827
|
llm: {
|
|
2550
2828
|
provider: "openai",
|
|
2551
2829
|
model: "gpt-4.1",
|
|
@@ -2558,7 +2836,9 @@ function createSampleConfig(agentId, name) {
|
|
|
2558
2836
|
maxPositionSizeBps: 1e3,
|
|
2559
2837
|
maxDailyLossBps: 500,
|
|
2560
2838
|
maxConcurrentPositions: 5,
|
|
2561
|
-
tradingIntervalMs: 6e4
|
|
2839
|
+
tradingIntervalMs: 6e4,
|
|
2840
|
+
maxSlippageBps: 100,
|
|
2841
|
+
minTradeValueUSD: 1
|
|
2562
2842
|
},
|
|
2563
2843
|
vault: {
|
|
2564
2844
|
// Default to manual - user must explicitly enable auto-creation
|