@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.
Files changed (78) hide show
  1. package/dist/chunk-7UGLJO6W.js +6392 -0
  2. package/dist/chunk-EHAOPCTJ.js +6406 -0
  3. package/dist/chunk-FGMXTW5I.js +6540 -0
  4. package/dist/chunk-IVA2SCSN.js +6756 -0
  5. package/dist/chunk-JHXCSGPC.js +6352 -0
  6. package/dist/chunk-V6O4UXVN.js +6345 -0
  7. package/dist/chunk-ZRAOPQQW.js +6406 -0
  8. package/dist/cli.js +40 -98
  9. package/dist/index.d.ts +24 -2
  10. package/dist/index.js +1 -1
  11. package/package.json +17 -14
  12. package/.turbo/turbo-build.log +0 -17
  13. package/src/bridge/across.ts +0 -240
  14. package/src/bridge/bridge-manager.ts +0 -87
  15. package/src/bridge/index.ts +0 -9
  16. package/src/bridge/types.ts +0 -77
  17. package/src/chains.ts +0 -105
  18. package/src/cli.ts +0 -244
  19. package/src/config.ts +0 -499
  20. package/src/diagnostics.ts +0 -335
  21. package/src/index.ts +0 -98
  22. package/src/llm/anthropic.ts +0 -63
  23. package/src/llm/base.ts +0 -264
  24. package/src/llm/deepseek.ts +0 -48
  25. package/src/llm/google.ts +0 -63
  26. package/src/llm/groq.ts +0 -48
  27. package/src/llm/index.ts +0 -42
  28. package/src/llm/mistral.ts +0 -48
  29. package/src/llm/ollama.ts +0 -52
  30. package/src/llm/openai.ts +0 -51
  31. package/src/llm/together.ts +0 -48
  32. package/src/llm-providers.ts +0 -100
  33. package/src/logger.ts +0 -137
  34. package/src/paper/executor.ts +0 -201
  35. package/src/paper/index.ts +0 -1
  36. package/src/perp/client.ts +0 -200
  37. package/src/perp/index.ts +0 -12
  38. package/src/perp/msgpack.ts +0 -272
  39. package/src/perp/orders.ts +0 -234
  40. package/src/perp/positions.ts +0 -126
  41. package/src/perp/signer.ts +0 -277
  42. package/src/perp/types.ts +0 -192
  43. package/src/perp/websocket.ts +0 -274
  44. package/src/position-tracker.ts +0 -243
  45. package/src/prediction/client.ts +0 -281
  46. package/src/prediction/index.ts +0 -3
  47. package/src/prediction/order-manager.ts +0 -297
  48. package/src/prediction/types.ts +0 -151
  49. package/src/relay.ts +0 -254
  50. package/src/runtime.ts +0 -1755
  51. package/src/scrub-secrets.ts +0 -39
  52. package/src/setup.ts +0 -384
  53. package/src/signal.ts +0 -212
  54. package/src/spot/aerodrome.ts +0 -158
  55. package/src/spot/client.ts +0 -138
  56. package/src/spot/index.ts +0 -11
  57. package/src/spot/swap-manager.ts +0 -219
  58. package/src/spot/types.ts +0 -203
  59. package/src/spot/uniswap.ts +0 -150
  60. package/src/store.ts +0 -50
  61. package/src/strategy/index.ts +0 -2
  62. package/src/strategy/loader.ts +0 -191
  63. package/src/strategy/templates.ts +0 -125
  64. package/src/trading/index.ts +0 -2
  65. package/src/trading/market.ts +0 -120
  66. package/src/trading/risk.ts +0 -107
  67. package/src/ui.ts +0 -75
  68. package/test-bridge-arb-to-base.mjs +0 -223
  69. package/test-funded-check.mjs +0 -79
  70. package/test-funded-phase19.mjs +0 -933
  71. package/test-hl-deposit-recover.mjs +0 -281
  72. package/test-hl-withdraw.mjs +0 -372
  73. package/test-live-signing.mjs +0 -374
  74. package/test-phase7.mjs +0 -416
  75. package/test-recover-arb.mjs +0 -206
  76. package/test-spot-bridge.mjs +0 -248
  77. package/test-wallet-setup.mjs +0 -126
  78. 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
- }
@@ -1,2 +0,0 @@
1
- export { RiskManager } from './risk.js';
2
- export { MarketDataService } from './market.js';
@@ -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
- }
@@ -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!');