@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/src/spot/aerodrome.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { getChainConfig } from '../chains.js';
|
|
2
|
-
import type { SpotDEXClient } from './client.js';
|
|
3
|
-
import {
|
|
4
|
-
AERODROME_ROUTER_ABI,
|
|
5
|
-
AERODROME_DEFAULT_FACTORY,
|
|
6
|
-
type SpotSwapParams,
|
|
7
|
-
type SpotQuoteResult,
|
|
8
|
-
type SpotSwapResult,
|
|
9
|
-
} from './types.js';
|
|
10
|
-
|
|
11
|
-
export class AerodromeAdapter {
|
|
12
|
-
private client: SpotDEXClient;
|
|
13
|
-
|
|
14
|
-
constructor(client: SpotDEXClient) {
|
|
15
|
-
this.client = client;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async quote(params: SpotSwapParams): Promise<SpotQuoteResult> {
|
|
19
|
-
if (params.chain !== 'base') {
|
|
20
|
-
throw new Error('Aerodrome is only available on Base');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const chainConfig = getChainConfig('base')!;
|
|
24
|
-
const routerAddress = chainConfig.dexRouters.aerodrome;
|
|
25
|
-
if (!routerAddress) {
|
|
26
|
-
throw new Error('No Aerodrome router configured for Base');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const { publicClient } = this.client.getClients('base');
|
|
30
|
-
|
|
31
|
-
let bestAmountOut = 0n;
|
|
32
|
-
let bestStable = false;
|
|
33
|
-
|
|
34
|
-
// Try both stable and volatile pools
|
|
35
|
-
for (const stable of [true, false]) {
|
|
36
|
-
try {
|
|
37
|
-
const route = [
|
|
38
|
-
{
|
|
39
|
-
from: params.tokenIn,
|
|
40
|
-
to: params.tokenOut,
|
|
41
|
-
stable,
|
|
42
|
-
factory: AERODROME_DEFAULT_FACTORY,
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const amounts = await publicClient.readContract({
|
|
47
|
-
address: routerAddress,
|
|
48
|
-
abi: AERODROME_ROUTER_ABI,
|
|
49
|
-
functionName: 'getAmountsOut',
|
|
50
|
-
args: [params.amountIn, route],
|
|
51
|
-
}) as bigint[];
|
|
52
|
-
|
|
53
|
-
const amountOut = amounts[amounts.length - 1];
|
|
54
|
-
if (amountOut > bestAmountOut) {
|
|
55
|
-
bestAmountOut = amountOut;
|
|
56
|
-
bestStable = stable;
|
|
57
|
-
}
|
|
58
|
-
} catch {
|
|
59
|
-
// Pool doesn't exist for this type — skip
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (bestAmountOut === 0n) {
|
|
64
|
-
throw new Error(`No Aerodrome pool found for ${params.tokenIn}/${params.tokenOut}`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const tokenInSymbol = await this.client.getSymbol(params.tokenIn, 'base');
|
|
68
|
-
const tokenOutSymbol = await this.client.getSymbol(params.tokenOut, 'base');
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
amountOut: bestAmountOut,
|
|
72
|
-
stable: bestStable,
|
|
73
|
-
route: `${tokenInSymbol} → ${tokenOutSymbol} (${bestStable ? 'stable' : 'volatile'})`,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async swap(params: SpotSwapParams): Promise<SpotSwapResult> {
|
|
78
|
-
if (params.chain !== 'base') {
|
|
79
|
-
throw new Error('Aerodrome is only available on Base');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const chainConfig = getChainConfig('base')!;
|
|
83
|
-
const routerAddress = chainConfig.dexRouters.aerodrome;
|
|
84
|
-
if (!routerAddress) {
|
|
85
|
-
throw new Error('No Aerodrome router configured for Base');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Get quote for pool type selection and slippage calc
|
|
89
|
-
const quoteResult = await this.quote(params);
|
|
90
|
-
const amountOutMin = (quoteResult.amountOut * BigInt(10000 - params.slippageBps)) / 10000n;
|
|
91
|
-
|
|
92
|
-
// Ensure approval
|
|
93
|
-
await this.client.ensureApproval(params.tokenIn, routerAddress, params.amountIn, 'base');
|
|
94
|
-
|
|
95
|
-
const { publicClient, walletClient } = this.client.getClients('base');
|
|
96
|
-
const recipient = params.recipient ?? this.client.address;
|
|
97
|
-
|
|
98
|
-
const route = [
|
|
99
|
-
{
|
|
100
|
-
from: params.tokenIn,
|
|
101
|
-
to: params.tokenOut,
|
|
102
|
-
stable: quoteResult.stable!,
|
|
103
|
-
factory: AERODROME_DEFAULT_FACTORY,
|
|
104
|
-
},
|
|
105
|
-
];
|
|
106
|
-
|
|
107
|
-
// Deadline: 5 minutes from now
|
|
108
|
-
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300);
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const hash = await walletClient.writeContract({
|
|
112
|
-
address: routerAddress,
|
|
113
|
-
abi: AERODROME_ROUTER_ABI,
|
|
114
|
-
functionName: 'swapExactTokensForTokens',
|
|
115
|
-
args: [params.amountIn, amountOutMin, route, recipient, deadline],
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
119
|
-
|
|
120
|
-
// Calculate effective price
|
|
121
|
-
const tokenInDecimals = await this.client.getDecimals(params.tokenIn, 'base');
|
|
122
|
-
const tokenOutDecimals = await this.client.getDecimals(params.tokenOut, 'base');
|
|
123
|
-
const amountInFloat = Number(params.amountIn) / 10 ** tokenInDecimals;
|
|
124
|
-
const amountOutFloat = Number(quoteResult.amountOut) / 10 ** tokenOutDecimals;
|
|
125
|
-
const effectivePrice = amountOutFloat / amountInFloat;
|
|
126
|
-
|
|
127
|
-
const gasUsed = receipt.gasUsed ?? 0n;
|
|
128
|
-
const gasPrice = receipt.effectiveGasPrice ?? 0n;
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
success: true,
|
|
132
|
-
txHash: hash,
|
|
133
|
-
amountIn: params.amountIn,
|
|
134
|
-
amountOut: quoteResult.amountOut,
|
|
135
|
-
tokenIn: params.tokenIn,
|
|
136
|
-
tokenOut: params.tokenOut,
|
|
137
|
-
effectivePrice,
|
|
138
|
-
gasCost: gasUsed * gasPrice,
|
|
139
|
-
chain: 'base',
|
|
140
|
-
dex: 'aerodrome',
|
|
141
|
-
};
|
|
142
|
-
} catch (err) {
|
|
143
|
-
return {
|
|
144
|
-
success: false,
|
|
145
|
-
txHash: '0x0' as `0x${string}`,
|
|
146
|
-
amountIn: params.amountIn,
|
|
147
|
-
amountOut: 0n,
|
|
148
|
-
tokenIn: params.tokenIn,
|
|
149
|
-
tokenOut: params.tokenOut,
|
|
150
|
-
effectivePrice: 0,
|
|
151
|
-
gasCost: 0n,
|
|
152
|
-
chain: 'base',
|
|
153
|
-
dex: 'aerodrome',
|
|
154
|
-
error: (err as Error).message,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
package/src/spot/client.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createPublicClient,
|
|
3
|
-
createWalletClient,
|
|
4
|
-
http,
|
|
5
|
-
maxUint256,
|
|
6
|
-
type PublicClient,
|
|
7
|
-
type WalletClient,
|
|
8
|
-
type Transport,
|
|
9
|
-
type Chain,
|
|
10
|
-
type Account,
|
|
11
|
-
} from 'viem';
|
|
12
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
13
|
-
import { getChainConfig, type ChainConfig } from '../chains.js';
|
|
14
|
-
import { ERC20_ABI } from './types.js';
|
|
15
|
-
|
|
16
|
-
interface ChainClients {
|
|
17
|
-
publicClient: PublicClient<Transport, Chain>;
|
|
18
|
-
walletClient: WalletClient<Transport, Chain, Account>;
|
|
19
|
-
chainConfig: ChainConfig;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class SpotDEXClient {
|
|
23
|
-
private account: ReturnType<typeof privateKeyToAccount>;
|
|
24
|
-
private clients: Map<string, ChainClients> = new Map();
|
|
25
|
-
private decimalsCache: Map<string, number> = new Map();
|
|
26
|
-
private symbolCache: Map<string, string> = new Map();
|
|
27
|
-
|
|
28
|
-
constructor(privateKey: string) {
|
|
29
|
-
this.account = privateKeyToAccount(privateKey as `0x${string}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get address(): `0x${string}` {
|
|
33
|
-
return this.account.address;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getClients(chainName: string): ChainClients {
|
|
37
|
-
const cached = this.clients.get(chainName);
|
|
38
|
-
if (cached) return cached;
|
|
39
|
-
|
|
40
|
-
const chainConfig = getChainConfig(chainName);
|
|
41
|
-
if (!chainConfig) {
|
|
42
|
-
throw new Error(`Unknown chain: ${chainName}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const publicClient = createPublicClient({
|
|
46
|
-
chain: chainConfig.viemChain,
|
|
47
|
-
transport: http(chainConfig.rpcUrl),
|
|
48
|
-
}) as PublicClient<Transport, Chain>;
|
|
49
|
-
|
|
50
|
-
const walletClient = createWalletClient({
|
|
51
|
-
account: this.account,
|
|
52
|
-
chain: chainConfig.viemChain,
|
|
53
|
-
transport: http(chainConfig.rpcUrl),
|
|
54
|
-
}) as WalletClient<Transport, Chain, Account>;
|
|
55
|
-
|
|
56
|
-
const entry: ChainClients = { publicClient, walletClient, chainConfig };
|
|
57
|
-
this.clients.set(chainName, entry);
|
|
58
|
-
return entry;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async getDecimals(token: `0x${string}`, chainName: string): Promise<number> {
|
|
62
|
-
const key = `${chainName}:${token}`;
|
|
63
|
-
const cached = this.decimalsCache.get(key);
|
|
64
|
-
if (cached !== undefined) return cached;
|
|
65
|
-
|
|
66
|
-
const { publicClient } = this.getClients(chainName);
|
|
67
|
-
const decimals = await publicClient.readContract({
|
|
68
|
-
address: token,
|
|
69
|
-
abi: ERC20_ABI,
|
|
70
|
-
functionName: 'decimals',
|
|
71
|
-
}) as number;
|
|
72
|
-
|
|
73
|
-
this.decimalsCache.set(key, decimals);
|
|
74
|
-
return decimals;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async getSymbol(token: `0x${string}`, chainName: string): Promise<string> {
|
|
78
|
-
const key = `${chainName}:${token}`;
|
|
79
|
-
const cached = this.symbolCache.get(key);
|
|
80
|
-
if (cached) return cached;
|
|
81
|
-
|
|
82
|
-
const { publicClient } = this.getClients(chainName);
|
|
83
|
-
const symbol = await publicClient.readContract({
|
|
84
|
-
address: token,
|
|
85
|
-
abi: ERC20_ABI,
|
|
86
|
-
functionName: 'symbol',
|
|
87
|
-
}) as string;
|
|
88
|
-
|
|
89
|
-
this.symbolCache.set(key, symbol);
|
|
90
|
-
return symbol;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async getBalance(token: `0x${string}`, chainName: string): Promise<bigint> {
|
|
94
|
-
const { publicClient } = this.getClients(chainName);
|
|
95
|
-
return await publicClient.readContract({
|
|
96
|
-
address: token,
|
|
97
|
-
abi: ERC20_ABI,
|
|
98
|
-
functionName: 'balanceOf',
|
|
99
|
-
args: [this.account.address],
|
|
100
|
-
}) as bigint;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Get the native token balance (ETH, POL, etc.) on a chain */
|
|
104
|
-
async getNativeBalance(chainName: string): Promise<bigint> {
|
|
105
|
-
const { publicClient } = this.getClients(chainName);
|
|
106
|
-
return await publicClient.getBalance({ address: this.account.address });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async ensureApproval(
|
|
110
|
-
token: `0x${string}`,
|
|
111
|
-
spender: `0x${string}`,
|
|
112
|
-
amount: bigint,
|
|
113
|
-
chainName: string,
|
|
114
|
-
): Promise<`0x${string}` | null> {
|
|
115
|
-
const { publicClient, walletClient } = this.getClients(chainName);
|
|
116
|
-
|
|
117
|
-
const allowance = await publicClient.readContract({
|
|
118
|
-
address: token,
|
|
119
|
-
abi: ERC20_ABI,
|
|
120
|
-
functionName: 'allowance',
|
|
121
|
-
args: [this.account.address, spender],
|
|
122
|
-
}) as bigint;
|
|
123
|
-
|
|
124
|
-
if (allowance >= amount) return null;
|
|
125
|
-
|
|
126
|
-
// Approve max to avoid repeated approvals
|
|
127
|
-
const hash = await walletClient.writeContract({
|
|
128
|
-
address: token,
|
|
129
|
-
abi: ERC20_ABI,
|
|
130
|
-
functionName: 'approve',
|
|
131
|
-
args: [spender, maxUint256],
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
await publicClient.waitForTransactionReceipt({ hash });
|
|
135
|
-
console.log(`[spot] Approved ${token} for ${spender} on ${chainName} (tx: ${hash})`);
|
|
136
|
-
return hash;
|
|
137
|
-
}
|
|
138
|
-
}
|
package/src/spot/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { SpotDEXClient } from './client.js';
|
|
2
|
-
export { UniswapAdapter } from './uniswap.js';
|
|
3
|
-
export { AerodromeAdapter } from './aerodrome.js';
|
|
4
|
-
export { SpotSwapManager } from './swap-manager.js';
|
|
5
|
-
export type {
|
|
6
|
-
SpotConfig,
|
|
7
|
-
SpotSwapParams,
|
|
8
|
-
SpotQuoteResult,
|
|
9
|
-
SpotSwapResult,
|
|
10
|
-
} from './types.js';
|
|
11
|
-
export { DEFAULT_SPOT_CONFIG } from './types.js';
|
package/src/spot/swap-manager.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { getChainConfig } from '../chains.js';
|
|
2
|
-
import { SpotDEXClient } from './client.js';
|
|
3
|
-
import { UniswapAdapter } from './uniswap.js';
|
|
4
|
-
import { AerodromeAdapter } from './aerodrome.js';
|
|
5
|
-
import type { SpotConfig, SpotSwapParams, SpotQuoteResult, SpotSwapResult } from './types.js';
|
|
6
|
-
|
|
7
|
-
/** Well-known token symbols → addresses per chain */
|
|
8
|
-
const TOKEN_ALIASES: Record<string, Record<string, `0x${string}`>> = {
|
|
9
|
-
base: {
|
|
10
|
-
ETH: '0x4200000000000000000000000000000000000006',
|
|
11
|
-
WETH: '0x4200000000000000000000000000000000000006',
|
|
12
|
-
USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
13
|
-
USDbC: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
|
|
14
|
-
DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
|
|
15
|
-
},
|
|
16
|
-
ethereum: {
|
|
17
|
-
ETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
|
18
|
-
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
|
19
|
-
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
20
|
-
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
21
|
-
DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
22
|
-
},
|
|
23
|
-
arbitrum: {
|
|
24
|
-
ETH: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
|
|
25
|
-
WETH: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
|
|
26
|
-
USDC: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
|
27
|
-
'USDC.e': '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
|
|
28
|
-
USDT: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
|
|
29
|
-
},
|
|
30
|
-
polygon: {
|
|
31
|
-
POL: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
|
|
32
|
-
WPOL: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
|
|
33
|
-
WMATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
|
|
34
|
-
USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
|
|
35
|
-
'USDC.e': '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
|
|
36
|
-
USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export class SpotSwapManager {
|
|
41
|
-
private dexClient: SpotDEXClient;
|
|
42
|
-
private uniswap: UniswapAdapter;
|
|
43
|
-
private aerodrome: AerodromeAdapter;
|
|
44
|
-
private config: SpotConfig;
|
|
45
|
-
|
|
46
|
-
constructor(privateKey: string, config: SpotConfig) {
|
|
47
|
-
this.config = config;
|
|
48
|
-
this.dexClient = new SpotDEXClient(privateKey);
|
|
49
|
-
this.uniswap = new UniswapAdapter(this.dexClient);
|
|
50
|
-
this.aerodrome = new AerodromeAdapter(this.dexClient);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get client(): SpotDEXClient {
|
|
54
|
-
return this.dexClient;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Resolve a token symbol or address to a hex address */
|
|
58
|
-
resolveToken(tokenOrSymbol: string, chain: string): `0x${string}` {
|
|
59
|
-
if (tokenOrSymbol.startsWith('0x') && tokenOrSymbol.length === 42) {
|
|
60
|
-
return tokenOrSymbol as `0x${string}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const chainAliases = TOKEN_ALIASES[chain];
|
|
64
|
-
if (chainAliases) {
|
|
65
|
-
const upper = tokenOrSymbol.toUpperCase();
|
|
66
|
-
const address = chainAliases[upper] ?? chainAliases[tokenOrSymbol];
|
|
67
|
-
if (address) return address;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Try WETH/USDC from chain config
|
|
71
|
-
const chainConfig = getChainConfig(chain);
|
|
72
|
-
if (chainConfig) {
|
|
73
|
-
const upper = tokenOrSymbol.toUpperCase();
|
|
74
|
-
if (upper === 'WETH' || upper === 'ETH') return chainConfig.wethAddress;
|
|
75
|
-
if (upper === 'USDC') return chainConfig.usdcAddress;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
throw new Error(`Cannot resolve token "${tokenOrSymbol}" on ${chain}. Provide contract address.`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Select DEX: explicit choice, or auto-detect */
|
|
82
|
-
private selectDex(chain: string, dex?: string): 'uniswap' | 'aerodrome' {
|
|
83
|
-
if (dex) return dex as 'uniswap' | 'aerodrome';
|
|
84
|
-
|
|
85
|
-
// Prefer Aerodrome on Base (lower fees, deeper liquidity for common pairs)
|
|
86
|
-
const chainConfig = getChainConfig(chain);
|
|
87
|
-
if (chain === 'base' && chainConfig?.dexRouters.aerodrome) {
|
|
88
|
-
return 'aerodrome';
|
|
89
|
-
}
|
|
90
|
-
return 'uniswap';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async getQuote(params: {
|
|
94
|
-
tokenIn: string;
|
|
95
|
-
tokenOut: string;
|
|
96
|
-
amountIn: bigint;
|
|
97
|
-
chain?: string;
|
|
98
|
-
dex?: string;
|
|
99
|
-
}): Promise<SpotQuoteResult> {
|
|
100
|
-
const chain = params.chain ?? this.config.defaultChain;
|
|
101
|
-
const dex = this.selectDex(chain, params.dex);
|
|
102
|
-
|
|
103
|
-
const swapParams: SpotSwapParams = {
|
|
104
|
-
tokenIn: this.resolveToken(params.tokenIn, chain),
|
|
105
|
-
tokenOut: this.resolveToken(params.tokenOut, chain),
|
|
106
|
-
amountIn: params.amountIn,
|
|
107
|
-
slippageBps: this.config.maxSlippageBps,
|
|
108
|
-
chain,
|
|
109
|
-
dex,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
if (dex === 'aerodrome') {
|
|
113
|
-
return this.aerodrome.quote(swapParams);
|
|
114
|
-
}
|
|
115
|
-
return this.uniswap.quote(swapParams);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async executeSwap(params: {
|
|
119
|
-
tokenIn: string;
|
|
120
|
-
tokenOut: string;
|
|
121
|
-
amountIn: bigint;
|
|
122
|
-
chain?: string;
|
|
123
|
-
dex?: string;
|
|
124
|
-
slippageBps?: number;
|
|
125
|
-
}): Promise<SpotSwapResult> {
|
|
126
|
-
const chain = params.chain ?? this.config.defaultChain;
|
|
127
|
-
const dex = this.selectDex(chain, params.dex);
|
|
128
|
-
|
|
129
|
-
// Enforce max swap value cap
|
|
130
|
-
// amountIn is in token decimals — we need to estimate USD value.
|
|
131
|
-
// For stablecoins (6 decimals), amountIn / 1e6 ≈ USD value.
|
|
132
|
-
// For non-stables, we can't precisely compute USD without a price feed,
|
|
133
|
-
// so we use a conservative estimate: assume the token could be worth up to $100k/unit.
|
|
134
|
-
// The risk manager's filterSignals() already checks signal-level USD values before
|
|
135
|
-
// we get here, so this is a belt-and-suspenders check using the config cap.
|
|
136
|
-
const tokenInAddress = this.resolveToken(params.tokenIn, chain);
|
|
137
|
-
try {
|
|
138
|
-
const decimals = await this.dexClient.getDecimals(tokenInAddress, chain);
|
|
139
|
-
const amountFloat = Number(params.amountIn) / 10 ** decimals;
|
|
140
|
-
// For stablecoins, 1 token ≈ $1. For others, this is approximate.
|
|
141
|
-
// A more precise check would need a price oracle, but the runtime's risk filter
|
|
142
|
-
// already validates USD values before calling executeSwap().
|
|
143
|
-
if (amountFloat > this.config.maxSwapValueUSD) {
|
|
144
|
-
return {
|
|
145
|
-
success: false,
|
|
146
|
-
txHash: '0x0' as `0x${string}`,
|
|
147
|
-
amountIn: params.amountIn,
|
|
148
|
-
amountOut: 0n,
|
|
149
|
-
tokenIn: '0x0' as `0x${string}`,
|
|
150
|
-
tokenOut: '0x0' as `0x${string}`,
|
|
151
|
-
effectivePrice: 0,
|
|
152
|
-
gasCost: 0n,
|
|
153
|
-
chain,
|
|
154
|
-
dex,
|
|
155
|
-
error: `Swap value ${amountFloat.toFixed(2)} tokens exceeds max $${this.config.maxSwapValueUSD}. Reduce size or increase maxSwapValueUSD.`,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
} catch {
|
|
159
|
-
// If we can't check decimals, proceed — let the swap itself validate
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (!this.config.chains.includes(chain)) {
|
|
163
|
-
return {
|
|
164
|
-
success: false,
|
|
165
|
-
txHash: '0x0' as `0x${string}`,
|
|
166
|
-
amountIn: params.amountIn,
|
|
167
|
-
amountOut: 0n,
|
|
168
|
-
tokenIn: '0x0' as `0x${string}`,
|
|
169
|
-
tokenOut: '0x0' as `0x${string}`,
|
|
170
|
-
effectivePrice: 0,
|
|
171
|
-
gasCost: 0n,
|
|
172
|
-
chain,
|
|
173
|
-
dex,
|
|
174
|
-
error: `Chain "${chain}" not enabled in spot config (enabled: ${this.config.chains.join(', ')})`,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Pre-check native gas balance — swaps revert with opaque errors if wallet has 0 gas
|
|
179
|
-
const MIN_GAS_WEI = 100_000_000_000_000n; // 0.0001 ETH/POL
|
|
180
|
-
try {
|
|
181
|
-
const nativeBalance = await this.dexClient.getNativeBalance(chain);
|
|
182
|
-
if (nativeBalance < MIN_GAS_WEI) {
|
|
183
|
-
const chainConfig = getChainConfig(chain);
|
|
184
|
-
const nativeName = chainConfig?.nativeCurrency ?? 'ETH';
|
|
185
|
-
return {
|
|
186
|
-
success: false,
|
|
187
|
-
txHash: '0x0' as `0x${string}`,
|
|
188
|
-
amountIn: params.amountIn,
|
|
189
|
-
amountOut: 0n,
|
|
190
|
-
tokenIn: '0x0' as `0x${string}`,
|
|
191
|
-
tokenOut: '0x0' as `0x${string}`,
|
|
192
|
-
effectivePrice: 0,
|
|
193
|
-
gasCost: 0n,
|
|
194
|
-
chain,
|
|
195
|
-
dex,
|
|
196
|
-
error: `Insufficient ${nativeName} for gas on ${chain}. Balance: ${nativeBalance} wei. Need at least 0.0001 ${nativeName}. Bridge gas to ${chain} before swapping.`,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
// Non-critical — proceed and let the swap itself surface the error
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const swapParams: SpotSwapParams = {
|
|
204
|
-
tokenIn: this.resolveToken(params.tokenIn, chain),
|
|
205
|
-
tokenOut: this.resolveToken(params.tokenOut, chain),
|
|
206
|
-
amountIn: params.amountIn,
|
|
207
|
-
slippageBps: params.slippageBps ?? this.config.maxSlippageBps,
|
|
208
|
-
chain,
|
|
209
|
-
dex,
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
console.log(`[spot] Swapping on ${dex}/${chain}: ${params.tokenIn} → ${params.tokenOut} (${params.amountIn})`);
|
|
213
|
-
|
|
214
|
-
if (dex === 'aerodrome') {
|
|
215
|
-
return this.aerodrome.swap(swapParams);
|
|
216
|
-
}
|
|
217
|
-
return this.uniswap.swap(swapParams);
|
|
218
|
-
}
|
|
219
|
-
}
|