@exagent/agent 0.3.5 → 0.3.7
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-7UGLJO6W.js +6392 -0
- package/dist/chunk-EHAOPCTJ.js +6406 -0
- package/dist/chunk-FGMXTW5I.js +6540 -0
- package/dist/chunk-IVA2SCSN.js +6756 -0
- package/dist/chunk-JHXCSGPC.js +6352 -0
- package/dist/chunk-V6O4UXVN.js +6345 -0
- package/dist/chunk-ZRAOPQQW.js +6406 -0
- package/dist/cli.js +40 -98
- package/dist/index.d.ts +24 -2
- package/dist/index.js +1 -1
- package/package.json +17 -14
- package/.turbo/turbo-build.log +0 -17
- package/src/bridge/across.ts +0 -240
- package/src/bridge/bridge-manager.ts +0 -87
- package/src/bridge/index.ts +0 -9
- package/src/bridge/types.ts +0 -77
- package/src/chains.ts +0 -105
- package/src/cli.ts +0 -244
- package/src/config.ts +0 -499
- package/src/diagnostics.ts +0 -335
- package/src/index.ts +0 -98
- package/src/llm/anthropic.ts +0 -63
- package/src/llm/base.ts +0 -264
- package/src/llm/deepseek.ts +0 -48
- package/src/llm/google.ts +0 -63
- package/src/llm/groq.ts +0 -48
- package/src/llm/index.ts +0 -42
- package/src/llm/mistral.ts +0 -48
- package/src/llm/ollama.ts +0 -52
- package/src/llm/openai.ts +0 -51
- package/src/llm/together.ts +0 -48
- package/src/llm-providers.ts +0 -100
- package/src/logger.ts +0 -137
- package/src/paper/executor.ts +0 -201
- package/src/paper/index.ts +0 -1
- package/src/perp/client.ts +0 -200
- package/src/perp/index.ts +0 -12
- package/src/perp/msgpack.ts +0 -272
- package/src/perp/orders.ts +0 -234
- package/src/perp/positions.ts +0 -126
- package/src/perp/signer.ts +0 -277
- package/src/perp/types.ts +0 -192
- package/src/perp/websocket.ts +0 -274
- package/src/position-tracker.ts +0 -243
- package/src/prediction/client.ts +0 -281
- package/src/prediction/index.ts +0 -3
- package/src/prediction/order-manager.ts +0 -297
- package/src/prediction/types.ts +0 -151
- package/src/relay.ts +0 -254
- package/src/runtime.ts +0 -1755
- package/src/scrub-secrets.ts +0 -39
- package/src/setup.ts +0 -384
- package/src/signal.ts +0 -212
- package/src/spot/aerodrome.ts +0 -158
- package/src/spot/client.ts +0 -138
- package/src/spot/index.ts +0 -11
- package/src/spot/swap-manager.ts +0 -219
- package/src/spot/types.ts +0 -203
- package/src/spot/uniswap.ts +0 -150
- package/src/store.ts +0 -50
- package/src/strategy/index.ts +0 -2
- package/src/strategy/loader.ts +0 -191
- package/src/strategy/templates.ts +0 -125
- package/src/trading/index.ts +0 -2
- package/src/trading/market.ts +0 -120
- package/src/trading/risk.ts +0 -107
- package/src/ui.ts +0 -75
- package/test-bridge-arb-to-base.mjs +0 -223
- package/test-funded-check.mjs +0 -79
- package/test-funded-phase19.mjs +0 -933
- package/test-hl-deposit-recover.mjs +0 -281
- package/test-hl-withdraw.mjs +0 -372
- package/test-live-signing.mjs +0 -374
- package/test-phase7.mjs +0 -416
- package/test-recover-arb.mjs +0 -206
- package/test-spot-bridge.mjs +0 -248
- package/test-wallet-setup.mjs +0 -126
- package/tsconfig.json +0 -8
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import type { StrategyTemplate } from '@exagent/sdk';
|
|
2
|
-
|
|
3
|
-
const templates: StrategyTemplate[] = [
|
|
4
|
-
{
|
|
5
|
-
id: 'momentum',
|
|
6
|
-
name: 'Momentum Trader',
|
|
7
|
-
description: 'Identifies tokens with strong upward price momentum and rides the trend. Uses LLM to analyze price action and market sentiment.',
|
|
8
|
-
category: 'momentum',
|
|
9
|
-
venues: ['hyperliquid_perp', 'hyperliquid_spot'],
|
|
10
|
-
riskLevel: 'moderate',
|
|
11
|
-
systemPrompt: `You are a momentum trading agent. Analyze the provided market data and identify tokens with strong upward momentum.
|
|
12
|
-
|
|
13
|
-
Rules:
|
|
14
|
-
- Only trade tokens with clear directional momentum (avoid choppy markets)
|
|
15
|
-
- Use trailing stops to protect gains
|
|
16
|
-
- Size positions based on conviction (higher confidence = larger position)
|
|
17
|
-
- Cut losses quickly if momentum reverses
|
|
18
|
-
- Consider volume as confirmation of momentum
|
|
19
|
-
|
|
20
|
-
Return a JSON array of trade signals.`,
|
|
21
|
-
code: `
|
|
22
|
-
const prices = context.market.getPrices();
|
|
23
|
-
const positions = context.position.openPositions;
|
|
24
|
-
|
|
25
|
-
const messages = [
|
|
26
|
-
{ role: 'system', content: 'You are a momentum trading agent. Analyze market data and return trade signals as a JSON array. Each signal: { symbol, side: "buy"|"sell", confidence: 0-1, reasoning }. Return [] if no opportunities.' },
|
|
27
|
-
{ role: 'user', content: 'Current prices: ' + JSON.stringify(prices) + '\\nOpen positions: ' + JSON.stringify(positions.map(p => p.token)) + '\\nAnalyze for momentum opportunities.' }
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const response = await context.llm.chat(messages);
|
|
31
|
-
try {
|
|
32
|
-
const signals = JSON.parse(response.content);
|
|
33
|
-
return Array.isArray(signals) ? signals : [];
|
|
34
|
-
} catch {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
`,
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 'value',
|
|
41
|
-
name: 'Value Investor',
|
|
42
|
-
description: 'Identifies undervalued tokens based on fundamental analysis. Buys dips and holds for mean reversion.',
|
|
43
|
-
category: 'value',
|
|
44
|
-
venues: ['hyperliquid_spot'],
|
|
45
|
-
riskLevel: 'conservative',
|
|
46
|
-
systemPrompt: `You are a value investing agent. Identify tokens trading below their intrinsic value.
|
|
47
|
-
|
|
48
|
-
Rules:
|
|
49
|
-
- Focus on established tokens with strong fundamentals
|
|
50
|
-
- Buy on significant dips (>10% from recent highs)
|
|
51
|
-
- Hold positions longer (swing/position timeframe)
|
|
52
|
-
- Avoid chasing pumps
|
|
53
|
-
- Diversify across sectors
|
|
54
|
-
|
|
55
|
-
Return a JSON array of trade signals.`,
|
|
56
|
-
code: `
|
|
57
|
-
const prices = context.market.getPrices();
|
|
58
|
-
const positions = context.position.openPositions;
|
|
59
|
-
|
|
60
|
-
const messages = [
|
|
61
|
-
{ role: 'system', content: 'You are a value investing agent. Identify undervalued tokens. Return trade signals as JSON array: { symbol, side: "buy"|"sell", confidence: 0-1, reasoning }. Return [] if nothing is compelling.' },
|
|
62
|
-
{ role: 'user', content: 'Current prices: ' + JSON.stringify(prices) + '\\nPositions: ' + JSON.stringify(positions.map(p => ({ token: p.token, entry: p.costBasisPerUnit }))) + '\\nLook for value opportunities.' }
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
const response = await context.llm.chat(messages);
|
|
66
|
-
try {
|
|
67
|
-
const signals = JSON.parse(response.content);
|
|
68
|
-
return Array.isArray(signals) ? signals : [];
|
|
69
|
-
} catch {
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
`,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: 'arbitrage',
|
|
76
|
-
name: 'Arbitrage Hunter',
|
|
77
|
-
description: 'Scans for price discrepancies across venues and chains. Executes quickly to capture spreads.',
|
|
78
|
-
category: 'arbitrage',
|
|
79
|
-
venues: ['hyperliquid_perp', 'hyperliquid_spot', 'uniswap', 'aerodrome'],
|
|
80
|
-
riskLevel: 'aggressive',
|
|
81
|
-
systemPrompt: `You are an arbitrage agent. Find price discrepancies between venues.
|
|
82
|
-
|
|
83
|
-
Rules:
|
|
84
|
-
- Speed is critical — execute quickly before spreads close
|
|
85
|
-
- Account for fees and slippage in profitability calculations
|
|
86
|
-
- Only trade when net profit > 0.5% after all costs
|
|
87
|
-
- Monitor cross-chain opportunities (CEX vs DEX spreads)
|
|
88
|
-
|
|
89
|
-
Return a JSON array of trade signals.`,
|
|
90
|
-
code: `
|
|
91
|
-
const prices = context.market.getPrices();
|
|
92
|
-
|
|
93
|
-
const messages = [
|
|
94
|
-
{ role: 'system', content: 'You are an arbitrage agent. Find price discrepancies. Return trade signals as JSON array: { symbol, side: "buy"|"sell", venue, confidence: 0-1, reasoning }. Return [] if no arb opportunities.' },
|
|
95
|
-
{ role: 'user', content: 'Market prices: ' + JSON.stringify(prices) + '\\nScan for cross-venue arbitrage opportunities.' }
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
const response = await context.llm.chat(messages);
|
|
99
|
-
try {
|
|
100
|
-
const signals = JSON.parse(response.content);
|
|
101
|
-
return Array.isArray(signals) ? signals : [];
|
|
102
|
-
} catch {
|
|
103
|
-
return [];
|
|
104
|
-
}
|
|
105
|
-
`,
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
id: 'hold',
|
|
109
|
-
name: 'Hold (No Trading)',
|
|
110
|
-
description: 'Passive strategy that makes no trades. Useful for monitoring only.',
|
|
111
|
-
category: 'custom',
|
|
112
|
-
venues: [],
|
|
113
|
-
riskLevel: 'conservative',
|
|
114
|
-
systemPrompt: '',
|
|
115
|
-
code: `return [];`,
|
|
116
|
-
},
|
|
117
|
-
];
|
|
118
|
-
|
|
119
|
-
export function getTemplate(id: string): StrategyTemplate | undefined {
|
|
120
|
-
return templates.find(t => t.id === id);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function listTemplates(): StrategyTemplate[] {
|
|
124
|
-
return [...templates];
|
|
125
|
-
}
|
package/src/trading/index.ts
DELETED
package/src/trading/market.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type { MarketData, OHLCV } from '@exagent/sdk';
|
|
2
|
-
|
|
3
|
-
export class MarketDataService implements MarketData {
|
|
4
|
-
private prices: Record<string, number> = {};
|
|
5
|
-
private lastFetch = 0;
|
|
6
|
-
private cacheTTL: number;
|
|
7
|
-
private apiUrl: string | null;
|
|
8
|
-
private ohlcvCache: Map<string, { data: OHLCV[]; fetchedAt: number }> = new Map();
|
|
9
|
-
|
|
10
|
-
constructor(cacheTTLMs: number = 30000, apiUrl?: string) {
|
|
11
|
-
this.cacheTTL = cacheTTLMs;
|
|
12
|
-
this.apiUrl = apiUrl || null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async refreshPrices(symbols: string[]): Promise<Record<string, number>> {
|
|
16
|
-
if (symbols.length === 0) return this.prices;
|
|
17
|
-
|
|
18
|
-
const now = Date.now();
|
|
19
|
-
if (now - this.lastFetch < this.cacheTTL && Object.keys(this.prices).length > 0) {
|
|
20
|
-
return this.prices;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Try API first (if relay URL is configured)
|
|
24
|
-
if (this.apiUrl) {
|
|
25
|
-
try {
|
|
26
|
-
const res = await fetch(`${this.apiUrl}/v1/prices`);
|
|
27
|
-
if (res.ok) {
|
|
28
|
-
const data = await res.json() as Record<string, number>;
|
|
29
|
-
for (const [symbol, price] of Object.entries(data)) {
|
|
30
|
-
this.prices[symbol.toUpperCase()] = price;
|
|
31
|
-
}
|
|
32
|
-
this.lastFetch = now;
|
|
33
|
-
return this.prices;
|
|
34
|
-
}
|
|
35
|
-
} catch {
|
|
36
|
-
// Fall through to CoinGecko
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Fallback: CoinGecko (free, no API key)
|
|
41
|
-
try {
|
|
42
|
-
const ids = symbols.map(s => s.toLowerCase()).join(',');
|
|
43
|
-
const res = await fetch(
|
|
44
|
-
`https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
if (res.ok) {
|
|
48
|
-
const data = await res.json() as Record<string, { usd: number }>;
|
|
49
|
-
for (const [id, price] of Object.entries(data)) {
|
|
50
|
-
this.prices[id.toUpperCase()] = price.usd;
|
|
51
|
-
}
|
|
52
|
-
this.lastFetch = now;
|
|
53
|
-
}
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.warn('[market] Price fetch failed:', (err as Error).message);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return this.prices;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
getPrice(symbol: string): number | undefined {
|
|
62
|
-
return this.prices[symbol.toUpperCase()];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
getPrices(): Record<string, number> {
|
|
66
|
-
return { ...this.prices };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
setPrice(symbol: string, price: number): void {
|
|
70
|
-
this.prices[symbol.toUpperCase()] = price;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
setPrices(prices: Record<string, number>): void {
|
|
74
|
-
for (const [symbol, price] of Object.entries(prices)) {
|
|
75
|
-
this.prices[symbol.toUpperCase()] = price;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
getOHLCV(symbol: string, timeframe: string): OHLCV[] {
|
|
80
|
-
const cacheKey = `${symbol.toUpperCase()}:${timeframe}`;
|
|
81
|
-
const cached = this.ohlcvCache.get(cacheKey);
|
|
82
|
-
if (cached && Date.now() - cached.fetchedAt < this.cacheTTL) {
|
|
83
|
-
return cached.data;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Trigger async fetch (non-blocking) — returns cached or empty on first call
|
|
87
|
-
this.fetchOHLCV(symbol, timeframe).catch(() => {});
|
|
88
|
-
return cached?.data || [];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private async fetchOHLCV(symbol: string, timeframe: string): Promise<void> {
|
|
92
|
-
const cacheKey = `${symbol.toUpperCase()}:${timeframe}`;
|
|
93
|
-
|
|
94
|
-
// Try API first
|
|
95
|
-
if (this.apiUrl) {
|
|
96
|
-
try {
|
|
97
|
-
const res = await fetch(
|
|
98
|
-
`${this.apiUrl}/v1/prices/${symbol.toUpperCase()}/ohlcv?interval=${timeframe}&limit=100`,
|
|
99
|
-
);
|
|
100
|
-
if (res.ok) {
|
|
101
|
-
const bars = await res.json() as Array<{ t: number; o: number; h: number; l: number; c: number; v: number }>;
|
|
102
|
-
const ohlcv: OHLCV[] = bars.map((b) => ({
|
|
103
|
-
timestamp: b.t,
|
|
104
|
-
open: b.o,
|
|
105
|
-
high: b.h,
|
|
106
|
-
low: b.l,
|
|
107
|
-
close: b.c,
|
|
108
|
-
volume: b.v,
|
|
109
|
-
}));
|
|
110
|
-
this.ohlcvCache.set(cacheKey, { data: ohlcv, fetchedAt: Date.now() });
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
} catch {
|
|
114
|
-
// Fall through
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// No fallback for OHLCV — requires API
|
|
119
|
-
}
|
|
120
|
-
}
|
package/src/trading/risk.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import type { TradeSignal, RiskParams, MarketData } from '@exagent/sdk';
|
|
2
|
-
|
|
3
|
-
export class RiskManager {
|
|
4
|
-
private params: RiskParams;
|
|
5
|
-
private dailyPnL = 0;
|
|
6
|
-
private dailyFees = 0;
|
|
7
|
-
private lastResetDate: string = '';
|
|
8
|
-
private initialCapitalUSD: number;
|
|
9
|
-
|
|
10
|
-
constructor(params: RiskParams, initialCapitalUSD: number = 10000) {
|
|
11
|
-
this.params = params;
|
|
12
|
-
this.initialCapitalUSD = initialCapitalUSD;
|
|
13
|
-
this.resetIfNewDay();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
filterSignals(signals: TradeSignal[], market: MarketData, openPositionCount: number): TradeSignal[] {
|
|
17
|
-
this.resetIfNewDay();
|
|
18
|
-
|
|
19
|
-
if (this.isDailyLossLimitHit()) {
|
|
20
|
-
console.log('[risk] Daily loss limit hit — blocking all trades');
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return signals.filter(signal => this.validateSignal(signal, market, openPositionCount));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private validateSignal(signal: TradeSignal, market: MarketData, openPositionCount: number): boolean {
|
|
28
|
-
// Confidence threshold
|
|
29
|
-
const threshold = this.params.confidenceThreshold ?? 0.5;
|
|
30
|
-
if (signal.confidence !== undefined && signal.confidence < threshold) {
|
|
31
|
-
console.log(`[risk] Blocked ${signal.symbol}: confidence ${signal.confidence} < ${threshold}`);
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Position size limit — use leverage-adjusted notional exposure
|
|
36
|
-
// A 10x leveraged $5k position = $50k notional exposure
|
|
37
|
-
const leverage = signal.leverage || 1;
|
|
38
|
-
const tradeValue = signal.size * signal.price * leverage;
|
|
39
|
-
const maxPositionValue = (this.params.maxPositionSizeBps / 10000) * this.initialCapitalUSD;
|
|
40
|
-
if (tradeValue > maxPositionValue) {
|
|
41
|
-
console.log(`[risk] Blocked ${signal.symbol}: notional $${tradeValue.toFixed(0)} (${leverage}x leverage) > max $${maxPositionValue.toFixed(0)}`);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Min trade value
|
|
46
|
-
if (tradeValue < this.params.minTradeValueUSD) {
|
|
47
|
-
console.log(`[risk] Blocked ${signal.symbol}: trade $${tradeValue.toFixed(2)} < min $${this.params.minTradeValueUSD}`);
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Max concurrent positions (only for buys/longs)
|
|
52
|
-
if ((signal.side === 'buy' || signal.side === 'long') && openPositionCount >= this.params.maxConcurrentPositions) {
|
|
53
|
-
console.log(`[risk] Blocked ${signal.symbol}: ${openPositionCount} positions >= max ${this.params.maxConcurrentPositions}`);
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Max slippage
|
|
58
|
-
if (signal.orderType === 'market') {
|
|
59
|
-
const currentPrice = market.getPrice(signal.symbol);
|
|
60
|
-
if (currentPrice) {
|
|
61
|
-
const slippageBps = Math.abs(signal.price - currentPrice) / currentPrice * 10000;
|
|
62
|
-
if (slippageBps > this.params.maxSlippageBps) {
|
|
63
|
-
console.log(`[risk] Blocked ${signal.symbol}: slippage ${slippageBps.toFixed(0)}bps > max ${this.params.maxSlippageBps}bps`);
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
updateParams(updates: Record<string, number>): void {
|
|
73
|
-
if (updates.maxPositionSizeBps !== undefined) this.params.maxPositionSizeBps = updates.maxPositionSizeBps;
|
|
74
|
-
if (updates.maxDailyLossBps !== undefined) this.params.maxDailyLossBps = updates.maxDailyLossBps;
|
|
75
|
-
if (updates.maxConcurrentPositions !== undefined) this.params.maxConcurrentPositions = updates.maxConcurrentPositions;
|
|
76
|
-
if (updates.maxSlippageBps !== undefined) this.params.maxSlippageBps = updates.maxSlippageBps;
|
|
77
|
-
if (updates.minTradeValueUSD !== undefined) this.params.minTradeValueUSD = updates.minTradeValueUSD;
|
|
78
|
-
console.log('[risk] Params updated:', JSON.stringify(this.params));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
recordTrade(pnl: number, fee: number): void {
|
|
82
|
-
this.dailyPnL += pnl;
|
|
83
|
-
this.dailyFees += fee;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
isDailyLossLimitHit(): boolean {
|
|
87
|
-
const limit = (this.params.maxDailyLossBps / 10000) * this.initialCapitalUSD;
|
|
88
|
-
return this.dailyPnL < -limit;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
getDailyPnL(): number {
|
|
92
|
-
return this.dailyPnL;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
getDailyLossLimit(): number {
|
|
96
|
-
return (this.params.maxDailyLossBps / 10000) * this.initialCapitalUSD;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private resetIfNewDay(): void {
|
|
100
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
101
|
-
if (today !== this.lastResetDate) {
|
|
102
|
-
this.dailyPnL = 0;
|
|
103
|
-
this.dailyFees = 0;
|
|
104
|
-
this.lastResetDate = today;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
package/src/ui.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import figlet from 'figlet';
|
|
2
|
-
import gradient from 'gradient-string';
|
|
3
|
-
import boxen from 'boxen';
|
|
4
|
-
import pc from 'picocolors';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
|
-
|
|
7
|
-
// Brand gradient: Blue → Indigo → Violet (from Exagent design system)
|
|
8
|
-
const brandGradient: (text: string) => string = gradient(['#3B82F6', '#6366F1', '#7C3AED']);
|
|
9
|
-
|
|
10
|
-
// Secondary gradient: Cyan → Blue
|
|
11
|
-
const accentGradient: (text: string) => string = gradient(['#22D3EE', '#3B82F6']);
|
|
12
|
-
|
|
13
|
-
function getVersion(): string {
|
|
14
|
-
try {
|
|
15
|
-
const require = createRequire(import.meta.url);
|
|
16
|
-
const pkg = require('../package.json');
|
|
17
|
-
return pkg.version || '0.0.0';
|
|
18
|
-
} catch {
|
|
19
|
-
return '0.0.0';
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function printBanner(): void {
|
|
24
|
-
const art = figlet.textSync('EXAGENT', {
|
|
25
|
-
font: 'Small',
|
|
26
|
-
horizontalLayout: 'default',
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
console.log();
|
|
30
|
-
console.log(brandGradient(art));
|
|
31
|
-
console.log(pc.dim(` v${getVersion()}`));
|
|
32
|
-
console.log();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function printSuccess(title: string, lines: string[]): void {
|
|
36
|
-
const body = [
|
|
37
|
-
'',
|
|
38
|
-
pc.bold(pc.white(title)),
|
|
39
|
-
'',
|
|
40
|
-
...lines.map(l => ` ${l}`),
|
|
41
|
-
'',
|
|
42
|
-
].join('\n');
|
|
43
|
-
|
|
44
|
-
console.log();
|
|
45
|
-
console.log(boxen(body, {
|
|
46
|
-
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
47
|
-
borderColor: '#3B82F6',
|
|
48
|
-
borderStyle: 'round',
|
|
49
|
-
dimBorder: false,
|
|
50
|
-
}));
|
|
51
|
-
console.log();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function printStep(step: number, total: number, label: string): void {
|
|
55
|
-
console.log();
|
|
56
|
-
console.log(accentGradient(` Step ${step} of ${total}`) + pc.dim(` — ${label}`));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function printDone(message: string): void {
|
|
60
|
-
console.log(` ${pc.green('✓')} ${message}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function printInfo(message: string): void {
|
|
64
|
-
console.log(` ${pc.dim('│')} ${message}`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function printWarn(message: string): void {
|
|
68
|
-
console.log(` ${pc.yellow('!')} ${message}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function printError(message: string): void {
|
|
72
|
-
console.log(` ${pc.red('✗')} ${message}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export { pc, brandGradient, accentGradient };
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fix the Arb → Base bridge issue (InvalidQuoteTimestamp).
|
|
3
|
-
*
|
|
4
|
-
* Root cause: The Across SpokePool's depositQuoteTimeBuffer is tight.
|
|
5
|
-
* If there's any delay between getting the fee quote and submitting the tx,
|
|
6
|
-
* the quoteTimestamp becomes stale and the tx reverts.
|
|
7
|
-
*
|
|
8
|
-
* Fix: Do approval BEFORE getting the quote, then submit IMMEDIATELY after.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
createPublicClient,
|
|
13
|
-
createWalletClient,
|
|
14
|
-
http,
|
|
15
|
-
formatUnits,
|
|
16
|
-
formatEther,
|
|
17
|
-
parseEther,
|
|
18
|
-
maxUint256,
|
|
19
|
-
} from 'viem';
|
|
20
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
21
|
-
import { base, arbitrum } from 'viem/chains';
|
|
22
|
-
|
|
23
|
-
const DEPLOYER_KEY = '0x0991f4e17be491bb11f4cf1d079db1771fe669e2b0d735f2b3ffc0e32d230ac9';
|
|
24
|
-
const TEST_KEY = '0xb027d931f6c8b4b2681451716981432130806f01ff87001c74412b504b810dbe';
|
|
25
|
-
|
|
26
|
-
const deployer = privateKeyToAccount(DEPLOYER_KEY);
|
|
27
|
-
const testWallet = privateKeyToAccount(TEST_KEY);
|
|
28
|
-
|
|
29
|
-
const arbPublic = createPublicClient({ chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc') });
|
|
30
|
-
const arbDeployerWallet = createWalletClient({ account: deployer, chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc') });
|
|
31
|
-
const arbTestWallet = createWalletClient({ account: testWallet, chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc') });
|
|
32
|
-
|
|
33
|
-
const basePublic = createPublicClient({ chain: base, transport: http('https://mainnet.base.org') });
|
|
34
|
-
const baseTestWallet = createWalletClient({ account: testWallet, chain: base, transport: http('https://mainnet.base.org') });
|
|
35
|
-
|
|
36
|
-
const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
|
|
37
|
-
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
38
|
-
const ACROSS_SPOKE_ARB = '0xe35e9842fceaCA96570B734083f4a58e8F7C5f2A';
|
|
39
|
-
|
|
40
|
-
const ERC20_ABI = [
|
|
41
|
-
{ type: 'function', name: 'transfer', inputs: [{name:'to',type:'address'},{name:'amount',type:'uint256'}], outputs: [{type:'bool'}], stateMutability: 'nonpayable' },
|
|
42
|
-
{ type: 'function', name: 'balanceOf', inputs: [{name:'account',type:'address'}], outputs: [{type:'uint256'}], stateMutability: 'view' },
|
|
43
|
-
{ type: 'function', name: 'approve', inputs: [{name:'spender',type:'address'},{name:'amount',type:'uint256'}], outputs: [{type:'bool'}], stateMutability: 'nonpayable' },
|
|
44
|
-
{ type: 'function', name: 'allowance', inputs: [{name:'owner',type:'address'},{name:'spender',type:'address'}], outputs: [{type:'uint256'}], stateMutability: 'view' },
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
const ACROSS_SPOKE_ABI = [
|
|
48
|
-
{
|
|
49
|
-
type: 'function', name: 'depositV3',
|
|
50
|
-
inputs: [
|
|
51
|
-
{ name: 'depositor', type: 'address' }, { name: 'recipient', type: 'address' },
|
|
52
|
-
{ name: 'inputToken', type: 'address' }, { name: 'outputToken', type: 'address' },
|
|
53
|
-
{ name: 'inputAmount', type: 'uint256' }, { name: 'outputAmount', type: 'uint256' },
|
|
54
|
-
{ name: 'destinationChainId', type: 'uint256' },
|
|
55
|
-
{ name: 'exclusiveRelayer', type: 'address' },
|
|
56
|
-
{ name: 'quoteTimestamp', type: 'uint32' }, { name: 'fillDeadline', type: 'uint32' },
|
|
57
|
-
{ name: 'exclusivityDeadline', type: 'uint32' }, { name: 'message', type: 'bytes' },
|
|
58
|
-
],
|
|
59
|
-
outputs: [],
|
|
60
|
-
stateMutability: 'payable',
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
65
|
-
|
|
66
|
-
async function waitForTx(client, hash, label) {
|
|
67
|
-
console.log(` ⏳ ${label}: tx ${hash.slice(0, 10)}...`);
|
|
68
|
-
const receipt = await client.waitForTransactionReceipt({ hash, timeout: 120_000 });
|
|
69
|
-
if (receipt.status === 'reverted') throw new Error(`${label} REVERTED: ${hash}`);
|
|
70
|
-
console.log(` ✅ ${label}: confirmed (gas: ${receipt.gasUsed})`);
|
|
71
|
-
return receipt;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ═══ Step 1: Fund deployer with ETH on Arb (from test wallet) ═══
|
|
75
|
-
console.log('═══ Step 1: Give deployer ETH on Arb for gas ═══');
|
|
76
|
-
|
|
77
|
-
const deployerArbEth = await arbPublic.getBalance({ address: deployer.address });
|
|
78
|
-
if (deployerArbEth < parseEther('0.0003')) {
|
|
79
|
-
console.log(' Sending 0.0005 ETH from test wallet to deployer on Arb...');
|
|
80
|
-
const h = await arbTestWallet.sendTransaction({
|
|
81
|
-
to: deployer.address,
|
|
82
|
-
value: parseEther('0.0005'),
|
|
83
|
-
});
|
|
84
|
-
await waitForTx(arbPublic, h, 'ETH to deployer');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ═══ Step 2: Deployer sends USDC to test wallet ═══
|
|
88
|
-
console.log('\n═══ Step 2: Deployer sends USDC to test wallet on Arb ═══');
|
|
89
|
-
|
|
90
|
-
const deployerUsdc = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [deployer.address] });
|
|
91
|
-
console.log(` Deployer USDC: ${formatUnits(deployerUsdc, 6)}`);
|
|
92
|
-
|
|
93
|
-
if (deployerUsdc > 0n) {
|
|
94
|
-
const h = await arbDeployerWallet.writeContract({
|
|
95
|
-
address: USDC_ARB, abi: ERC20_ABI, functionName: 'transfer',
|
|
96
|
-
args: [testWallet.address, deployerUsdc],
|
|
97
|
-
});
|
|
98
|
-
await waitForTx(arbPublic, h, 'USDC to test wallet');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const testUsdc = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
|
|
102
|
-
console.log(` Test wallet USDC: ${formatUnits(testUsdc, 6)}`);
|
|
103
|
-
|
|
104
|
-
// ═══ Step 3: Pre-approve USDC to Across SpokePool (BEFORE getting quote) ═══
|
|
105
|
-
console.log('\n═══ Step 3: Pre-approve USDC to Across ═══');
|
|
106
|
-
|
|
107
|
-
const allowance = await arbPublic.readContract({
|
|
108
|
-
address: USDC_ARB, abi: ERC20_ABI, functionName: 'allowance',
|
|
109
|
-
args: [testWallet.address, ACROSS_SPOKE_ARB],
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
if (allowance < testUsdc) {
|
|
113
|
-
const h = await arbTestWallet.writeContract({
|
|
114
|
-
address: USDC_ARB, abi: ERC20_ABI, functionName: 'approve',
|
|
115
|
-
args: [ACROSS_SPOKE_ARB, maxUint256],
|
|
116
|
-
});
|
|
117
|
-
await waitForTx(arbPublic, h, 'Approve Across');
|
|
118
|
-
} else {
|
|
119
|
-
console.log(' Already approved');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ═══ Step 4: Get quote + submit bridge IMMEDIATELY (no delays!) ═══
|
|
123
|
-
console.log('\n═══ Step 4: Bridge USDC Arb → Base (quote + submit together) ═══');
|
|
124
|
-
|
|
125
|
-
const bridgeAmount = testUsdc;
|
|
126
|
-
console.log(` Bridging ${formatUnits(bridgeAmount, 6)} USDC...`);
|
|
127
|
-
|
|
128
|
-
// Get fee quote
|
|
129
|
-
const params = new URLSearchParams({
|
|
130
|
-
inputToken: USDC_ARB,
|
|
131
|
-
outputToken: USDC_BASE,
|
|
132
|
-
originChainId: '42161',
|
|
133
|
-
destinationChainId: '8453',
|
|
134
|
-
amount: bridgeAmount.toString(),
|
|
135
|
-
recipient: testWallet.address,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const feeRes = await fetch(`https://app.across.to/api/suggested-fees?${params}`);
|
|
139
|
-
if (!feeRes.ok) {
|
|
140
|
-
console.log(` ❌ Fee API error: ${await feeRes.text()}`);
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const feeData = await feeRes.json();
|
|
145
|
-
|
|
146
|
-
if (feeData.isAmountTooLow) {
|
|
147
|
-
console.log(' ❌ Amount too low for bridge');
|
|
148
|
-
process.exit(1);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const outputAmount = BigInt(bridgeAmount) - BigInt(feeData.totalRelayFee.total);
|
|
152
|
-
const quoteTimestamp = parseInt(feeData.timestamp);
|
|
153
|
-
const fillDeadline = Math.floor(Date.now() / 1000) + 21600;
|
|
154
|
-
|
|
155
|
-
console.log(` Quote timestamp: ${quoteTimestamp}`);
|
|
156
|
-
console.log(` Output: ${formatUnits(outputAmount, 6)} USDC`);
|
|
157
|
-
|
|
158
|
-
// IMMEDIATELY submit — no awaits between quote and this call!
|
|
159
|
-
console.log(' Submitting depositV3 IMMEDIATELY...');
|
|
160
|
-
const bridgeHash = await arbTestWallet.writeContract({
|
|
161
|
-
address: ACROSS_SPOKE_ARB,
|
|
162
|
-
abi: ACROSS_SPOKE_ABI,
|
|
163
|
-
functionName: 'depositV3',
|
|
164
|
-
args: [
|
|
165
|
-
testWallet.address, testWallet.address,
|
|
166
|
-
USDC_ARB, USDC_BASE,
|
|
167
|
-
bridgeAmount, outputAmount,
|
|
168
|
-
8453n,
|
|
169
|
-
'0x0000000000000000000000000000000000000000',
|
|
170
|
-
quoteTimestamp, fillDeadline, 0, '0x',
|
|
171
|
-
],
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
await waitForTx(arbPublic, bridgeHash, 'Arb→Base bridge');
|
|
175
|
-
|
|
176
|
-
// Poll for fill
|
|
177
|
-
console.log(' Polling for fill...');
|
|
178
|
-
for (let i = 0; i < 60; i++) {
|
|
179
|
-
await sleep(2000);
|
|
180
|
-
try {
|
|
181
|
-
const sRes = await fetch(`https://app.across.to/api/deposit/status?depositTxHash=${bridgeHash}&originChainId=42161`);
|
|
182
|
-
if (sRes.ok) {
|
|
183
|
-
const s = await sRes.json();
|
|
184
|
-
if (s.status === 'filled') { console.log(' ✅ Bridge filled!'); break; }
|
|
185
|
-
if (i % 5 === 0) console.log(` ... ${s.status} (${i*2}s)`);
|
|
186
|
-
}
|
|
187
|
-
} catch {}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await sleep(3000);
|
|
191
|
-
|
|
192
|
-
// ═══ Step 5: Return all funds to deployer on Base ═══
|
|
193
|
-
console.log('\n═══ Step 5: Return funds to deployer ═══');
|
|
194
|
-
|
|
195
|
-
const baseUsdc = await basePublic.readContract({ address: USDC_BASE, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
|
|
196
|
-
if (baseUsdc > 0n) {
|
|
197
|
-
console.log(` Returning ${formatUnits(baseUsdc, 6)} USDC...`);
|
|
198
|
-
const h = await baseTestWallet.writeContract({
|
|
199
|
-
address: USDC_BASE, abi: ERC20_ABI, functionName: 'transfer',
|
|
200
|
-
args: [deployer.address, baseUsdc],
|
|
201
|
-
});
|
|
202
|
-
await waitForTx(basePublic, h, 'Return USDC');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Return remaining Base ETH
|
|
206
|
-
const baseEth = await basePublic.getBalance({ address: testWallet.address });
|
|
207
|
-
if (baseEth > 200000000000000n) {
|
|
208
|
-
const returnEth = baseEth - 100000000000000n;
|
|
209
|
-
const h = await baseTestWallet.sendTransaction({ to: deployer.address, value: returnEth });
|
|
210
|
-
await waitForTx(basePublic, h, 'Return ETH');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Final report
|
|
214
|
-
console.log('\n═══ Final Deployer Balance ═══');
|
|
215
|
-
const dEth = await basePublic.getBalance({ address: deployer.address });
|
|
216
|
-
const dUsdc = await basePublic.readContract({ address: USDC_BASE, abi: ERC20_ABI, functionName: 'balanceOf', args: [deployer.address] });
|
|
217
|
-
console.log(` Base: ${formatEther(dEth)} ETH, ${formatUnits(dUsdc, 6)} USDC`);
|
|
218
|
-
|
|
219
|
-
const dArbUsdc = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [deployer.address] });
|
|
220
|
-
const dArbEth = await arbPublic.getBalance({ address: deployer.address });
|
|
221
|
-
console.log(` Arb: ${formatEther(dArbEth)} ETH, ${formatUnits(dArbUsdc, 6)} USDC`);
|
|
222
|
-
|
|
223
|
-
console.log('\nDone!');
|