@exagent/agent 0.3.6 → 0.3.8
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-GYYW4EKM.js +6756 -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-WTECTX2Z.js +6345 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +1 -1
- package/package.json +12 -9
- 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 -250
- package/src/config.ts +0 -502
- 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 -94
- package/src/llm/together.ts +0 -48
- package/src/llm-providers.ts +0 -8
- 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 -288
- 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 -392
- 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 -265
- package/src/strategy/templates.ts +0 -74
- 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/strategy-loader.test.ts +0 -150
- 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-GYYW4EKM.js";
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
15
|
import { Command } from "commander";
|
|
@@ -359,7 +359,7 @@ async function ensureLocalSetup(configPath) {
|
|
|
359
359
|
// src/cli.ts
|
|
360
360
|
import * as clack2 from "@clack/prompts";
|
|
361
361
|
var program = new Command();
|
|
362
|
-
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.8");
|
|
363
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) => {
|
|
364
364
|
printBanner();
|
|
365
365
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
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.8",
|
|
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
|
},
|
|
@@ -13,8 +16,14 @@
|
|
|
13
16
|
"types": "./dist/index.d.ts"
|
|
14
17
|
}
|
|
15
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts src/cli.ts --format esm --dts",
|
|
21
|
+
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch",
|
|
22
|
+
"test": "tsx --test test/**/*.test.ts"
|
|
23
|
+
},
|
|
16
24
|
"dependencies": {
|
|
17
25
|
"@clack/prompts": "^1.1.0",
|
|
26
|
+
"@exagent/sdk": "0.2.2",
|
|
18
27
|
"@polymarket/clob-client": "^5.8.1",
|
|
19
28
|
"boxen": "^8.0.1",
|
|
20
29
|
"commander": "^12.1.0",
|
|
@@ -23,8 +32,7 @@
|
|
|
23
32
|
"picocolors": "^1.1.1",
|
|
24
33
|
"viem": "^2.48.11",
|
|
25
34
|
"ws": "^8.20.0",
|
|
26
|
-
"zod": "^3.25.76"
|
|
27
|
-
"@exagent/sdk": "0.2.2"
|
|
35
|
+
"zod": "^3.25.76"
|
|
28
36
|
},
|
|
29
37
|
"devDependencies": {
|
|
30
38
|
"@types/figlet": "^1.7.0",
|
|
@@ -34,10 +42,5 @@
|
|
|
34
42
|
"tsup": "^8.5.1",
|
|
35
43
|
"tsx": "^4.21.0",
|
|
36
44
|
"typescript": "^5.9.3"
|
|
37
|
-
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsup src/index.ts src/cli.ts --format esm --dts",
|
|
40
|
-
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch",
|
|
41
|
-
"test": "tsx --test test/**/*.test.ts"
|
|
42
45
|
}
|
|
43
|
-
}
|
|
46
|
+
}
|
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
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { getChainConfig } from '../chains.js';
|
|
2
|
-
import type { SpotDEXClient } from '../spot/client.js';
|
|
3
|
-
import { AcrossAdapter } from './across.js';
|
|
4
|
-
import type { BridgeConfig, BridgeRequest, BridgeFeeEstimate, BridgeResult } from './types.js';
|
|
5
|
-
|
|
6
|
-
const MIN_GAS_WEI = 100_000_000_000_000n; // 0.0001 ETH/POL
|
|
7
|
-
|
|
8
|
-
export class BridgeManager {
|
|
9
|
-
private across: AcrossAdapter;
|
|
10
|
-
private client: SpotDEXClient;
|
|
11
|
-
private config: BridgeConfig;
|
|
12
|
-
|
|
13
|
-
constructor(client: SpotDEXClient, config: BridgeConfig) {
|
|
14
|
-
this.config = config;
|
|
15
|
-
this.client = client;
|
|
16
|
-
this.across = new AcrossAdapter(client, config);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async estimateFee(request: BridgeRequest): Promise<BridgeFeeEstimate> {
|
|
20
|
-
const bridge = request.bridge ?? this.config.defaultBridge;
|
|
21
|
-
if (bridge !== 'across') {
|
|
22
|
-
throw new Error(`Unsupported bridge: ${bridge}. Only "across" is currently supported.`);
|
|
23
|
-
}
|
|
24
|
-
return this.across.estimateFee(request);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async bridge(request: BridgeRequest): Promise<BridgeResult> {
|
|
28
|
-
const bridge = request.bridge ?? this.config.defaultBridge;
|
|
29
|
-
if (bridge !== 'across') {
|
|
30
|
-
return {
|
|
31
|
-
success: false,
|
|
32
|
-
depositTxHash: '0x0' as `0x${string}`,
|
|
33
|
-
fromChain: request.fromChain,
|
|
34
|
-
toChain: request.toChain,
|
|
35
|
-
token: request.token,
|
|
36
|
-
amount: request.amount,
|
|
37
|
-
fee: 0n,
|
|
38
|
-
error: `Unsupported bridge: ${bridge}. Only "across" is currently supported.`,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Enforce max bridge value cap
|
|
43
|
-
// Bridge amounts are in token decimals — estimate USD for common tokens (USDC = 6 decimals)
|
|
44
|
-
try {
|
|
45
|
-
const decimals = await this.client.getDecimals(request.token, request.fromChain);
|
|
46
|
-
const amountFloat = Number(request.amount) / 10 ** decimals;
|
|
47
|
-
if (amountFloat > this.config.maxBridgeValueUSD) {
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
depositTxHash: '0x0' as `0x${string}`,
|
|
51
|
-
fromChain: request.fromChain,
|
|
52
|
-
toChain: request.toChain,
|
|
53
|
-
token: request.token,
|
|
54
|
-
amount: request.amount,
|
|
55
|
-
fee: 0n,
|
|
56
|
-
error: `Bridge value ${amountFloat.toFixed(2)} exceeds max $${this.config.maxBridgeValueUSD}. Reduce amount or increase maxBridgeValueUSD.`,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// If decimals lookup fails, proceed — let the bridge itself validate
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Pre-check native gas balance on origin chain
|
|
64
|
-
try {
|
|
65
|
-
const nativeBalance = await this.client.getNativeBalance(request.fromChain);
|
|
66
|
-
if (nativeBalance < MIN_GAS_WEI) {
|
|
67
|
-
const chainConfig = getChainConfig(request.fromChain);
|
|
68
|
-
const nativeName = chainConfig?.nativeCurrency ?? 'ETH';
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
depositTxHash: '0x0' as `0x${string}`,
|
|
72
|
-
fromChain: request.fromChain,
|
|
73
|
-
toChain: request.toChain,
|
|
74
|
-
token: request.token,
|
|
75
|
-
amount: request.amount,
|
|
76
|
-
fee: 0n,
|
|
77
|
-
error: `Insufficient ${nativeName} for gas on ${request.fromChain}. Balance: ${nativeBalance} wei. Need at least 0.0001 ${nativeName}.`,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
// Non-critical — proceed and let the bridge itself surface the error
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log(`[bridge] Bridging via Across: ${request.fromChain} → ${request.toChain} (${request.amount})`);
|
|
85
|
-
return this.across.deposit(request);
|
|
86
|
-
}
|
|
87
|
-
}
|
package/src/bridge/index.ts
DELETED
package/src/bridge/types.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// ─── Bridge Config ──────────────────────────────────────────────
|
|
2
|
-
|
|
3
|
-
export interface BridgeConfig {
|
|
4
|
-
enabled: boolean;
|
|
5
|
-
defaultBridge: string;
|
|
6
|
-
maxBridgeValueUSD: number;
|
|
7
|
-
fillTimeoutMs: number;
|
|
8
|
-
pollIntervalMs: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const DEFAULT_BRIDGE_CONFIG: BridgeConfig = {
|
|
12
|
-
enabled: false,
|
|
13
|
-
defaultBridge: 'across',
|
|
14
|
-
maxBridgeValueUSD: 10_000,
|
|
15
|
-
fillTimeoutMs: 300_000, // 5 minutes
|
|
16
|
-
pollIntervalMs: 2_000, // 2 seconds
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// ─── Bridge Request & Results ───────────────────────────────────
|
|
20
|
-
|
|
21
|
-
export interface BridgeRequest {
|
|
22
|
-
token: `0x${string}`;
|
|
23
|
-
amount: bigint;
|
|
24
|
-
fromChain: string;
|
|
25
|
-
toChain: string;
|
|
26
|
-
recipient?: `0x${string}`;
|
|
27
|
-
bridge?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface BridgeFeeEstimate {
|
|
31
|
-
totalRelayFeePct: number;
|
|
32
|
-
capitalFeePct: number;
|
|
33
|
-
lpFeePct: number;
|
|
34
|
-
relayGasFeePct: number;
|
|
35
|
-
timestamp: number;
|
|
36
|
-
estimatedFillTimeSec: number;
|
|
37
|
-
isAmountTooLow: boolean;
|
|
38
|
-
outputAmount: bigint;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface BridgeResult {
|
|
42
|
-
success: boolean;
|
|
43
|
-
depositTxHash: `0x${string}`;
|
|
44
|
-
fromChain: string;
|
|
45
|
-
toChain: string;
|
|
46
|
-
token: `0x${string}`;
|
|
47
|
-
amount: bigint;
|
|
48
|
-
fee: bigint;
|
|
49
|
-
fillTxHash?: `0x${string}`;
|
|
50
|
-
fillTimestamp?: number;
|
|
51
|
-
error?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ─── Across SpokePool ABI Fragment ──────────────────────────────
|
|
55
|
-
|
|
56
|
-
export const ACROSS_SPOKE_POOL_ABI = [
|
|
57
|
-
{
|
|
58
|
-
type: 'function' as const,
|
|
59
|
-
name: 'depositV3' as const,
|
|
60
|
-
inputs: [
|
|
61
|
-
{ name: 'depositor', type: 'address' as const },
|
|
62
|
-
{ name: 'recipient', type: 'address' as const },
|
|
63
|
-
{ name: 'inputToken', type: 'address' as const },
|
|
64
|
-
{ name: 'outputToken', type: 'address' as const },
|
|
65
|
-
{ name: 'inputAmount', type: 'uint256' as const },
|
|
66
|
-
{ name: 'outputAmount', type: 'uint256' as const },
|
|
67
|
-
{ name: 'destinationChainId', type: 'uint256' as const },
|
|
68
|
-
{ name: 'exclusiveRelayer', type: 'address' as const },
|
|
69
|
-
{ name: 'quoteTimestamp', type: 'uint32' as const },
|
|
70
|
-
{ name: 'fillDeadline', type: 'uint32' as const },
|
|
71
|
-
{ name: 'exclusivityDeadline', type: 'uint32' as const },
|
|
72
|
-
{ name: 'message', type: 'bytes' as const },
|
|
73
|
-
],
|
|
74
|
-
outputs: [],
|
|
75
|
-
stateMutability: 'payable' as const,
|
|
76
|
-
},
|
|
77
|
-
] as const;
|