@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
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
updateSecureStore,
|
|
10
10
|
writeConfigFile,
|
|
11
11
|
writeSampleConfig
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IVA2SCSN.js";
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
15
|
import { Command } from "commander";
|
|
@@ -80,85 +80,13 @@ function printError(message) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// src/llm-providers.ts
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{ id: "gpt-5-mini", label: "GPT-5 Mini" },
|
|
91
|
-
{ id: "gpt-5-nano", label: "GPT-5 Nano" },
|
|
92
|
-
{ id: "gpt-4o", label: "GPT-4o" },
|
|
93
|
-
{ id: "gpt-4o-mini", label: "GPT-4o Mini" }
|
|
94
|
-
]
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "anthropic",
|
|
98
|
-
label: "Anthropic",
|
|
99
|
-
models: [
|
|
100
|
-
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
|
|
101
|
-
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
|
|
102
|
-
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" }
|
|
103
|
-
]
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
id: "google",
|
|
107
|
-
label: "Google",
|
|
108
|
-
models: [
|
|
109
|
-
{ id: "gemini-3-pro", label: "Gemini 3 Pro" },
|
|
110
|
-
{ id: "gemini-3-flash", label: "Gemini 3 Flash" },
|
|
111
|
-
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
|
112
|
-
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
|
|
113
|
-
{ id: "gemini-2.5-flash-lite", label: "Gemini 2.5 Flash Lite" }
|
|
114
|
-
]
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
id: "deepseek",
|
|
118
|
-
label: "DeepSeek",
|
|
119
|
-
models: [
|
|
120
|
-
{ id: "deepseek-chat", label: "DeepSeek Chat" },
|
|
121
|
-
{ id: "deepseek-reasoner", label: "DeepSeek Reasoner" }
|
|
122
|
-
]
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
id: "mistral",
|
|
126
|
-
label: "Mistral",
|
|
127
|
-
models: [
|
|
128
|
-
{ id: "mistral-large-latest", label: "Mistral Large" },
|
|
129
|
-
{ id: "mistral-small-latest", label: "Mistral Small" }
|
|
130
|
-
]
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
id: "groq",
|
|
134
|
-
label: "Groq",
|
|
135
|
-
models: [
|
|
136
|
-
{ id: "llama-3.3-70b-versatile", label: "Llama 3.3 70B" },
|
|
137
|
-
{ id: "llama-3.1-8b-instant", label: "Llama 3.1 8B" },
|
|
138
|
-
{ id: "mixtral-8x7b-32768", label: "Mixtral 8x7B" }
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
id: "together",
|
|
143
|
-
label: "Together",
|
|
144
|
-
models: [
|
|
145
|
-
{ id: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo", label: "Llama 3.1 70B" },
|
|
146
|
-
{ id: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", label: "Llama 3.1 8B" }
|
|
147
|
-
]
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
id: "ollama",
|
|
151
|
-
label: "Ollama (local)",
|
|
152
|
-
models: [
|
|
153
|
-
{ id: "llama3.1", label: "Llama 3.1" },
|
|
154
|
-
{ id: "mistral", label: "Mistral" },
|
|
155
|
-
{ id: "custom", label: "Custom (type model name)" }
|
|
156
|
-
]
|
|
157
|
-
}
|
|
158
|
-
];
|
|
159
|
-
function getProvider(id) {
|
|
160
|
-
return LLM_PROVIDERS.find((p) => p.id === id);
|
|
161
|
-
}
|
|
83
|
+
import {
|
|
84
|
+
LLM_PROVIDERS,
|
|
85
|
+
getDefaultModel,
|
|
86
|
+
getProvider,
|
|
87
|
+
getProviderIds,
|
|
88
|
+
providerRequiresApiKey
|
|
89
|
+
} from "@exagent/sdk";
|
|
162
90
|
|
|
163
91
|
// src/setup.ts
|
|
164
92
|
function expandHomeDir(path) {
|
|
@@ -261,7 +189,9 @@ async function setupLlm(config) {
|
|
|
261
189
|
const apiKey2 = process.env.EXAGENT_LLM_KEY;
|
|
262
190
|
if (!provider2) throw new Error("EXAGENT_LLM_PROVIDER required in non-interactive mode");
|
|
263
191
|
if (!model2) throw new Error("EXAGENT_LLM_MODEL required in non-interactive mode");
|
|
264
|
-
if (!apiKey2)
|
|
192
|
+
if (providerRequiresApiKey(provider2) && !apiKey2) {
|
|
193
|
+
throw new Error("EXAGENT_LLM_KEY required in non-interactive mode");
|
|
194
|
+
}
|
|
265
195
|
printDone("LLM configured");
|
|
266
196
|
return { provider: provider2, model: model2, apiKey: apiKey2 };
|
|
267
197
|
}
|
|
@@ -276,7 +206,7 @@ async function setupLlm(config) {
|
|
|
276
206
|
const provider = selected;
|
|
277
207
|
const defaultModel = config.llm?.model;
|
|
278
208
|
const providerInfo = getProvider(provider);
|
|
279
|
-
const modelOptions = providerInfo ? providerInfo.models.map((m) => ({ value: m.id, label: m.label })) : [{ value: defaultModel || "
|
|
209
|
+
const modelOptions = providerInfo ? providerInfo.models.map((m) => ({ value: m.id, label: m.label })) : [{ value: defaultModel || getDefaultModel("openai"), label: defaultModel || getDefaultModel("openai") }];
|
|
280
210
|
const selectedModel = await clack.select({
|
|
281
211
|
message: "LLM model:",
|
|
282
212
|
options: modelOptions,
|
|
@@ -284,11 +214,17 @@ async function setupLlm(config) {
|
|
|
284
214
|
});
|
|
285
215
|
if (clack.isCancel(selectedModel)) cancelled();
|
|
286
216
|
const model = selectedModel;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
217
|
+
let apiKey;
|
|
218
|
+
if (providerRequiresApiKey(provider)) {
|
|
219
|
+
const enteredApiKey = await clack.password({
|
|
220
|
+
message: "LLM API key:",
|
|
221
|
+
validate: (val) => validateLlmKeyFormat(provider, val)
|
|
222
|
+
});
|
|
223
|
+
if (clack.isCancel(enteredApiKey)) cancelled();
|
|
224
|
+
apiKey = enteredApiKey;
|
|
225
|
+
} else {
|
|
226
|
+
printInfo("Ollama uses your local server; no API key needed.");
|
|
227
|
+
}
|
|
292
228
|
printDone("LLM configured");
|
|
293
229
|
return { provider, model, apiKey };
|
|
294
230
|
}
|
|
@@ -423,7 +359,7 @@ async function ensureLocalSetup(configPath) {
|
|
|
423
359
|
// src/cli.ts
|
|
424
360
|
import * as clack2 from "@clack/prompts";
|
|
425
361
|
var program = new Command();
|
|
426
|
-
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.
|
|
362
|
+
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.7");
|
|
427
363
|
program.command("init").description("Create a sample agent configuration file").option("--agent-id <id>", "Agent ID (from dashboard)", "my-agent").option("--api-url <url>", "API server URL", "http://localhost:3002").option("--config <path>", "Config file path", "agent-config.json").action((opts) => {
|
|
428
364
|
printBanner();
|
|
429
365
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
|
@@ -558,16 +494,22 @@ program.command("config").description("Change LLM provider, model, or API key").
|
|
|
558
494
|
}
|
|
559
495
|
newModel = selectedModel;
|
|
560
496
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
497
|
+
let newKey;
|
|
498
|
+
if (providerRequiresApiKey(newProvider)) {
|
|
499
|
+
const enteredKey = await clack2.password({
|
|
500
|
+
message: "New LLM API key:",
|
|
501
|
+
validate: (val) => {
|
|
502
|
+
if (!val?.trim()) return "API key is required.";
|
|
503
|
+
if (val.length < 10) return "API key seems too short.";
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
if (clack2.isCancel(enteredKey)) {
|
|
507
|
+
clack2.cancel("Cancelled.");
|
|
508
|
+
process.exit(0);
|
|
566
509
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
process.exit(0);
|
|
510
|
+
newKey = enteredKey;
|
|
511
|
+
} else {
|
|
512
|
+
printInfo("Ollama uses your local server; no API key needed.");
|
|
571
513
|
}
|
|
572
514
|
updateSecureStore(secureStorePath, password3, { llmApiKey: newKey });
|
|
573
515
|
const updatedConfig = readConfigFile(opts.config);
|
|
@@ -581,7 +523,7 @@ program.command("config").description("Change LLM provider, model, or API key").
|
|
|
581
523
|
printSuccess("Updated", [
|
|
582
524
|
`${pc.dim("Provider:")} ${pc.cyan(newProvider)}`,
|
|
583
525
|
`${pc.dim("Model:")} ${pc.cyan(newModel)}`,
|
|
584
|
-
`${pc.dim("API key:")} ${pc.dim(`${newKey.slice(0, 7)}...${newKey.slice(-4)}`)}`,
|
|
526
|
+
`${pc.dim("API key:")} ${newKey ? pc.dim(`${newKey.slice(0, 7)}...${newKey.slice(-4)}`) : pc.dim("not required")}`,
|
|
585
527
|
"",
|
|
586
528
|
`Run ${pc.cyan("npx exagent run")} to start with the new configuration.`
|
|
587
529
|
]);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLMProvider, RelayCommand, AgentStatusPayload, MessageType, MessageLevel, TradeSignal, StrategyStore, PositionSummary, TrackedPosition, TradeRecord, LLMAdapter, LLMConfig, LLMMessage, LLMResponse, LLMMetadata, StrategyFunction, StrategyTemplate, RiskParams, MarketData, OHLCV, PaperTrade, PaperMetrics } from '@exagent/sdk';
|
|
1
|
+
import { LLMProvider, StrategyProvenance, StrategyPermissions, RelayCommand, AgentStatusPayload, MessageType, MessageLevel, TradeSignal, StrategyStore, PositionSummary, TrackedPosition, TradeRecord, LLMAdapter, LLMConfig, LLMMessage, LLMResponse, LLMMetadata, StrategyFunction, StrategyTemplate, RiskParams, MarketData, OHLCV, PaperTrade, PaperMetrics } from '@exagent/sdk';
|
|
2
2
|
import { WalletClient, PublicClient, Transport, Chain as Chain$1, Account } from 'viem';
|
|
3
3
|
import { Chain } from 'viem/chains';
|
|
4
4
|
|
|
@@ -22,6 +22,8 @@ interface RuntimeConfig {
|
|
|
22
22
|
code?: string;
|
|
23
23
|
template?: string;
|
|
24
24
|
venues?: string[];
|
|
25
|
+
provenance?: StrategyProvenance;
|
|
26
|
+
permissions?: StrategyPermissions;
|
|
25
27
|
prompt?: {
|
|
26
28
|
name?: string;
|
|
27
29
|
systemPrompt: string;
|
|
@@ -137,6 +139,8 @@ declare class AgentRuntime {
|
|
|
137
139
|
private buildRuntimeVenuesFromSelection;
|
|
138
140
|
private buildStrategyFallbackPrompt;
|
|
139
141
|
private extractStrategyConfigFromAgentConfig;
|
|
142
|
+
private extractStrategyProvenance;
|
|
143
|
+
private extractStrategyPermissions;
|
|
140
144
|
private applyExecutionMode;
|
|
141
145
|
private initializeVenues;
|
|
142
146
|
private startTrading;
|
|
@@ -436,6 +440,8 @@ declare function loadStrategy(config: {
|
|
|
436
440
|
systemPrompt: string;
|
|
437
441
|
venues?: string[];
|
|
438
442
|
};
|
|
443
|
+
provenance?: StrategyProvenance;
|
|
444
|
+
permissions?: StrategyPermissions;
|
|
439
445
|
}): Promise<StrategyFunction>;
|
|
440
446
|
|
|
441
447
|
declare function getTemplate(id: string): StrategyTemplate | undefined;
|
|
@@ -456,6 +462,7 @@ declare class RiskManager {
|
|
|
456
462
|
getDailyPnL(): number;
|
|
457
463
|
getDailyLossLimit(): number;
|
|
458
464
|
private resetIfNewDay;
|
|
465
|
+
private validateUpdate;
|
|
459
466
|
}
|
|
460
467
|
|
|
461
468
|
declare class MarketDataService implements MarketData {
|
|
@@ -559,6 +566,7 @@ interface PerpConfig {
|
|
|
559
566
|
wsUrl: string;
|
|
560
567
|
maxLeverage: number;
|
|
561
568
|
maxNotionalUSD: number;
|
|
569
|
+
marketOrderSlippageBps?: number;
|
|
562
570
|
allowedInstruments?: string[];
|
|
563
571
|
}
|
|
564
572
|
type PerpAction = 'open_long' | 'open_short' | 'close_long' | 'close_short' | 'hold';
|
|
@@ -847,6 +855,13 @@ declare class HyperliquidSigner {
|
|
|
847
855
|
* No builder fee — private group, not needed.
|
|
848
856
|
*/
|
|
849
857
|
|
|
858
|
+
interface CancelAllResult {
|
|
859
|
+
success: boolean;
|
|
860
|
+
total: number;
|
|
861
|
+
cancelled: number;
|
|
862
|
+
failed: number;
|
|
863
|
+
errors: string[];
|
|
864
|
+
}
|
|
850
865
|
declare class HyperliquidOrderManager {
|
|
851
866
|
private readonly client;
|
|
852
867
|
private readonly signer;
|
|
@@ -854,11 +869,15 @@ declare class HyperliquidOrderManager {
|
|
|
854
869
|
constructor(client: HyperliquidClient, signer: HyperliquidSigner, config: PerpConfig);
|
|
855
870
|
placeOrder(signal: PerpTradeSignal): Promise<OrderResult>;
|
|
856
871
|
cancelOrder(instrument: string, orderId: number): Promise<boolean>;
|
|
857
|
-
|
|
872
|
+
cancelAllOrders(userAddress?: `0x${string}`): Promise<CancelAllResult>;
|
|
873
|
+
closePosition(instrument: string, positionSize: number, referencePrice?: number): Promise<OrderResult>;
|
|
858
874
|
updateLeverage(instrument: string, leverage: number, isCross?: boolean): Promise<boolean>;
|
|
859
875
|
private getMarketPrice;
|
|
876
|
+
private getReferencePrice;
|
|
877
|
+
private formatDecimal;
|
|
860
878
|
private parseOrderResponse;
|
|
861
879
|
private exchangeRequest;
|
|
880
|
+
private signedExchangeRequest;
|
|
862
881
|
}
|
|
863
882
|
|
|
864
883
|
/**
|
|
@@ -964,6 +983,7 @@ interface PredictionTradeSignal {
|
|
|
964
983
|
amount: number;
|
|
965
984
|
limitPrice: number;
|
|
966
985
|
orderType: 'limit' | 'market';
|
|
986
|
+
marketOrderType?: 'FOK' | 'FAK';
|
|
967
987
|
confidence: number;
|
|
968
988
|
reasoning?: string;
|
|
969
989
|
}
|
|
@@ -1078,6 +1098,8 @@ declare class PolymarketClient {
|
|
|
1078
1098
|
tokenId: string;
|
|
1079
1099
|
amount: number;
|
|
1080
1100
|
side: 'BUY' | 'SELL';
|
|
1101
|
+
price?: number;
|
|
1102
|
+
orderType?: 'FOK' | 'FAK';
|
|
1081
1103
|
}): Promise<{
|
|
1082
1104
|
orderId: string;
|
|
1083
1105
|
success: boolean;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exagent/agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
7
10
|
"bin": {
|
|
8
11
|
"exagent": "dist/cli.js"
|
|
9
12
|
},
|
|
@@ -15,29 +18,29 @@
|
|
|
15
18
|
},
|
|
16
19
|
"scripts": {
|
|
17
20
|
"build": "tsup src/index.ts src/cli.ts --format esm --dts",
|
|
18
|
-
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch"
|
|
21
|
+
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch",
|
|
22
|
+
"test": "tsx --test test/**/*.test.ts"
|
|
19
23
|
},
|
|
20
24
|
"dependencies": {
|
|
21
25
|
"@clack/prompts": "^1.1.0",
|
|
22
|
-
"@exagent/sdk": "
|
|
23
|
-
"@polymarket/clob-client": "^
|
|
26
|
+
"@exagent/sdk": "workspace:*",
|
|
27
|
+
"@polymarket/clob-client": "^5.8.1",
|
|
24
28
|
"boxen": "^8.0.1",
|
|
25
|
-
"commander": "^12.
|
|
26
|
-
"ethers": "^5.7.2",
|
|
29
|
+
"commander": "^12.1.0",
|
|
27
30
|
"figlet": "^1.10.0",
|
|
28
31
|
"gradient-string": "^3.0.0",
|
|
29
32
|
"picocolors": "^1.1.1",
|
|
30
|
-
"viem": "^2.
|
|
31
|
-
"ws": "^8.
|
|
32
|
-
"zod": "^3.
|
|
33
|
+
"viem": "^2.48.11",
|
|
34
|
+
"ws": "^8.20.0",
|
|
35
|
+
"zod": "^3.25.76"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@types/figlet": "^1.7.0",
|
|
36
39
|
"@types/gradient-string": "^1.1.6",
|
|
37
|
-
"@types/node": "^
|
|
38
|
-
"@types/ws": "^8.
|
|
39
|
-
"tsup": "^8.
|
|
40
|
-
"tsx": "^4.
|
|
41
|
-
"typescript": "^5.
|
|
40
|
+
"@types/node": "^22.19.18",
|
|
41
|
+
"@types/ws": "^8.18.1",
|
|
42
|
+
"tsup": "^8.5.1",
|
|
43
|
+
"tsx": "^4.21.0",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
42
45
|
}
|
|
43
46
|
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @exagent/agent@0.3.0 build /Users/graydon/Codebase Repertoire/Exagent/packages/agent
|
|
3
|
-
> tsup src/index.ts src/cli.ts --format esm --dts
|
|
4
|
-
|
|
5
|
-
CLI Building entry: src/cli.ts, src/index.ts
|
|
6
|
-
CLI Using tsconfig: tsconfig.json
|
|
7
|
-
CLI tsup v8.5.1
|
|
8
|
-
CLI Target: es2022
|
|
9
|
-
ESM Build start
|
|
10
|
-
ESM dist/cli.js 9.50 KB
|
|
11
|
-
ESM dist/index.js 1.75 KB
|
|
12
|
-
ESM dist/chunk-SVFTC5V2.js 201.37 KB
|
|
13
|
-
ESM ⚡️ Build success in 34ms
|
|
14
|
-
DTS Build start
|
|
15
|
-
DTS ⚡️ Build success in 8241ms
|
|
16
|
-
DTS dist/cli.d.ts 20.00 B
|
|
17
|
-
DTS dist/index.d.ts 41.02 KB
|
package/src/bridge/across.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { getChainConfig } from '../chains.js';
|
|
2
|
-
import { ERC20_ABI } from '../spot/types.js';
|
|
3
|
-
import type { SpotDEXClient } from '../spot/client.js';
|
|
4
|
-
import {
|
|
5
|
-
ACROSS_SPOKE_POOL_ABI,
|
|
6
|
-
type BridgeRequest,
|
|
7
|
-
type BridgeFeeEstimate,
|
|
8
|
-
type BridgeResult,
|
|
9
|
-
type BridgeConfig,
|
|
10
|
-
} from './types.js';
|
|
11
|
-
|
|
12
|
-
const ACROSS_API_BASE = 'https://app.across.to/api';
|
|
13
|
-
const ZERO_ADDRESS: `0x${string}` = '0x0000000000000000000000000000000000000000';
|
|
14
|
-
|
|
15
|
-
export class AcrossAdapter {
|
|
16
|
-
private client: SpotDEXClient;
|
|
17
|
-
private config: BridgeConfig;
|
|
18
|
-
|
|
19
|
-
constructor(client: SpotDEXClient, config: BridgeConfig) {
|
|
20
|
-
this.client = client;
|
|
21
|
-
this.config = config;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async estimateFee(request: BridgeRequest): Promise<BridgeFeeEstimate> {
|
|
25
|
-
const fromChain = getChainConfig(request.fromChain);
|
|
26
|
-
const toChain = getChainConfig(request.toChain);
|
|
27
|
-
if (!fromChain) throw new Error(`Unknown source chain: ${request.fromChain}`);
|
|
28
|
-
if (!toChain) throw new Error(`Unknown destination chain: ${request.toChain}`);
|
|
29
|
-
|
|
30
|
-
// Resolve output token on destination chain (same token, different address)
|
|
31
|
-
const outputToken = this.resolveOutputToken(request.token, request.fromChain, request.toChain);
|
|
32
|
-
|
|
33
|
-
const params = new URLSearchParams({
|
|
34
|
-
inputToken: request.token,
|
|
35
|
-
outputToken,
|
|
36
|
-
originChainId: fromChain.chainId.toString(),
|
|
37
|
-
destinationChainId: toChain.chainId.toString(),
|
|
38
|
-
amount: request.amount.toString(),
|
|
39
|
-
recipient: request.recipient ?? this.client.address,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const url = `${ACROSS_API_BASE}/suggested-fees?${params}`;
|
|
43
|
-
const res = await fetch(url);
|
|
44
|
-
if (!res.ok) {
|
|
45
|
-
const body = await res.text();
|
|
46
|
-
throw new Error(`Across fee API error (${res.status}): ${body}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const data = await res.json() as {
|
|
50
|
-
totalRelayFee: { pct: string };
|
|
51
|
-
relayerCapitalFee: { pct: string };
|
|
52
|
-
relayerGasFee: { pct: string };
|
|
53
|
-
lpFee: { pct: string };
|
|
54
|
-
timestamp: string;
|
|
55
|
-
estimatedFillTimeSec: number;
|
|
56
|
-
isAmountTooLow: boolean;
|
|
57
|
-
limits?: { minDeposit: string };
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Calculate output amount: inputAmount - totalFee
|
|
61
|
-
const totalFeePct = parseFloat(data.totalRelayFee.pct) / 1e18;
|
|
62
|
-
const feeAmount = BigInt(Math.floor(Number(request.amount) * totalFeePct));
|
|
63
|
-
const outputAmount = request.amount - feeAmount;
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
totalRelayFeePct: totalFeePct * 100,
|
|
67
|
-
capitalFeePct: parseFloat(data.relayerCapitalFee.pct) / 1e18 * 100,
|
|
68
|
-
lpFeePct: parseFloat(data.lpFee.pct) / 1e18 * 100,
|
|
69
|
-
relayGasFeePct: parseFloat(data.relayerGasFee.pct) / 1e18 * 100,
|
|
70
|
-
timestamp: parseInt(data.timestamp),
|
|
71
|
-
estimatedFillTimeSec: data.estimatedFillTimeSec,
|
|
72
|
-
isAmountTooLow: data.isAmountTooLow,
|
|
73
|
-
outputAmount,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async deposit(request: BridgeRequest): Promise<BridgeResult> {
|
|
78
|
-
const fromChain = getChainConfig(request.fromChain);
|
|
79
|
-
const toChain = getChainConfig(request.toChain);
|
|
80
|
-
if (!fromChain?.acrossSpokePool) throw new Error(`No Across SpokePool on ${request.fromChain}`);
|
|
81
|
-
if (!toChain) throw new Error(`Unknown destination chain: ${request.toChain}`);
|
|
82
|
-
|
|
83
|
-
const spokePool = fromChain.acrossSpokePool;
|
|
84
|
-
const outputToken = this.resolveOutputToken(request.token, request.fromChain, request.toChain) as `0x${string}`;
|
|
85
|
-
const recipient = request.recipient ?? this.client.address;
|
|
86
|
-
|
|
87
|
-
// IMPORTANT: Approve BEFORE getting the fee quote.
|
|
88
|
-
// The Across SpokePool has a tight depositQuoteTimeBuffer — if there's any
|
|
89
|
-
// delay between getting the quoteTimestamp and submitting depositV3, the tx
|
|
90
|
-
// reverts with InvalidQuoteTimestamp. Approval can take 10+ seconds on L2s.
|
|
91
|
-
await this.client.ensureApproval(request.token, spokePool, request.amount, request.fromChain);
|
|
92
|
-
|
|
93
|
-
// Get fee estimate (immediately before submitting — no awaits after this!)
|
|
94
|
-
const feeEstimate = await this.estimateFee(request);
|
|
95
|
-
if (feeEstimate.isAmountTooLow) {
|
|
96
|
-
return {
|
|
97
|
-
success: false,
|
|
98
|
-
depositTxHash: '0x0' as `0x${string}`,
|
|
99
|
-
fromChain: request.fromChain,
|
|
100
|
-
toChain: request.toChain,
|
|
101
|
-
token: request.token,
|
|
102
|
-
amount: request.amount,
|
|
103
|
-
fee: 0n,
|
|
104
|
-
error: 'Amount too low for Across bridge',
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const { publicClient, walletClient } = this.client.getClients(request.fromChain);
|
|
109
|
-
|
|
110
|
-
// Fill deadline: 6 hours from now
|
|
111
|
-
const fillDeadline = Math.floor(Date.now() / 1000) + 21600;
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const hash = await walletClient.writeContract({
|
|
115
|
-
address: spokePool,
|
|
116
|
-
abi: ACROSS_SPOKE_POOL_ABI,
|
|
117
|
-
functionName: 'depositV3',
|
|
118
|
-
args: [
|
|
119
|
-
this.client.address, // depositor
|
|
120
|
-
recipient, // recipient
|
|
121
|
-
request.token, // inputToken
|
|
122
|
-
outputToken, // outputToken
|
|
123
|
-
request.amount, // inputAmount
|
|
124
|
-
feeEstimate.outputAmount, // outputAmount (inputAmount - fees)
|
|
125
|
-
BigInt(toChain.chainId), // destinationChainId
|
|
126
|
-
ZERO_ADDRESS, // exclusiveRelayer (none)
|
|
127
|
-
feeEstimate.timestamp, // quoteTimestamp
|
|
128
|
-
fillDeadline, // fillDeadline
|
|
129
|
-
0, // exclusivityDeadline (none)
|
|
130
|
-
'0x', // message (empty)
|
|
131
|
-
],
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
135
|
-
const fee = request.amount - feeEstimate.outputAmount;
|
|
136
|
-
|
|
137
|
-
console.log(`[bridge] Across deposit: ${hash} (${request.fromChain} → ${request.toChain})`);
|
|
138
|
-
|
|
139
|
-
const result: BridgeResult = {
|
|
140
|
-
success: true,
|
|
141
|
-
depositTxHash: hash,
|
|
142
|
-
fromChain: request.fromChain,
|
|
143
|
-
toChain: request.toChain,
|
|
144
|
-
token: request.token,
|
|
145
|
-
amount: request.amount,
|
|
146
|
-
fee,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Poll for fill
|
|
150
|
-
const fill = await this.waitForFill(hash, request.fromChain);
|
|
151
|
-
if (fill.filled) {
|
|
152
|
-
result.fillTxHash = fill.fillTxHash;
|
|
153
|
-
result.fillTimestamp = Date.now();
|
|
154
|
-
console.log(`[bridge] Across fill confirmed: ${fill.fillTxHash}`);
|
|
155
|
-
} else {
|
|
156
|
-
// Deposit tx succeeded but fill was not confirmed within the timeout.
|
|
157
|
-
// Mark as failure so the trade signal system doesn't report a completed bridge.
|
|
158
|
-
result.success = false;
|
|
159
|
-
result.error = 'Bridge deposit submitted but fill not confirmed within timeout. Deposit tx may still be filled — check the Across explorer.';
|
|
160
|
-
console.warn(`[bridge] Across fill not confirmed within timeout (deposit: ${hash})`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return result;
|
|
164
|
-
} catch (err) {
|
|
165
|
-
return {
|
|
166
|
-
success: false,
|
|
167
|
-
depositTxHash: '0x0' as `0x${string}`,
|
|
168
|
-
fromChain: request.fromChain,
|
|
169
|
-
toChain: request.toChain,
|
|
170
|
-
token: request.token,
|
|
171
|
-
amount: request.amount,
|
|
172
|
-
fee: 0n,
|
|
173
|
-
error: (err as Error).message,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async waitForFill(
|
|
179
|
-
depositTxHash: string,
|
|
180
|
-
originChain: string,
|
|
181
|
-
): Promise<{ filled: boolean; fillTxHash?: `0x${string}` }> {
|
|
182
|
-
const chainConfig = getChainConfig(originChain);
|
|
183
|
-
if (!chainConfig) return { filled: false };
|
|
184
|
-
|
|
185
|
-
const startTime = Date.now();
|
|
186
|
-
const timeout = this.config.fillTimeoutMs;
|
|
187
|
-
const interval = this.config.pollIntervalMs;
|
|
188
|
-
|
|
189
|
-
while (Date.now() - startTime < timeout) {
|
|
190
|
-
try {
|
|
191
|
-
const params = new URLSearchParams({
|
|
192
|
-
depositTxHash,
|
|
193
|
-
originChainId: chainConfig.chainId.toString(),
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const res = await fetch(`${ACROSS_API_BASE}/deposit/status?${params}`);
|
|
197
|
-
if (res.ok) {
|
|
198
|
-
const data = await res.json() as {
|
|
199
|
-
status: string;
|
|
200
|
-
fillTx?: string;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
if (data.status === 'filled' && data.fillTx) {
|
|
204
|
-
return { filled: true, fillTxHash: data.fillTx as `0x${string}` };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (data.status === 'expired' || data.status === 'refunded') {
|
|
208
|
-
return { filled: false };
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
} catch {
|
|
212
|
-
// Non-critical — retry on next poll
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
await new Promise(resolve => setTimeout(resolve, interval));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return { filled: false };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** Resolve the equivalent token address on the destination chain */
|
|
222
|
-
private resolveOutputToken(inputToken: `0x${string}`, fromChain: string, toChain: string): string {
|
|
223
|
-
const from = getChainConfig(fromChain);
|
|
224
|
-
const to = getChainConfig(toChain);
|
|
225
|
-
if (!from || !to) return inputToken;
|
|
226
|
-
|
|
227
|
-
// If input is USDC on source chain, output is USDC on destination chain
|
|
228
|
-
if (inputToken.toLowerCase() === from.usdcAddress.toLowerCase()) {
|
|
229
|
-
return to.usdcAddress;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// If input is WETH on source chain, output is WETH on destination chain
|
|
233
|
-
if (inputToken.toLowerCase() === from.wethAddress.toLowerCase()) {
|
|
234
|
-
return to.wethAddress;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Default: same address (for tokens deployed at same address on both chains)
|
|
238
|
-
return inputToken;
|
|
239
|
-
}
|
|
240
|
-
}
|