@exagent/agent 0.1.46 → 0.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -332,7 +332,7 @@ function getTokenDecimals(address) {
332
332
  function getTokenSymbol(address) {
333
333
  return _globalResolver?.getSymbol(address.toLowerCase());
334
334
  }
335
- var import_viem2, import_chains, NATIVE_ETH, _globalResolver, TOKEN_DECIMALS, TOKEN_TO_COINGECKO, STABLECOIN_IDS, PRICE_STALENESS_MS, MarketDataService;
335
+ var import_viem2, import_chains, NATIVE_ETH, _globalResolver, TOKEN_DECIMALS, TOKEN_TO_COINGECKO, STABLECOIN_IDS, PRICE_STALENESS_MS, HISTORY_STALENESS_MS, MarketDataService;
336
336
  var init_market = __esm({
337
337
  "src/trading/market.ts"() {
338
338
  "use strict";
@@ -498,6 +498,7 @@ var init_market = __esm({
498
498
  };
499
499
  STABLECOIN_IDS = /* @__PURE__ */ new Set(["usd-coin", "dai", "tether"]);
500
500
  PRICE_STALENESS_MS = 6e4;
501
+ HISTORY_STALENESS_MS = 60 * 6e4;
501
502
  MarketDataService = class {
502
503
  rpcUrl;
503
504
  client;
@@ -510,6 +511,10 @@ var init_market = __esm({
510
511
  cachedVolume24h = {};
511
512
  /** Cached price change data */
512
513
  cachedPriceChange24h = {};
514
+ /** Cached 24h hourly price history per token address */
515
+ cachedPriceHistory = {};
516
+ /** Timestamp of last successful price history fetch */
517
+ lastHistoryFetchAt = 0;
513
518
  constructor(rpcUrl, store) {
514
519
  this.rpcUrl = rpcUrl;
515
520
  this.client = (0, import_viem2.createPublicClient)({
@@ -530,11 +535,10 @@ var init_market = __esm({
530
535
  const prices = await this.fetchPrices(tokenAddresses);
531
536
  const balances = await this.fetchBalances(walletAddress, tokenAddresses);
532
537
  const portfolioValue = this.calculatePortfolioValue(balances, prices);
533
- let gasPrice;
534
- try {
535
- gasPrice = await this.client.getGasPrice();
536
- } catch {
537
- }
538
+ const [priceHistory, gasPrice] = await Promise.all([
539
+ this.fetchPriceHistory(tokenAddresses).catch(() => void 0),
540
+ this.client.getGasPrice().catch(() => void 0)
541
+ ]);
538
542
  return {
539
543
  timestamp: Date.now(),
540
544
  prices,
@@ -545,7 +549,8 @@ var init_market = __esm({
545
549
  gasPrice,
546
550
  network: {
547
551
  chainId: this.client.chain?.id ?? 8453
548
- }
552
+ },
553
+ priceHistory: priceHistory && Object.keys(priceHistory).length > 0 ? priceHistory : void 0
549
554
  };
550
555
  }
551
556
  /**
@@ -718,6 +723,58 @@ var init_market = __esm({
718
723
  }
719
724
  return prices;
720
725
  }
726
+ /**
727
+ * Fetch 24h hourly price history from CoinGecko.
728
+ * Returns cached data if still fresh (< 60 min old).
729
+ * Only fetches for tokens with known CoinGecko IDs.
730
+ */
731
+ async fetchPriceHistory(tokenAddresses) {
732
+ if (Date.now() - this.lastHistoryFetchAt < HISTORY_STALENESS_MS && Object.keys(this.cachedPriceHistory).length > 0) {
733
+ return { ...this.cachedPriceHistory };
734
+ }
735
+ const history = {};
736
+ const idToAddrs = {};
737
+ for (const addr of tokenAddresses) {
738
+ const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
739
+ if (cgId && !STABLECOIN_IDS.has(cgId)) {
740
+ if (!idToAddrs[cgId]) idToAddrs[cgId] = [];
741
+ idToAddrs[cgId].push(addr.toLowerCase());
742
+ }
743
+ }
744
+ const ids = Object.keys(idToAddrs);
745
+ for (let i = 0; i < ids.length; i++) {
746
+ const cgId = ids[i];
747
+ try {
748
+ const resp = await fetch(
749
+ `https://api.coingecko.com/api/v3/coins/${cgId}/market_chart?vs_currency=usd&days=1`,
750
+ { signal: AbortSignal.timeout(5e3) }
751
+ );
752
+ if (!resp.ok) {
753
+ if (resp.status === 429) {
754
+ console.warn("CoinGecko rate limit hit during price history fetch \u2014 using partial data");
755
+ break;
756
+ }
757
+ continue;
758
+ }
759
+ const data = await resp.json();
760
+ if (data.prices && Array.isArray(data.prices)) {
761
+ const candles = data.prices.map(([ts, price]) => ({ timestamp: ts, price }));
762
+ for (const addr of idToAddrs[cgId]) {
763
+ history[addr] = candles;
764
+ }
765
+ }
766
+ } catch {
767
+ }
768
+ if (i < ids.length - 1) {
769
+ await new Promise((r) => setTimeout(r, 2500));
770
+ }
771
+ }
772
+ if (Object.keys(history).length > 0) {
773
+ this.cachedPriceHistory = { ...this.cachedPriceHistory, ...history };
774
+ this.lastHistoryFetchAt = Date.now();
775
+ }
776
+ return { ...this.cachedPriceHistory };
777
+ }
721
778
  /**
722
779
  * Fetch real on-chain balances: native ETH + ERC-20 tokens.
723
780
  * Uses Multicall3 to batch all balanceOf calls into a single RPC request.
@@ -1552,16 +1609,25 @@ var init_executor = __esm({
1552
1609
  config;
1553
1610
  allowedTokens;
1554
1611
  configHashFn;
1555
- constructor(client, config, configHashFn) {
1612
+ vaultManager;
1613
+ constructor(client, config, configHashFn, vaultManager) {
1556
1614
  this.client = client;
1557
1615
  this.config = config;
1558
1616
  this.configHashFn = configHashFn;
1617
+ this.vaultManager = vaultManager;
1559
1618
  this.allowedTokens = new Set(
1560
1619
  (config.allowedTokens || []).map((t) => t.toLowerCase())
1561
1620
  );
1562
1621
  }
1563
1622
  /**
1564
- * Execute a single trade signal
1623
+ * Set the vault manager (allows wiring after construction)
1624
+ */
1625
+ setVaultManager(vm) {
1626
+ this.vaultManager = vm;
1627
+ }
1628
+ /**
1629
+ * Execute a single trade signal.
1630
+ * Tries vault trading first if vault exists, falls back to wallet trading.
1565
1631
  */
1566
1632
  async execute(signal) {
1567
1633
  if (signal.action === "hold") {
@@ -1574,13 +1640,38 @@ var init_executor = __esm({
1574
1640
  return { success: false, error: "Signal exceeds position limits" };
1575
1641
  }
1576
1642
  const configHash = this.configHashFn?.();
1577
- const result = await this.client.trade({
1643
+ const tradeIntent = {
1578
1644
  tokenIn: signal.tokenIn,
1579
1645
  tokenOut: signal.tokenOut,
1580
1646
  amountIn: signal.amountIn,
1581
1647
  maxSlippageBps: this.config.trading?.maxSlippageBps ?? 100,
1582
1648
  ...configHash && { configHash }
1583
- });
1649
+ };
1650
+ if (this.vaultManager) {
1651
+ const vaultAddress = await this.vaultManager.getVaultAddress();
1652
+ if (vaultAddress && this.vaultManager.preferVaultTrading) {
1653
+ const routerTrade = await this.client.buildRouterTrade(tradeIntent);
1654
+ if (routerTrade.vaultParams) {
1655
+ console.log(`Attempting vault trade via ${vaultAddress}`);
1656
+ const vaultResult = await this.vaultManager.executeVaultTrade({
1657
+ tokenIn: signal.tokenIn,
1658
+ tokenOut: signal.tokenOut,
1659
+ amountIn: BigInt(signal.amountIn),
1660
+ minAmountOut: BigInt(routerTrade.minAmountOut),
1661
+ vaultParams: routerTrade.vaultParams
1662
+ });
1663
+ if (vaultResult) {
1664
+ if (vaultResult.txHash) {
1665
+ console.log(`Vault trade executed: ${vaultResult.txHash}`);
1666
+ return { success: true, txHash: vaultResult.txHash };
1667
+ }
1668
+ console.error(`Vault trade failed: ${vaultResult.error}`);
1669
+ return { success: false, error: vaultResult.error };
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+ const result = await this.client.trade(tradeIntent);
1584
1675
  console.log(`Trade executed: ${result.hash}`);
1585
1676
  return { success: true, txHash: result.hash };
1586
1677
  } catch (error) {
@@ -1609,6 +1700,10 @@ var init_executor = __esm({
1609
1700
  * Validate a signal against config limits and token restrictions
1610
1701
  */
1611
1702
  validateSignal(signal) {
1703
+ if (signal.amountIn <= 0n) {
1704
+ console.warn(`Signal amountIn is ${signal.amountIn} \u2014 skipping (must be positive)`);
1705
+ return false;
1706
+ }
1612
1707
  if (signal.confidence < 0.5) {
1613
1708
  console.warn(`Signal confidence ${signal.confidence} below threshold (0.5)`);
1614
1709
  return false;
@@ -2553,6 +2648,33 @@ function formatSessionReport(result) {
2553
2648
  }
2554
2649
  lines.push("");
2555
2650
  }
2651
+ if (result.trades.length > 0) {
2652
+ lines.push(` ${thinDivider}`);
2653
+ lines.push(" TRADE LOG");
2654
+ lines.push(` ${thinDivider}`);
2655
+ lines.push("");
2656
+ const recentTrades = result.trades.slice(-50);
2657
+ for (const t of recentTrades) {
2658
+ const time = new Date(t.timestamp).toLocaleString(void 0, {
2659
+ month: "short",
2660
+ day: "2-digit",
2661
+ hour: "2-digit",
2662
+ minute: "2-digit"
2663
+ });
2664
+ const tag = t.action === "buy" ? "BUY " : "SELL";
2665
+ const tokenIn = t.tokenIn.slice(0, 6) + "\u2026";
2666
+ const tokenOut = t.tokenOut.slice(0, 6) + "\u2026";
2667
+ const value = `$${t.valueInUSD.toFixed(2)}`;
2668
+ const pnl = t.valueOutUSD - t.valueInUSD - t.feeUSD - t.gasUSD;
2669
+ const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
2670
+ const reason = t.reasoning ? ` "${t.reasoning.slice(0, 60)}"` : "";
2671
+ lines.push(` ${time} ${tag} ${tokenIn}\u2192${tokenOut} ${value} ${pnlStr}${reason}`);
2672
+ }
2673
+ if (result.trades.length > 50) {
2674
+ lines.push(` ... and ${result.trades.length - 50} earlier trades`);
2675
+ }
2676
+ lines.push("");
2677
+ }
2556
2678
  if (result.equityCurve.length >= 3) {
2557
2679
  lines.push(` ${thinDivider}`);
2558
2680
  lines.push(" EQUITY CURVE");
@@ -3886,7 +4008,7 @@ var STRATEGY_TEMPLATES = [
3886
4008
  {
3887
4009
  id: "momentum",
3888
4010
  name: "Momentum Trader",
3889
- description: "Follows price trends and momentum indicators. Buys assets with strong upward momentum.",
4011
+ description: "Follows price trends and momentum indicators. Buys assets with strong upward momentum, sells when momentum fades.",
3890
4012
  riskLevel: "medium",
3891
4013
  riskWarnings: [
3892
4014
  "Momentum strategies can suffer significant losses during trend reversals",
@@ -3894,60 +4016,248 @@ var STRATEGY_TEMPLATES = [
3894
4016
  "Past performance does not guarantee future results",
3895
4017
  "This strategy may underperform in sideways markets"
3896
4018
  ],
3897
- systemPrompt: `You are an AI trading analyst specializing in momentum trading strategies.
4019
+ systemPrompt: `You are an AI trading analyst specializing in momentum trading on Base network.
3898
4020
 
3899
- Your role is to analyze market data and identify momentum-based trading opportunities.
4021
+ Analyze the provided market data and identify momentum-based trading opportunities.
3900
4022
 
3901
- IMPORTANT CONSTRAINTS:
4023
+ RULES:
3902
4024
  - Only recommend trades when there is clear momentum evidence
3903
- - Always consider risk/reward ratios
3904
- - Never recommend more than the configured position size limits
3905
- - Be conservative with confidence scores
4025
+ - Use priceTrends (1h, 4h, 12h, 24h changes) as your primary momentum signal
4026
+ - Fall back to priceChange24h if priceTrends is not available
4027
+ - Be conservative with confidence \u2014 only use > 0.6 for strong signals
4028
+ - Never recommend buying a token you already hold significant amounts of
4029
+ - Always consider selling positions that have lost momentum
4030
+ - Return "hold" signals when uncertain \u2014 doing nothing is often the best trade
3906
4031
 
3907
- When analyzing data, look for:
3908
- 1. Price trends (higher highs, higher lows for uptrends)
3909
- 2. Volume confirmation (increasing volume on moves)
3910
- 3. Relative strength vs market benchmarks
4032
+ ANALYZE:
4033
+ 1. Multi-timeframe momentum: use priceTrends (1h, 4h, 12h, 24h) to confirm trend direction
4034
+ 2. Strong momentum = positive across all timeframes. Divergence (e.g., 1h negative, 12h positive) = weakening
4035
+ 3. Volume confirmation: prefer tokens with > $10K daily volume
4036
+ 4. Portfolio concentration: avoid putting > 20% in any single token
4037
+ 5. Exit signals: sell when short-term trend (1h, 4h) turns negative on a held position
3911
4038
 
3912
- Respond with JSON in this format:
4039
+ Respond with ONLY valid JSON (no markdown, no code fences):
3913
4040
  {
3914
- "analysis": "Brief market analysis",
4041
+ "analysis": "Brief market analysis (1-2 sentences)",
3915
4042
  "signals": [
3916
4043
  {
3917
4044
  "action": "buy" | "sell" | "hold",
3918
- "tokenIn": "0x...",
3919
- "tokenOut": "0x...",
3920
- "percentage": 0-100,
3921
- "confidence": 0-1,
3922
- "reasoning": "Why this trade"
4045
+ "token": "0x... (token address to buy/sell)",
4046
+ "percentage": 5-25,
4047
+ "confidence": 0.0-1.0,
4048
+ "reasoning": "Why this trade (1 sentence)"
3923
4049
  }
3924
4050
  ]
3925
- }`,
3926
- exampleCode: `import { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig } from '@exagent/agent';
4051
+ }
4052
+
4053
+ If no good opportunities exist, return: { "analysis": "No clear momentum signals", "signals": [] }`,
4054
+ exampleCode: `import type { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig, StrategyContext } from '@exagent/agent';
4055
+
4056
+ // Well-known token addresses on Base
4057
+ const WETH = '0x4200000000000000000000000000000000000006';
4058
+ const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
3927
4059
 
4060
+ // Token decimals lookup (extend as needed)
4061
+ const DECIMALS: Record<string, number> = {
4062
+ [WETH]: 18,
4063
+ [USDC]: 6,
4064
+ };
4065
+
4066
+ /**
4067
+ * Format balances for LLM consumption (human-readable amounts)
4068
+ */
4069
+ function formatBalances(balances: Record<string, bigint>, prices: Record<string, number>): Record<string, { amount: string; usdValue: string }> {
4070
+ const result: Record<string, { amount: string; usdValue: string }> = {};
4071
+ for (const [token, balance] of Object.entries(balances)) {
4072
+ if (balance === 0n) continue;
4073
+ const decimals = DECIMALS[token.toLowerCase()] ?? 18;
4074
+ const amount = Number(balance) / (10 ** decimals);
4075
+ const price = prices[token.toLowerCase()] ?? 0;
4076
+ result[token] = {
4077
+ amount: amount.toFixed(decimals <= 8 ? decimals : 6),
4078
+ usdValue: (amount * price).toFixed(2),
4079
+ };
4080
+ }
4081
+ return result;
4082
+ }
4083
+
4084
+ /**
4085
+ * Convert LLM percentage signal to a TradeSignal with bigint amountIn
4086
+ *
4087
+ * The LLM says "buy token X with 10% of my ETH" \u2014 this function converts
4088
+ * that percentage into the actual wei amount to trade.
4089
+ */
4090
+ function toTradeSignal(
4091
+ signal: { action: string; token: string; percentage: number; confidence: number; reasoning?: string },
4092
+ balances: Record<string, bigint>,
4093
+ ): TradeSignal | null {
4094
+ const { action, token, percentage, confidence, reasoning } = signal;
4095
+
4096
+ // Skip hold signals and low confidence
4097
+ if (action === 'hold' || confidence < 0.5) return null;
4098
+
4099
+ // Clamp percentage to 1-50 range for safety
4100
+ const pct = Math.max(1, Math.min(50, percentage));
4101
+
4102
+ if (action === 'buy') {
4103
+ // Buying: spend ETH (or USDC) to acquire the target token
4104
+ // Use ETH balance as the source
4105
+ const ethBalance = balances[WETH] ?? 0n;
4106
+ if (ethBalance === 0n) return null;
4107
+ const amountIn = (ethBalance * BigInt(Math.round(pct * 100))) / 10000n;
4108
+ if (amountIn === 0n) return null;
4109
+ return { action: 'buy', tokenIn: WETH, tokenOut: token, amountIn, confidence, reasoning };
4110
+ }
4111
+
4112
+ if (action === 'sell') {
4113
+ // Selling: sell the target token for ETH
4114
+ const tokenBalance = balances[token.toLowerCase()] ?? balances[token] ?? 0n;
4115
+ if (tokenBalance === 0n) return null;
4116
+ const amountIn = (tokenBalance * BigInt(Math.round(pct * 100))) / 10000n;
4117
+ if (amountIn === 0n) return null;
4118
+ return { action: 'sell', tokenIn: token, tokenOut: WETH, amountIn, confidence, reasoning };
4119
+ }
4120
+
4121
+ return null;
4122
+ }
4123
+
4124
+ /**
4125
+ * Momentum Trading Strategy
4126
+ *
4127
+ * Sends market data to the LLM, parses the response, and converts
4128
+ * percentage-based signals into executable TradeSignal objects.
4129
+ */
3928
4130
  export const generateSignals: StrategyFunction = async (
3929
4131
  marketData: MarketData,
3930
4132
  llm: LLMAdapter,
3931
- config: AgentConfig
4133
+ config: AgentConfig,
4134
+ context?: StrategyContext,
3932
4135
  ): Promise<TradeSignal[]> => {
4136
+ // Summarize price history into compact trend data for the LLM
4137
+ // Instead of sending 24 raw candles per token, compute useful signals:
4138
+ // - 1h, 4h, 12h, 24h price changes (momentum at different timeframes)
4139
+ const priceTrends: Record<string, { '1h': string; '4h': string; '12h': string; '24h': string }> = {};
4140
+ if (marketData.priceHistory) {
4141
+ for (const [addr, candles] of Object.entries(marketData.priceHistory)) {
4142
+ if (candles.length < 2) continue;
4143
+ const latest = candles[candles.length - 1].price;
4144
+ const getChangeAt = (hoursAgo: number): string => {
4145
+ const target = Date.now() - hoursAgo * 3600_000;
4146
+ // Find the candle closest to the target time
4147
+ let closest = candles[0];
4148
+ for (const c of candles) {
4149
+ if (Math.abs(c.timestamp - target) < Math.abs(closest.timestamp - target)) closest = c;
4150
+ }
4151
+ if (closest.price === 0) return '0.0%';
4152
+ const pct = ((latest - closest.price) / closest.price) * 100;
4153
+ return (pct >= 0 ? '+' : '') + pct.toFixed(1) + '%';
4154
+ };
4155
+ priceTrends[addr] = {
4156
+ '1h': getChangeAt(1),
4157
+ '4h': getChangeAt(4),
4158
+ '12h': getChangeAt(12),
4159
+ '24h': getChangeAt(24),
4160
+ };
4161
+ }
4162
+ }
4163
+
4164
+ // Build the data payload for the LLM
4165
+ const payload: Record<string, unknown> = {
4166
+ prices: marketData.prices,
4167
+ balances: formatBalances(marketData.balances, marketData.prices),
4168
+ portfolioValueUSD: marketData.portfolioValue.toFixed(2),
4169
+ priceChange24h: marketData.priceChange24h ?? {},
4170
+ volume24h: marketData.volume24h ?? {},
4171
+ recentTrades: (context?.tradeHistory ?? []).slice(0, 5).map(t => ({
4172
+ action: t.action,
4173
+ token: t.action === 'buy' ? t.tokenOut : t.tokenIn,
4174
+ reasoning: t.reasoning,
4175
+ timestamp: new Date(t.timestamp).toISOString(),
4176
+ })),
4177
+ };
4178
+
4179
+ // Include price trends if available (more useful than raw candles for LLM)
4180
+ if (Object.keys(priceTrends).length > 0) {
4181
+ payload.priceTrends = priceTrends;
4182
+ }
4183
+
4184
+ // Call the LLM
3933
4185
  const response = await llm.chat([
3934
- { role: 'system', content: MOMENTUM_SYSTEM_PROMPT },
3935
- { role: 'user', content: JSON.stringify({
3936
- prices: marketData.prices,
3937
- balances: formatBalances(marketData.balances),
3938
- portfolioValue: marketData.portfolioValue,
3939
- })}
4186
+ { role: 'system', content: config.strategyPrompt ?? MOMENTUM_SYSTEM_PROMPT },
4187
+ { role: 'user', content: JSON.stringify(payload) },
3940
4188
  ]);
3941
4189
 
3942
- // Parse LLM response and convert to TradeSignals
3943
- const parsed = JSON.parse(response.content);
3944
- return parsed.signals.map(convertToTradeSignal);
3945
- };`
4190
+ // Parse the response \u2014 handle markdown code fences if the LLM wraps its output
4191
+ let content = response.content.trim();
4192
+ if (content.startsWith('\`\`\`')) {
4193
+ content = content.replace(/^\`\`\`(?:json)?\\n?/, '').replace(/\\n?\`\`\`$/, '').trim();
4194
+ }
4195
+
4196
+ let parsed: { signals?: Array<{ action: string; token: string; percentage: number; confidence: number; reasoning?: string }> };
4197
+ try {
4198
+ parsed = JSON.parse(content);
4199
+ } catch (e) {
4200
+ console.error('[strategy] Failed to parse LLM response:', (e as Error).message);
4201
+ console.error('[strategy] Raw response:', content.slice(0, 200));
4202
+ return []; // Safe fallback: no trades
4203
+ }
4204
+
4205
+ if (!parsed.signals || !Array.isArray(parsed.signals)) {
4206
+ return [];
4207
+ }
4208
+
4209
+ // Convert each signal to a TradeSignal with proper bigint amountIn
4210
+ const signals: TradeSignal[] = [];
4211
+ for (const raw of parsed.signals) {
4212
+ const signal = toTradeSignal(raw, marketData.balances);
4213
+ if (signal) signals.push(signal);
4214
+ }
4215
+
4216
+ return signals;
4217
+ };
4218
+
4219
+ // Default system prompt \u2014 used when config.strategyPrompt is not set
4220
+ const MOMENTUM_SYSTEM_PROMPT = \`You are an AI trading analyst specializing in momentum trading on Base network.
4221
+
4222
+ Analyze the provided market data and identify momentum-based trading opportunities.
4223
+
4224
+ RULES:
4225
+ - Only recommend trades when there is clear momentum evidence
4226
+ - Use priceTrends (1h, 4h, 12h, 24h changes) as your primary momentum signal
4227
+ - Fall back to priceChange24h if priceTrends is not available
4228
+ - Be conservative with confidence \u2014 only use > 0.6 for strong signals
4229
+ - Never recommend buying a token you already hold significant amounts of
4230
+ - Always consider selling positions that have lost momentum
4231
+ - Return "hold" signals when uncertain \u2014 doing nothing is often the best trade
4232
+
4233
+ ANALYZE:
4234
+ 1. Multi-timeframe momentum: use priceTrends (1h, 4h, 12h, 24h) to confirm trend direction
4235
+ 2. Strong momentum = positive across all timeframes. Divergence (e.g., 1h negative, 12h positive) = weakening
4236
+ 3. Volume confirmation: prefer tokens with > $10K daily volume
4237
+ 4. Portfolio concentration: avoid putting > 20% in any single token
4238
+ 5. Exit signals: sell when short-term trend (1h, 4h) turns negative on a held position
4239
+
4240
+ Respond with ONLY valid JSON (no markdown, no code fences):
4241
+ {
4242
+ "analysis": "Brief market analysis (1-2 sentences)",
4243
+ "signals": [
4244
+ {
4245
+ "action": "buy" | "sell" | "hold",
4246
+ "token": "0x... (token address to buy/sell)",
4247
+ "percentage": 5-25,
4248
+ "confidence": 0.0-1.0,
4249
+ "reasoning": "Why this trade (1 sentence)"
4250
+ }
4251
+ ]
4252
+ }
4253
+
4254
+ If no good opportunities exist, return: { "analysis": "No clear momentum signals", "signals": [] }\`;
4255
+ `
3946
4256
  },
3947
4257
  {
3948
4258
  id: "value",
3949
4259
  name: "Value Investor",
3950
- description: "Looks for undervalued assets based on fundamentals. Takes long-term positions.",
4260
+ description: "Looks for undervalued assets based on fundamentals. Takes long-term positions with lower turnover.",
3951
4261
  riskLevel: "low",
3952
4262
  riskWarnings: [
3953
4263
  "Value traps can result in prolonged losses",
@@ -3955,47 +4265,135 @@ export const generateSignals: StrategyFunction = async (
3955
4265
  "Fundamental analysis may not apply well to all crypto assets",
3956
4266
  "Market sentiment can override fundamentals for long periods"
3957
4267
  ],
3958
- systemPrompt: `You are an AI trading analyst specializing in value investing.
4268
+ systemPrompt: `You are an AI trading analyst specializing in value investing on Base network.
3959
4269
 
3960
4270
  Your role is to identify undervalued assets with strong fundamentals.
3961
4271
 
3962
- IMPORTANT CONSTRAINTS:
4272
+ RULES:
3963
4273
  - Focus on long-term value, not short-term price movements
3964
4274
  - Only recommend assets with clear value propositions
3965
4275
  - Consider protocol revenue, TVL, active users, developer activity
3966
- - Be very selective - quality over quantity
3967
-
3968
- When analyzing, consider:
3969
- 1. Protocol fundamentals (revenue, TVL, user growth)
3970
- 2. Token economics (supply schedule, utility)
3971
- 3. Competitive positioning
3972
- 4. Valuation relative to peers
4276
+ - Be very selective \u2014 quality over quantity
4277
+ - Prefer established tokens (AERO, WELL, MORPHO, COMP, CRV) over memecoins
4278
+ - Hold positions for days/weeks, not hours
3973
4279
 
3974
- Respond with JSON in this format:
4280
+ Respond with ONLY valid JSON:
3975
4281
  {
3976
4282
  "analysis": "Brief fundamental analysis",
3977
4283
  "signals": [
3978
4284
  {
3979
4285
  "action": "buy" | "sell" | "hold",
3980
- "tokenIn": "0x...",
3981
- "tokenOut": "0x...",
3982
- "percentage": 0-100,
3983
- "confidence": 0-1,
4286
+ "token": "0x... (token address)",
4287
+ "percentage": 5-20,
4288
+ "confidence": 0.0-1.0,
3984
4289
  "reasoning": "Fundamental thesis"
3985
4290
  }
3986
4291
  ]
3987
4292
  }`,
3988
- exampleCode: `import { StrategyFunction } from '@exagent/agent';
4293
+ exampleCode: `import type { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig, StrategyContext } from '@exagent/agent';
4294
+
4295
+ const WETH = '0x4200000000000000000000000000000000000006';
4296
+ const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
4297
+ const DECIMALS: Record<string, number> = { [WETH]: 18, [USDC]: 6 };
4298
+
4299
+ function formatBalances(balances: Record<string, bigint>, prices: Record<string, number>): Record<string, { amount: string; usdValue: string }> {
4300
+ const result: Record<string, { amount: string; usdValue: string }> = {};
4301
+ for (const [token, balance] of Object.entries(balances)) {
4302
+ if (balance === 0n) continue;
4303
+ const decimals = DECIMALS[token.toLowerCase()] ?? 18;
4304
+ const amount = Number(balance) / (10 ** decimals);
4305
+ const price = prices[token.toLowerCase()] ?? 0;
4306
+ result[token] = { amount: amount.toFixed(6), usdValue: (amount * price).toFixed(2) };
4307
+ }
4308
+ return result;
4309
+ }
4310
+
4311
+ function toTradeSignal(
4312
+ signal: { action: string; token: string; percentage: number; confidence: number; reasoning?: string },
4313
+ balances: Record<string, bigint>,
4314
+ ): TradeSignal | null {
4315
+ if (signal.action === 'hold' || signal.confidence < 0.5) return null;
4316
+ const pct = Math.max(1, Math.min(50, signal.percentage));
4317
+ if (signal.action === 'buy') {
4318
+ const ethBalance = balances[WETH] ?? 0n;
4319
+ if (ethBalance === 0n) return null;
4320
+ const amountIn = (ethBalance * BigInt(Math.round(pct * 100))) / 10000n;
4321
+ if (amountIn === 0n) return null;
4322
+ return { action: 'buy', tokenIn: WETH, tokenOut: signal.token, amountIn, confidence: signal.confidence, reasoning: signal.reasoning };
4323
+ }
4324
+ if (signal.action === 'sell') {
4325
+ const tokenBalance = balances[signal.token.toLowerCase()] ?? balances[signal.token] ?? 0n;
4326
+ if (tokenBalance === 0n) return null;
4327
+ const amountIn = (tokenBalance * BigInt(Math.round(pct * 100))) / 10000n;
4328
+ if (amountIn === 0n) return null;
4329
+ return { action: 'sell', tokenIn: signal.token, tokenOut: WETH, amountIn, confidence: signal.confidence, reasoning: signal.reasoning };
4330
+ }
4331
+ return null;
4332
+ }
4333
+
4334
+ export const generateSignals: StrategyFunction = async (
4335
+ marketData: MarketData,
4336
+ llm: LLMAdapter,
4337
+ config: AgentConfig,
4338
+ context?: StrategyContext,
4339
+ ): Promise<TradeSignal[]> => {
4340
+ const payload = {
4341
+ prices: marketData.prices,
4342
+ balances: formatBalances(marketData.balances, marketData.prices),
4343
+ portfolioValueUSD: marketData.portfolioValue.toFixed(2),
4344
+ priceChange24h: marketData.priceChange24h ?? {},
4345
+ positions: (context?.positions ?? []).map(p => ({
4346
+ token: p.tokenAddress,
4347
+ entryPrice: p.averageEntryPrice,
4348
+ currentAmount: p.currentAmount.toString(),
4349
+ })),
4350
+ };
3989
4351
 
3990
- export const generateSignals: StrategyFunction = async (marketData, llm, config) => {
3991
- // Value strategy runs less frequently
3992
4352
  const response = await llm.chat([
3993
- { role: 'system', content: VALUE_SYSTEM_PROMPT },
3994
- { role: 'user', content: JSON.stringify(marketData) }
4353
+ { role: 'system', content: config.strategyPrompt ?? VALUE_SYSTEM_PROMPT },
4354
+ { role: 'user', content: JSON.stringify(payload) },
3995
4355
  ]);
3996
4356
 
3997
- return parseSignals(response.content);
3998
- };`
4357
+ let content = response.content.trim();
4358
+ if (content.startsWith('\\\`\\\`\\\`')) {
4359
+ content = content.replace(/^\\\`\\\`\\\`(?:json)?\\n?/, '').replace(/\\n?\\\`\\\`\\\`$/, '').trim();
4360
+ }
4361
+
4362
+ try {
4363
+ const parsed = JSON.parse(content);
4364
+ if (!parsed.signals || !Array.isArray(parsed.signals)) return [];
4365
+ return parsed.signals.map((s: any) => toTradeSignal(s, marketData.balances)).filter(Boolean) as TradeSignal[];
4366
+ } catch (e) {
4367
+ console.error('[strategy] Failed to parse LLM response:', (e as Error).message);
4368
+ return [];
4369
+ }
4370
+ };
4371
+
4372
+ const VALUE_SYSTEM_PROMPT = \`You are an AI trading analyst specializing in value investing on Base network.
4373
+
4374
+ Your role is to identify undervalued assets with strong fundamentals.
4375
+
4376
+ RULES:
4377
+ - Focus on long-term value, not short-term price movements
4378
+ - Only recommend assets with clear value propositions
4379
+ - Be very selective \u2014 quality over quantity
4380
+ - Prefer established tokens over memecoins
4381
+ - Hold positions for days/weeks, not hours
4382
+
4383
+ Respond with ONLY valid JSON:
4384
+ {
4385
+ "analysis": "Brief fundamental analysis",
4386
+ "signals": [
4387
+ {
4388
+ "action": "buy" | "sell" | "hold",
4389
+ "token": "0x... (token address)",
4390
+ "percentage": 5-20,
4391
+ "confidence": 0.0-1.0,
4392
+ "reasoning": "Fundamental thesis"
4393
+ }
4394
+ ]
4395
+ }\`;
4396
+ `
3999
4397
  },
4000
4398
  {
4001
4399
  id: "arbitrage",
@@ -4035,7 +4433,7 @@ Respond with JSON in this format:
4035
4433
  exampleCode: `// Note: Pure arbitrage requires specialized infrastructure
4036
4434
  // This template is for educational purposes
4037
4435
 
4038
- import { StrategyFunction } from '@exagent/agent';
4436
+ import type { StrategyFunction } from '@exagent/agent';
4039
4437
 
4040
4438
  export const generateSignals: StrategyFunction = async (marketData, llm, config) => {
4041
4439
  // Arbitrage requires real-time price feeds from multiple sources
@@ -4067,18 +4465,21 @@ Respond with JSON:
4067
4465
  {
4068
4466
  "signals": []
4069
4467
  }`,
4070
- exampleCode: `import { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig } from '@exagent/agent';
4468
+ exampleCode: `import type { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig, StrategyContext } from '@exagent/agent';
4469
+
4470
+ const WETH = '0x4200000000000000000000000000000000000006';
4071
4471
 
4072
4472
  /**
4073
4473
  * Custom Strategy Template
4074
4474
  *
4075
4475
  * Customize this file with your own trading logic and prompts.
4076
- * Your prompts are YOUR intellectual property - we don't store them.
4476
+ * Your prompts are YOUR intellectual property \u2014 we don't store them.
4077
4477
  */
4078
4478
  export const generateSignals: StrategyFunction = async (
4079
4479
  marketData: MarketData,
4080
4480
  llm: LLMAdapter,
4081
- config: AgentConfig
4481
+ config: AgentConfig,
4482
+ context?: StrategyContext,
4082
4483
  ): Promise<TradeSignal[]> => {
4083
4484
  // Your custom system prompt (this is your secret sauce)
4084
4485
  const systemPrompt = \`
@@ -4088,13 +4489,26 @@ export const generateSignals: StrategyFunction = async (
4088
4489
  // Call the LLM with your prompt
4089
4490
  const response = await llm.chat([
4090
4491
  { role: 'system', content: systemPrompt },
4091
- { role: 'user', content: JSON.stringify(marketData) }
4492
+ { role: 'user', content: JSON.stringify({
4493
+ prices: marketData.prices,
4494
+ balances: Object.fromEntries(
4495
+ Object.entries(marketData.balances)
4496
+ .filter(([, v]) => v > 0n)
4497
+ .map(([k, v]) => [k, v.toString()])
4498
+ ),
4499
+ portfolioValue: marketData.portfolioValue,
4500
+ })},
4092
4501
  ]);
4093
4502
 
4094
4503
  // Parse and return signals
4095
4504
  // IMPORTANT: Validate LLM output before using
4505
+ let content = response.content.trim();
4506
+ if (content.startsWith('\\\`\\\`\\\`')) {
4507
+ content = content.replace(/^\\\`\\\`\\\`(?:json)?\\n?/, '').replace(/\\n?\\\`\\\`\\\`$/, '').trim();
4508
+ }
4509
+
4096
4510
  try {
4097
- const parsed = JSON.parse(response.content);
4511
+ const parsed = JSON.parse(content);
4098
4512
  return parsed.signals || [];
4099
4513
  } catch (e) {
4100
4514
  console.error('Failed to parse LLM response:', e);
@@ -4103,6 +4517,9 @@ export const generateSignals: StrategyFunction = async (
4103
4517
  };`
4104
4518
  }
4105
4519
  ];
4520
+ function getStrategyTemplate(id) {
4521
+ return STRATEGY_TEMPLATES.find((t) => t.id === id);
4522
+ }
4106
4523
  function getAllStrategyTemplates() {
4107
4524
  return STRATEGY_TEMPLATES;
4108
4525
  }
@@ -4250,6 +4667,8 @@ var AgentConfigSchema = import_zod.z.object({
4250
4667
  perp: PerpConfigSchema,
4251
4668
  // Prediction market configuration (Polymarket)
4252
4669
  prediction: PredictionConfigSchema,
4670
+ // Custom strategy system prompt (overrides built-in template prompt)
4671
+ strategyPrompt: import_zod.z.string().optional(),
4253
4672
  // Allowed tokens (addresses)
4254
4673
  allowedTokens: import_zod.z.array(import_zod.z.string()).optional()
4255
4674
  });
@@ -4388,9 +4807,10 @@ var VAULT_ABI = [
4388
4807
  outputs: [{ type: "uint256" }],
4389
4808
  stateMutability: "view"
4390
4809
  },
4810
+ // TOKEN → base asset (fee from output)
4391
4811
  {
4392
4812
  type: "function",
4393
- name: "executeTrade",
4813
+ name: "executeSwap",
4394
4814
  inputs: [
4395
4815
  { name: "tokenIn", type: "address" },
4396
4816
  { name: "tokenOut", type: "address" },
@@ -4398,6 +4818,44 @@ var VAULT_ABI = [
4398
4818
  { name: "minAmountOut", type: "uint256" },
4399
4819
  { name: "aggregator", type: "address" },
4400
4820
  { name: "swapData", type: "bytes" },
4821
+ { name: "feeConvertAggregator", type: "address" },
4822
+ { name: "feeSwapData", type: "bytes" },
4823
+ { name: "deadline", type: "uint256" }
4824
+ ],
4825
+ outputs: [{ type: "uint256" }],
4826
+ stateMutability: "nonpayable"
4827
+ },
4828
+ // Base asset → TOKEN (fee from input)
4829
+ {
4830
+ type: "function",
4831
+ name: "executeSwapSimple",
4832
+ inputs: [
4833
+ { name: "tokenIn", type: "address" },
4834
+ { name: "tokenOut", type: "address" },
4835
+ { name: "amountIn", type: "uint256" },
4836
+ { name: "minAmountOut", type: "uint256" },
4837
+ { name: "target", type: "address" },
4838
+ { name: "data", type: "bytes" },
4839
+ { name: "feeConvertAggregator", type: "address" },
4840
+ { name: "feeSwapData", type: "bytes" },
4841
+ { name: "deadline", type: "uint256" }
4842
+ ],
4843
+ outputs: [{ type: "uint256" }],
4844
+ stateMutability: "nonpayable"
4845
+ },
4846
+ // TOKEN → TOKEN (multi-hop via USDC, fee at midpoint)
4847
+ {
4848
+ type: "function",
4849
+ name: "executeSwapMultiHop",
4850
+ inputs: [
4851
+ { name: "tokenIn", type: "address" },
4852
+ { name: "tokenOut", type: "address" },
4853
+ { name: "amountIn", type: "uint256" },
4854
+ { name: "minAmountOut", type: "uint256" },
4855
+ { name: "aggregator1", type: "address" },
4856
+ { name: "swapData1", type: "bytes" },
4857
+ { name: "aggregator2", type: "address" },
4858
+ { name: "swapData2", type: "bytes" },
4401
4859
  { name: "deadline", type: "uint256" }
4402
4860
  ],
4403
4861
  outputs: [{ type: "uint256" }],
@@ -4663,8 +5121,9 @@ var VaultManager = class {
4663
5121
  }
4664
5122
  }
4665
5123
  /**
4666
- * Execute a trade through the vault (if it exists and policy allows)
4667
- * Returns null if should use direct trading instead
5124
+ * Execute a trade through the vault using API-provided vaultParams.
5125
+ * Dispatches to the correct vault function based on swapType.
5126
+ * Returns null if vault trading shouldn't be used (no vault, policy disabled).
4668
5127
  */
4669
5128
  async executeVaultTrade(params) {
4670
5129
  if (!this.preferVaultTrading) {
@@ -4675,23 +5134,71 @@ var VaultManager = class {
4675
5134
  return null;
4676
5135
  }
4677
5136
  const deadline = params.deadline || BigInt(Math.floor(Date.now() / 1e3) + 3600);
5137
+ const { vaultParams } = params;
4678
5138
  try {
4679
- const hash = await this.walletClient.writeContract({
4680
- address: vaultAddress,
4681
- abi: VAULT_ABI,
4682
- functionName: "executeTrade",
4683
- args: [
4684
- params.tokenIn,
4685
- params.tokenOut,
4686
- params.amountIn,
4687
- params.minAmountOut,
4688
- params.aggregator,
4689
- params.swapData,
4690
- deadline
4691
- ],
4692
- chain: import_chains2.base,
4693
- account: this.account
4694
- });
5139
+ let hash;
5140
+ switch (vaultParams.swapType) {
5141
+ case "swap":
5142
+ hash = await this.walletClient.writeContract({
5143
+ address: vaultAddress,
5144
+ abi: VAULT_ABI,
5145
+ functionName: "executeSwap",
5146
+ args: [
5147
+ params.tokenIn,
5148
+ params.tokenOut,
5149
+ params.amountIn,
5150
+ params.minAmountOut,
5151
+ vaultParams.aggregator,
5152
+ vaultParams.swapData,
5153
+ vaultParams.feeConvertAggregator,
5154
+ vaultParams.feeSwapData,
5155
+ deadline
5156
+ ],
5157
+ chain: import_chains2.base,
5158
+ account: this.account
5159
+ });
5160
+ break;
5161
+ case "simple":
5162
+ hash = await this.walletClient.writeContract({
5163
+ address: vaultAddress,
5164
+ abi: VAULT_ABI,
5165
+ functionName: "executeSwapSimple",
5166
+ args: [
5167
+ params.tokenIn,
5168
+ params.tokenOut,
5169
+ params.amountIn,
5170
+ params.minAmountOut,
5171
+ vaultParams.target,
5172
+ vaultParams.data,
5173
+ vaultParams.feeConvertAggregator,
5174
+ vaultParams.feeSwapData,
5175
+ deadline
5176
+ ],
5177
+ chain: import_chains2.base,
5178
+ account: this.account
5179
+ });
5180
+ break;
5181
+ case "multihop":
5182
+ hash = await this.walletClient.writeContract({
5183
+ address: vaultAddress,
5184
+ abi: VAULT_ABI,
5185
+ functionName: "executeSwapMultiHop",
5186
+ args: [
5187
+ params.tokenIn,
5188
+ params.tokenOut,
5189
+ params.amountIn,
5190
+ params.minAmountOut,
5191
+ vaultParams.aggregator1,
5192
+ vaultParams.swapData1,
5193
+ vaultParams.aggregator2,
5194
+ vaultParams.swapData2,
5195
+ deadline
5196
+ ],
5197
+ chain: import_chains2.base,
5198
+ account: this.account
5199
+ });
5200
+ break;
5201
+ }
4695
5202
  return { usedVault: true, txHash: hash };
4696
5203
  } catch (error) {
4697
5204
  return {
@@ -7949,7 +8456,7 @@ function loadSecureEnv(basePath, passphrase) {
7949
8456
  }
7950
8457
 
7951
8458
  // src/index.ts
7952
- var AGENT_VERSION = "0.1.46";
8459
+ var AGENT_VERSION = "0.1.48";
7953
8460
 
7954
8461
  // src/relay.ts
7955
8462
  var RelayClient = class {
@@ -8410,10 +8917,16 @@ var AgentRuntime = class {
8410
8917
  vaultConfig
8411
8918
  });
8412
8919
  console.log(`Vault policy: ${vaultConfig.policy}`);
8920
+ if (this.executor) {
8921
+ this.executor.setVaultManager(this.vaultManager);
8922
+ }
8413
8923
  const status = await this.vaultManager.getVaultStatus();
8414
8924
  if (status.hasVault) {
8415
8925
  console.log(`Vault exists: ${status.vaultAddress}`);
8416
8926
  console.log(`Vault TVL: ${Number(status.totalAssets) / 1e6} USDC`);
8927
+ if (vaultConfig.preferVaultTrading) {
8928
+ console.log("Vault trading enabled \u2014 trades will execute from vault");
8929
+ }
8417
8930
  } else {
8418
8931
  console.log("No vault exists for this agent");
8419
8932
  if (vaultConfig.policy === "manual") {
@@ -9834,8 +10347,9 @@ This exit has been recorded in your trade history and counts toward your total P
9834
10347
  this.paperPortfolio.save();
9835
10348
  } else {
9836
10349
  const vaultStatus = await this.vaultManager?.getVaultStatus();
9837
- if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
9838
- console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
10350
+ if (vaultStatus?.hasVault) {
10351
+ const via = this.vaultManager?.preferVaultTrading ? "vault" : "wallet";
10352
+ console.log(`Vault active: ${vaultStatus.vaultAddress} (trades execute from ${via})`);
9839
10353
  }
9840
10354
  const results = await this.executor.executeAll(filteredSignals);
9841
10355
  let totalFeesUSD = 0;
@@ -10659,6 +11173,95 @@ ${llmEnvVar}EXAGENT_LLM_MODEL=${config.llm?.model || ""}
10659
11173
  (0, import_dotenv2.config)({ path: envPath, override: true });
10660
11174
  }
10661
11175
  }
11176
+ program.command("init").description("Scaffold a new agent project with strategy template and config").option("-t, --template <template>", "Strategy template (momentum, value, arbitrage, custom)", "momentum").option("--force", "Overwrite existing files without prompting").action(async (options) => {
11177
+ try {
11178
+ const templateId = options.template;
11179
+ const template = getStrategyTemplate(templateId);
11180
+ if (!template) {
11181
+ const templates = getAllStrategyTemplates();
11182
+ console.error(`Unknown template: ${templateId}`);
11183
+ console.error(`Available templates: ${templates.map((t) => t.id).join(", ")}`);
11184
+ process.exit(1);
11185
+ }
11186
+ console.log("");
11187
+ console.log("=".repeat(50));
11188
+ console.log(" EXAGENT INIT");
11189
+ console.log("=".repeat(50));
11190
+ console.log("");
11191
+ console.log(` Template: ${template.name}`);
11192
+ console.log(` Risk: ${template.riskLevel}`);
11193
+ console.log("");
11194
+ const cwd = process.cwd();
11195
+ const strategyPath = path7.join(cwd, "strategy.ts");
11196
+ const configPath = path7.join(cwd, "agent-config.json");
11197
+ const existingFiles = [];
11198
+ if (fs6.existsSync(strategyPath)) existingFiles.push("strategy.ts");
11199
+ if (fs6.existsSync(configPath)) existingFiles.push("agent-config.json");
11200
+ if (existingFiles.length > 0 && !options.force) {
11201
+ console.log(` Files already exist: ${existingFiles.join(", ")}`);
11202
+ const answer = await prompt(" Overwrite? (y/n): ");
11203
+ if (answer.toLowerCase() !== "y") {
11204
+ console.log(" Aborted.");
11205
+ process.exit(0);
11206
+ }
11207
+ console.log("");
11208
+ }
11209
+ fs6.writeFileSync(strategyPath, template.exampleCode.trim() + "\n");
11210
+ console.log(" Created: strategy.ts");
11211
+ const defaultConfig = {
11212
+ agentId: 0,
11213
+ name: "my-agent",
11214
+ network: "mainnet",
11215
+ llm: {
11216
+ provider: "openai",
11217
+ model: "gpt-4o",
11218
+ temperature: 0.7,
11219
+ maxTokens: 4096
11220
+ },
11221
+ riskUniverse: "established",
11222
+ trading: {
11223
+ timeHorizon: "swing",
11224
+ maxPositionSizeBps: 1e3,
11225
+ maxDailyLossBps: 500,
11226
+ maxConcurrentPositions: 5,
11227
+ tradingIntervalMs: 6e4,
11228
+ maxSlippageBps: 100,
11229
+ minTradeValueUSD: 1
11230
+ },
11231
+ wallet: {
11232
+ setup: "generate"
11233
+ },
11234
+ relay: {
11235
+ enabled: true,
11236
+ apiUrl: "https://exagent-api.onrender.com"
11237
+ }
11238
+ };
11239
+ if (!fs6.existsSync(configPath) || options.force || existingFiles.includes("agent-config.json")) {
11240
+ fs6.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n");
11241
+ console.log(" Created: agent-config.json");
11242
+ }
11243
+ console.log("");
11244
+ console.log("=".repeat(50));
11245
+ console.log(" NEXT STEPS");
11246
+ console.log("=".repeat(50));
11247
+ console.log("");
11248
+ console.log(" 1. Register your agent at https://exagent.io/deploy");
11249
+ console.log(" 2. Copy your agent ID into agent-config.json");
11250
+ console.log(" 3. Update agent-config.json with your name and LLM provider");
11251
+ console.log(" 4. Run: npx @exagent/agent paper");
11252
+ console.log(" (paper trade first to validate your strategy)");
11253
+ console.log("");
11254
+ console.log(" 5. When ready for real trading:");
11255
+ console.log(" npx @exagent/agent run");
11256
+ console.log("");
11257
+ console.log("=".repeat(50));
11258
+ console.log("");
11259
+ process.exit(0);
11260
+ } catch (error) {
11261
+ console.error("Error:", error instanceof Error ? error.message : error);
11262
+ process.exit(1);
11263
+ }
11264
+ });
10662
11265
  program.command("run").description("Start the trading agent").option("-c, --config <path>", "Path to agent-config.json", "agent-config.json").option("-p, --passphrase <passphrase>", "Passphrase to decrypt .env.enc").action(async (options) => {
10663
11266
  try {
10664
11267
  await checkFirstRunSetup(options.config);