@guru-fund/sdk 0.2.3 → 0.2.5
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/quotes/quoteDeposit.d.ts +7 -0
- package/dist/quotes/quoteDeposit.js +148 -38
- package/dist/router/constants.d.ts +1 -1
- package/dist/router/constants.js +1 -0
- package/dist/router/getUniswapV4Route.d.ts +2 -1
- package/dist/router/getUniswapV4Route.js +8 -4
- package/dist/router/getVeloraRoute.js +2 -1
- package/dist/router/index.js +171 -22
- package/dist/router/pathCache.js +10 -2
- package/dist/router/poolHelper.js +9 -1
- package/dist/router/types.d.ts +2 -0
- package/dist/router/velora.d.ts +3 -1
- package/dist/router/velora.js +61 -13
- package/package.json +1 -1
|
@@ -37,5 +37,12 @@ export interface QuoteDepositResult {
|
|
|
37
37
|
txData: TransactionRequest;
|
|
38
38
|
decodeLogs: (logs: SimulationLog[]) => QuoteDepositLogs | null;
|
|
39
39
|
}
|
|
40
|
+
type DepositAllocationQuote = {
|
|
41
|
+
asset: Fund.Asset;
|
|
42
|
+
amountIn: bigint;
|
|
43
|
+
amountOut: bigint;
|
|
44
|
+
};
|
|
40
45
|
export declare function allocateDepositInputAmounts(assets: Fund.Asset[], totalAmount: bigint, totalValueLocked: bigint): Map<string, bigint>;
|
|
46
|
+
export declare function rebalanceDepositInputAmounts(quotes: DepositAllocationQuote[], totalAmount: bigint): Map<string, bigint>;
|
|
41
47
|
export default function quoteDeposit(params: QuoteDepositParams, ctx: QuoteDepositContext): Promise<QuoteDepositResult>;
|
|
48
|
+
export {};
|
|
@@ -8,28 +8,30 @@ import { quoteDepositSchema } from '../schemas/quoteDeposit.js';
|
|
|
8
8
|
import buildDepositTx from '../txBuilders/buildDepositTx.js';
|
|
9
9
|
import { FundLedger__factory } from '../typechain/index.js';
|
|
10
10
|
const MAX_UINT256 = (1n << 256n) - 1n;
|
|
11
|
+
const MIN_ROUTE_INPUT_AMOUNT = 2n;
|
|
12
|
+
const ROUTE_AWARE_ALLOCATION_PASSES = 1;
|
|
11
13
|
const minBigint = (a, b) => (a < b ? a : b);
|
|
12
14
|
const slippageE2For = (settings, token) => {
|
|
13
15
|
const slippage = settings?.[token.toLowerCase()];
|
|
14
16
|
return slippage == null ? undefined : Number(BigInt(slippage) / 10n);
|
|
15
17
|
};
|
|
16
18
|
const assetUsd1e18Value = (asset) => (asset.usd1e18Price * asset.balance) / Token.unitFor(asset.token.decimals);
|
|
17
|
-
|
|
19
|
+
function allocateWeightedInputAmounts(items, totalAmount) {
|
|
18
20
|
const allocations = new Map();
|
|
19
|
-
if (totalAmount === 0n
|
|
20
|
-
for (const
|
|
21
|
-
allocations.set(
|
|
22
|
-
}
|
|
21
|
+
if (totalAmount === 0n) {
|
|
22
|
+
for (const item of items)
|
|
23
|
+
allocations.set(item.key, 0n);
|
|
23
24
|
return allocations;
|
|
24
25
|
}
|
|
25
|
-
const weighted =
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const amount = weightedAmount / totalValueLocked;
|
|
26
|
+
const weighted = items.map((item) => {
|
|
27
|
+
const weightedAmount = totalAmount * item.weight;
|
|
28
|
+
const amount = item.denominator === 0n ? 0n : weightedAmount / item.denominator;
|
|
29
29
|
return {
|
|
30
|
-
|
|
30
|
+
...item,
|
|
31
31
|
amount,
|
|
32
|
-
remainder:
|
|
32
|
+
remainder: item.denominator === 0n
|
|
33
|
+
? 0n
|
|
34
|
+
: weightedAmount % item.denominator,
|
|
33
35
|
};
|
|
34
36
|
});
|
|
35
37
|
const allocated = weighted.reduce((sum, item) => sum + item.amount, 0n);
|
|
@@ -38,11 +40,116 @@ export function allocateDepositInputAmounts(assets, totalAmount, totalValueLocke
|
|
|
38
40
|
.sort((a, b) => a.remainder === b.remainder ? 0 : a.remainder > b.remainder ? -1 : 1)
|
|
39
41
|
.forEach((item) => {
|
|
40
42
|
const extra = remainder > 0n ? 1n : 0n;
|
|
41
|
-
allocations.set(item.
|
|
43
|
+
allocations.set(item.key, item.amount + extra);
|
|
42
44
|
remainder -= extra;
|
|
43
45
|
});
|
|
46
|
+
const zeroRoundedAssets = weighted
|
|
47
|
+
.filter((item) => item.balance > 0n && (allocations.get(item.key) ?? 0n) === 0n)
|
|
48
|
+
.sort((a, b) => a.remainder === b.remainder ? 0 : a.remainder > b.remainder ? -1 : 1);
|
|
49
|
+
for (const item of zeroRoundedAssets) {
|
|
50
|
+
const donor = weighted
|
|
51
|
+
.filter((candidate) => candidate.key !== item.key)
|
|
52
|
+
.sort((a, b) => {
|
|
53
|
+
const aAmount = allocations.get(a.key) ?? 0n;
|
|
54
|
+
const bAmount = allocations.get(b.key) ?? 0n;
|
|
55
|
+
return aAmount === bAmount ? 0 : aAmount > bAmount ? -1 : 1;
|
|
56
|
+
})
|
|
57
|
+
.find((candidate) => (allocations.get(candidate.key) ?? 0n) >
|
|
58
|
+
MIN_ROUTE_INPUT_AMOUNT);
|
|
59
|
+
if (!donor)
|
|
60
|
+
break;
|
|
61
|
+
allocations.set(donor.key, (allocations.get(donor.key) ?? 0n) - MIN_ROUTE_INPUT_AMOUNT);
|
|
62
|
+
allocations.set(item.key, MIN_ROUTE_INPUT_AMOUNT);
|
|
63
|
+
}
|
|
44
64
|
return allocations;
|
|
45
65
|
}
|
|
66
|
+
export function allocateDepositInputAmounts(assets, totalAmount, totalValueLocked) {
|
|
67
|
+
if (totalAmount === 0n || totalValueLocked === 0n) {
|
|
68
|
+
const allocations = new Map();
|
|
69
|
+
for (const asset of assets) {
|
|
70
|
+
allocations.set(asset.token.address.toLowerCase(), 0n);
|
|
71
|
+
}
|
|
72
|
+
return allocations;
|
|
73
|
+
}
|
|
74
|
+
return allocateWeightedInputAmounts(assets.map((asset) => ({
|
|
75
|
+
key: asset.token.address.toLowerCase(),
|
|
76
|
+
weight: assetUsd1e18Value(asset),
|
|
77
|
+
denominator: totalValueLocked,
|
|
78
|
+
balance: asset.balance,
|
|
79
|
+
})), totalAmount);
|
|
80
|
+
}
|
|
81
|
+
export function rebalanceDepositInputAmounts(quotes, totalAmount) {
|
|
82
|
+
let denominator = 0n;
|
|
83
|
+
const weights = quotes.map(({ asset, amountIn, amountOut }) => {
|
|
84
|
+
let weight = 0n;
|
|
85
|
+
if (asset.balance > 0n && amountIn > 0n && amountOut > 0n) {
|
|
86
|
+
const inputRatio = (amountOut * UNIT) / asset.balance;
|
|
87
|
+
if (inputRatio > 0n) {
|
|
88
|
+
weight = (amountIn * UNIT) / inputRatio;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
denominator += weight;
|
|
92
|
+
return {
|
|
93
|
+
key: asset.token.address.toLowerCase(),
|
|
94
|
+
weight,
|
|
95
|
+
denominator: 0n,
|
|
96
|
+
balance: asset.balance,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (denominator === 0n) {
|
|
100
|
+
return new Map(quotes.map(({ asset, amountIn }) => [
|
|
101
|
+
asset.token.address.toLowerCase(),
|
|
102
|
+
amountIn,
|
|
103
|
+
]));
|
|
104
|
+
}
|
|
105
|
+
return allocateWeightedInputAmounts(weights.map((item) => ({ ...item, denominator })), totalAmount);
|
|
106
|
+
}
|
|
107
|
+
function allocationsEqual(a, b) {
|
|
108
|
+
if (a.size !== b.size)
|
|
109
|
+
return false;
|
|
110
|
+
for (const [key, value] of a) {
|
|
111
|
+
if (b.get(key) !== value)
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
async function quoteDepositAllocations({ inputAllocations, coinData, fundData, parsed, ctx, vault, }) {
|
|
117
|
+
const allocationQuotes = [];
|
|
118
|
+
if (coinData) {
|
|
119
|
+
const amountIn = inputAllocations.get(coinData.token.address.toLowerCase()) ?? 0n;
|
|
120
|
+
if (coinData.balance > 0n && amountIn === 0n) {
|
|
121
|
+
throw new Error(`Deposit amount is too small to allocate input for asset ${coinData.token.address}`);
|
|
122
|
+
}
|
|
123
|
+
allocationQuotes.push({
|
|
124
|
+
asset: coinData,
|
|
125
|
+
amountIn,
|
|
126
|
+
amountOut: amountIn,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const assetsToSwap = fundData.assets.filter((asset) => !compareAddresses(asset.token.address, parsed.coin));
|
|
130
|
+
const routeResults = await Promise.all(assetsToSwap.map(async (asset) => {
|
|
131
|
+
const amountIn = inputAllocations.get(asset.token.address.toLowerCase()) ?? 0n;
|
|
132
|
+
if (asset.balance > 0n && amountIn === 0n) {
|
|
133
|
+
throw new Error(`Deposit amount is too small to allocate input for asset ${asset.token.address}`);
|
|
134
|
+
}
|
|
135
|
+
const route = await getRouteIn({
|
|
136
|
+
chainId: ctx.chainId,
|
|
137
|
+
tokenIn: parsed.coin,
|
|
138
|
+
tokenOut: asset.token.address,
|
|
139
|
+
amountIn,
|
|
140
|
+
account: parsed.account,
|
|
141
|
+
vault,
|
|
142
|
+
slippageE2: slippageE2For(parsed.slippageSettings, asset.token.address),
|
|
143
|
+
}, ctx);
|
|
144
|
+
allocationQuotes.push({
|
|
145
|
+
asset,
|
|
146
|
+
amountIn,
|
|
147
|
+
amountOut: BigInt(route.data.amountToReceive),
|
|
148
|
+
});
|
|
149
|
+
return { asset, amountIn, route };
|
|
150
|
+
}));
|
|
151
|
+
return { allocationQuotes, routeResults };
|
|
152
|
+
}
|
|
46
153
|
export default async function quoteDeposit(params, ctx) {
|
|
47
154
|
const parsed = quoteDepositSchema.parse(params);
|
|
48
155
|
if (parsed.referrerFeeBps < 0n || parsed.referrerFeeBps > MAX_BPS) {
|
|
@@ -65,32 +172,36 @@ export default async function quoteDeposit(params, ctx) {
|
|
|
65
172
|
let fees = (originalAmount * referrerFeeBps) / MAX_BPS;
|
|
66
173
|
const adjustedAmount = originalAmount - fees;
|
|
67
174
|
const coinData = fundData.assets.find((asset) => compareAddresses(asset.token.address, parsed.coin));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
amountIn,
|
|
89
|
-
account: parsed.account,
|
|
175
|
+
let inputAllocations = allocateDepositInputAmounts(fundData.assets, adjustedAmount, fundData.totalValueLocked);
|
|
176
|
+
let quoted = await quoteDepositAllocations({
|
|
177
|
+
inputAllocations,
|
|
178
|
+
coinData,
|
|
179
|
+
fundData,
|
|
180
|
+
parsed,
|
|
181
|
+
ctx,
|
|
182
|
+
vault,
|
|
183
|
+
});
|
|
184
|
+
for (let i = 0; i < ROUTE_AWARE_ALLOCATION_PASSES; i++) {
|
|
185
|
+
const rebalanced = rebalanceDepositInputAmounts(quoted.allocationQuotes, adjustedAmount);
|
|
186
|
+
if (allocationsEqual(inputAllocations, rebalanced))
|
|
187
|
+
break;
|
|
188
|
+
inputAllocations = rebalanced;
|
|
189
|
+
quoted = await quoteDepositAllocations({
|
|
190
|
+
inputAllocations,
|
|
191
|
+
coinData,
|
|
192
|
+
fundData,
|
|
193
|
+
parsed,
|
|
194
|
+
ctx,
|
|
90
195
|
vault,
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
let lowestInputRatio = MAX_UINT256;
|
|
199
|
+
for (const { asset, amountOut } of quoted.allocationQuotes) {
|
|
200
|
+
if (asset.balance === 0n)
|
|
201
|
+
continue;
|
|
202
|
+
lowestInputRatio = minBigint(lowestInputRatio, (amountOut * UNIT) / asset.balance);
|
|
203
|
+
}
|
|
204
|
+
const routeResults = quoted.routeResults;
|
|
94
205
|
const coinToken = await new Token(parsed.coin, provider).metadata();
|
|
95
206
|
const coinUsd1e18Price = await ctx.getPriceUsd1e18(parsed.coin);
|
|
96
207
|
const externalTokenMetadata = new Map();
|
|
@@ -142,7 +253,6 @@ export default async function quoteDeposit(params, ctx) {
|
|
|
142
253
|
callData: route.callData,
|
|
143
254
|
});
|
|
144
255
|
fees += await tollToCoinUnits(route.toll.amount, route.toll.currency);
|
|
145
|
-
lowestInputRatio = minBigint(lowestInputRatio, (BigInt(route.data.amountToReceive) * UNIT) / asset.balance);
|
|
146
256
|
const bps = route.effectiveSlippageBps;
|
|
147
257
|
if (bps != null) {
|
|
148
258
|
const bpsN = BigInt(bps);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const VELORA_ENDPOINT = "https://api.velora.xyz/swap";
|
|
2
|
-
export declare const SUPPORTED_DEXS: readonly ["UniswapV2", "UniswapV3", "UniswapV4", "AerodromeV2", "PancakeSwapV2", "PancakeSwapV3"];
|
|
2
|
+
export declare const SUPPORTED_DEXS: readonly ["UniswapV2", "UniswapV3", "UniswapV4", "AerodromeV3", "AerodromeV2", "PancakeSwapV2", "PancakeSwapV3"];
|
|
3
3
|
export declare const VELORA_VERSION_BY_DEX: Partial<Record<SupportedDex, string>>;
|
|
4
4
|
export type SupportedDex = (typeof SUPPORTED_DEXS)[number];
|
|
5
5
|
export declare const INITIAL_SLIPPAGE_E3 = 500n;
|
package/dist/router/constants.js
CHANGED
|
@@ -15,6 +15,7 @@ export declare function connectV4Quoter(address: string, provider: Provider): V4
|
|
|
15
15
|
export declare function toAdapterPathKeys(path: V4Path): SwapV4Struct['path'];
|
|
16
16
|
type V4QuoteSource = 'quoter' | 'adapter-preview';
|
|
17
17
|
export interface ApplyV4TollParams {
|
|
18
|
+
tollIn: boolean;
|
|
18
19
|
baseToll: {
|
|
19
20
|
currency: string;
|
|
20
21
|
amount: bigint;
|
|
@@ -32,7 +33,7 @@ export interface ApplyV4TollResult {
|
|
|
32
33
|
amountToSend: bigint;
|
|
33
34
|
initialTollAmount: bigint;
|
|
34
35
|
}
|
|
35
|
-
export declare function applyV4Toll({ baseToll, quotedAmountToReceive, quoteSource, swapAmountIn, }: ApplyV4TollParams): ApplyV4TollResult;
|
|
36
|
+
export declare function applyV4Toll({ tollIn, baseToll, quotedAmountToReceive, quoteSource, swapAmountIn, }: ApplyV4TollParams): ApplyV4TollResult;
|
|
36
37
|
export declare function toHookAdapterPathKeys(path: V4Path): V4HookPathKey[];
|
|
37
38
|
export interface GetUniswapV4RouteContext {
|
|
38
39
|
provider: Provider;
|
|
@@ -45,15 +45,17 @@ function isV4HookLockedError(reason) {
|
|
|
45
45
|
}
|
|
46
46
|
return false;
|
|
47
47
|
}
|
|
48
|
-
export function applyV4Toll({ baseToll, quotedAmountToReceive, quoteSource, swapAmountIn, }) {
|
|
48
|
+
export function applyV4Toll({ tollIn, baseToll, quotedAmountToReceive, quoteSource, swapAmountIn, }) {
|
|
49
49
|
const toll = { ...baseToll };
|
|
50
50
|
let amountQuoted = quotedAmountToReceive;
|
|
51
51
|
let amountToSend = swapAmountIn;
|
|
52
52
|
let initialTollAmount = toll.amount;
|
|
53
53
|
if (toll.amount === 0n) {
|
|
54
54
|
if (quoteSource === 'quoter') {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
if (!tollIn) {
|
|
56
|
+
toll.amount = quotedAmountToReceive / TOLL_DIVISOR_20BPS;
|
|
57
|
+
amountQuoted -= toll.amount;
|
|
58
|
+
}
|
|
57
59
|
}
|
|
58
60
|
initialTollAmount = 0n;
|
|
59
61
|
}
|
|
@@ -194,7 +196,7 @@ export default async function getUniswapV4Route(params, ctx) {
|
|
|
194
196
|
throw new Error(`UniswapV4 not supported on chainId ${chainId}`);
|
|
195
197
|
}
|
|
196
198
|
const maxSlippageE3 = slippageE2 != null ? BigInt(slippageE2) * 10n : undefined;
|
|
197
|
-
const { baseToll, swapAmountIn } = resolveBaseToll(addresses, tokenIn, tokenOut, amountIn);
|
|
199
|
+
const { tollIn, baseToll, swapAmountIn } = resolveBaseToll(addresses, tokenIn, tokenOut, amountIn);
|
|
198
200
|
const candidates = await discoverV4Paths(chainId, tokenIn, tokenOut, provider);
|
|
199
201
|
if (candidates.length === 0) {
|
|
200
202
|
throw new Error('NO_UNISWAP_V4_ROUTES_FOUND');
|
|
@@ -254,6 +256,7 @@ export default async function getUniswapV4Route(params, ctx) {
|
|
|
254
256
|
const selectedAdapter = bestQuote.adapter ?? adapter;
|
|
255
257
|
const bestPath = bestQuote.path;
|
|
256
258
|
const { toll, amountQuoted, amountToSend, initialTollAmount } = applyV4Toll({
|
|
259
|
+
tollIn,
|
|
257
260
|
baseToll,
|
|
258
261
|
quotedAmountToReceive: bestQuote.amountToReceive,
|
|
259
262
|
quoteSource: bestQuote.source,
|
|
@@ -296,6 +299,7 @@ export default async function getUniswapV4Route(params, ctx) {
|
|
|
296
299
|
amountToSend,
|
|
297
300
|
amountQuoted,
|
|
298
301
|
initialTollAmount,
|
|
302
|
+
outputTollE3: tollIn ? 0n : undefined,
|
|
299
303
|
maxSlippageE3,
|
|
300
304
|
});
|
|
301
305
|
return {
|
|
@@ -20,7 +20,7 @@ export default async function getVeloraRoute(params, ctx) {
|
|
|
20
20
|
tokenOutDecimals ?? fetchDecimals(tokenOut, provider),
|
|
21
21
|
FundVault__factory.connect(vault, provider).controller(),
|
|
22
22
|
]);
|
|
23
|
-
const { baseToll, swapAmountIn } = resolveBaseToll(addresses, tokenIn, tokenOut, amountIn);
|
|
23
|
+
const { tollIn, baseToll, swapAmountIn } = resolveBaseToll(addresses, tokenIn, tokenOut, amountIn);
|
|
24
24
|
const routes = (await Promise.all(SUPPORTED_DEXS.map(async (dex) => {
|
|
25
25
|
try {
|
|
26
26
|
if (!isDexConfigured(addresses, dex))
|
|
@@ -47,6 +47,7 @@ export default async function getVeloraRoute(params, ctx) {
|
|
|
47
47
|
dex,
|
|
48
48
|
cachedPath,
|
|
49
49
|
amountIn: swapAmountIn,
|
|
50
|
+
tollIn,
|
|
50
51
|
toll,
|
|
51
52
|
vault,
|
|
52
53
|
controller,
|
package/dist/router/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseEther } from 'ethers';
|
|
1
|
+
import { Contract, parseEther, ZeroAddress } from 'ethers';
|
|
2
2
|
import { getGuruProtocolAddresses, } from '../addresses.js';
|
|
3
3
|
import compareAddresses from '../helpers/compareAddresses.js';
|
|
4
4
|
import { Token } from '../helpers/Token.js';
|
|
@@ -7,6 +7,33 @@ import getUniswapV4Route, { connectV4Quoter, toAdapterPathKeys, } from './getUni
|
|
|
7
7
|
import getVeloraRoute from './getVeloraRoute.js';
|
|
8
8
|
import PoolHelper from './poolHelper.js';
|
|
9
9
|
import { discoverV4Paths } from './v4PoolDiscovery.js';
|
|
10
|
+
const AERO_V3_FACTORY_ABI = [
|
|
11
|
+
'function getPool(address tokenA, address tokenB, int24 tickSpacing) view returns (address pool)',
|
|
12
|
+
];
|
|
13
|
+
const V2_FACTORY_ABI = [
|
|
14
|
+
'function getPair(address tokenA, address tokenB) view returns (address pair)',
|
|
15
|
+
];
|
|
16
|
+
const V2_ROUTER_ABI = [
|
|
17
|
+
'function getAmountsOut(uint256 amountIn, address[] memory path) view returns (uint256[] memory amounts)',
|
|
18
|
+
];
|
|
19
|
+
const AERO_V2_FACTORY_ABI = [
|
|
20
|
+
'function getPool(address tokenA, address tokenB, bool stable) view returns (address)',
|
|
21
|
+
];
|
|
22
|
+
const AERO_V2_ROUTER_ABI = [
|
|
23
|
+
'function getAmountsOut(uint256 amountIn, (address from, address to, bool stable, address factory)[] routes) view returns (uint256[] amounts)',
|
|
24
|
+
];
|
|
25
|
+
const V3_FACTORY_ABI = [
|
|
26
|
+
'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)',
|
|
27
|
+
];
|
|
28
|
+
const V3_QUOTER_ABI = [
|
|
29
|
+
'function quoteExactInputSingle((address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96) params) returns (uint256 amountOut,uint160 sqrtPriceX96After,uint32 initializedTicksCrossed,uint256 gasEstimate)',
|
|
30
|
+
];
|
|
31
|
+
const AERO_V3_QUOTER_ABI = [
|
|
32
|
+
'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, int24 tickSpacing, uint160 sqrtPriceLimitX96) params) returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
|
|
33
|
+
];
|
|
34
|
+
const UNISWAP_V3_FEES = [100, 500, 3000, 10000];
|
|
35
|
+
const PANCAKE_V3_FEES = [100, 500, 2500, 10000];
|
|
36
|
+
const AERODROME_V3_TICK_SPACINGS = [1, 50, 100, 200];
|
|
10
37
|
export async function veloraThenV4Discovery(velora, v4Discovery) {
|
|
11
38
|
try {
|
|
12
39
|
return await velora();
|
|
@@ -97,9 +124,7 @@ export async function getPriceUsd1e18(token, ctx) {
|
|
|
97
124
|
}
|
|
98
125
|
const { decimals } = await new Token(token, ctx.provider).metadata();
|
|
99
126
|
const oneToken = 10n ** BigInt(decimals);
|
|
100
|
-
const stableUsd = await _priceViaDirectStablePool(token, oneToken, poolHelper, stable, stableDecimals);
|
|
101
|
-
if (stableUsd !== null)
|
|
102
|
-
return stableUsd;
|
|
127
|
+
const stableUsd = await _priceViaDirectStablePool(token, oneToken, poolHelper, stable, stableDecimals, ctx.provider);
|
|
103
128
|
const WETH_UNIT = 10n ** 18n;
|
|
104
129
|
try {
|
|
105
130
|
const wethUsd = await getWethUsd();
|
|
@@ -108,33 +133,157 @@ export async function getPriceUsd1e18(token, ctx) {
|
|
|
108
133
|
path: [token, addresses.tokens.WETH],
|
|
109
134
|
requireSwappable: false,
|
|
110
135
|
});
|
|
111
|
-
|
|
136
|
+
const wethRouteUsd = (oneTokenInWeth * wethUsd) / WETH_UNIT;
|
|
137
|
+
return stableUsd !== null && stableUsd > wethRouteUsd
|
|
138
|
+
? stableUsd
|
|
139
|
+
: wethRouteUsd;
|
|
112
140
|
}
|
|
113
141
|
catch (error) {
|
|
114
142
|
const v4Usd = await _priceViaV4Pools(token, oneToken, getWethUsd, ctx);
|
|
143
|
+
if (stableUsd !== null && v4Usd !== null) {
|
|
144
|
+
return stableUsd > v4Usd ? stableUsd : v4Usd;
|
|
145
|
+
}
|
|
146
|
+
if (stableUsd !== null)
|
|
147
|
+
return stableUsd;
|
|
115
148
|
if (v4Usd !== null)
|
|
116
149
|
return v4Usd;
|
|
117
150
|
throw error;
|
|
118
151
|
}
|
|
119
152
|
}
|
|
120
|
-
async function _priceViaDirectStablePool(token, oneToken, poolHelper, stable, stableDecimals) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const amountToReceive = BigInt(route.data.amountToReceive);
|
|
135
|
-
if (amountToReceive === 0n)
|
|
153
|
+
async function _priceViaDirectStablePool(token, oneToken, poolHelper, stable, stableDecimals, provider) {
|
|
154
|
+
const stableUnit = Token.unitFor(stableDecimals);
|
|
155
|
+
const toUsd1e18 = (amount) => (amount * 10n ** 18n) / stableUnit;
|
|
156
|
+
const quotes = [];
|
|
157
|
+
const v2Amount = await _quoteDirectV2StablePools(token, stable, oneToken, poolHelper, provider);
|
|
158
|
+
if (v2Amount > 0n)
|
|
159
|
+
quotes.push(v2Amount);
|
|
160
|
+
const v3Amount = await _quoteDirectV3StablePools(token, stable, oneToken, poolHelper, provider);
|
|
161
|
+
if (v3Amount > 0n)
|
|
162
|
+
quotes.push(v3Amount);
|
|
163
|
+
const aeroV3Amount = await _quoteDirectAerodromeV3StablePool(token, stable, oneToken, poolHelper, provider);
|
|
164
|
+
if (aeroV3Amount > 0n)
|
|
165
|
+
quotes.push(aeroV3Amount);
|
|
166
|
+
if (quotes.length === 0)
|
|
136
167
|
return null;
|
|
137
|
-
|
|
168
|
+
const best = quotes.reduce((currentBest, amount) => amount > currentBest ? amount : currentBest);
|
|
169
|
+
return toUsd1e18(best);
|
|
170
|
+
}
|
|
171
|
+
async function _quoteDirectV2StablePools(token, stable, oneToken, poolHelper, provider) {
|
|
172
|
+
let best = 0n;
|
|
173
|
+
const quoteStandardV2 = async (factoryAddress, routerAddress) => {
|
|
174
|
+
if (!factoryAddress || !routerAddress)
|
|
175
|
+
return;
|
|
176
|
+
const factory = new Contract(factoryAddress, V2_FACTORY_ABI, provider);
|
|
177
|
+
const pairAddress = await factory
|
|
178
|
+
.getPair(token, stable)
|
|
179
|
+
.catch(() => ZeroAddress);
|
|
180
|
+
if (pairAddress === ZeroAddress)
|
|
181
|
+
return;
|
|
182
|
+
const router = new Contract(routerAddress, V2_ROUTER_ABI, provider);
|
|
183
|
+
const amounts = await router
|
|
184
|
+
.getAmountsOut(oneToken, [token, stable])
|
|
185
|
+
.catch(() => null);
|
|
186
|
+
const amountOut = amounts?.at(-1) ?? 0n;
|
|
187
|
+
if (amountOut > best)
|
|
188
|
+
best = amountOut;
|
|
189
|
+
};
|
|
190
|
+
const quoteAerodromeV2 = async (factoryAddress, routerAddress) => {
|
|
191
|
+
if (!factoryAddress || !routerAddress)
|
|
192
|
+
return;
|
|
193
|
+
const factory = new Contract(factoryAddress, AERO_V2_FACTORY_ABI, provider);
|
|
194
|
+
const router = new Contract(routerAddress, AERO_V2_ROUTER_ABI, provider);
|
|
195
|
+
await Promise.all([false, true].map(async (stablePool) => {
|
|
196
|
+
const poolAddress = await factory
|
|
197
|
+
.getPool(token, stable, stablePool)
|
|
198
|
+
.catch(() => ZeroAddress);
|
|
199
|
+
if (poolAddress === ZeroAddress)
|
|
200
|
+
return;
|
|
201
|
+
const amounts = await router
|
|
202
|
+
.getAmountsOut(oneToken, [
|
|
203
|
+
{
|
|
204
|
+
from: token,
|
|
205
|
+
to: stable,
|
|
206
|
+
stable: stablePool,
|
|
207
|
+
factory: factoryAddress,
|
|
208
|
+
},
|
|
209
|
+
])
|
|
210
|
+
.catch(() => null);
|
|
211
|
+
const amountOut = amounts?.at(-1) ?? 0n;
|
|
212
|
+
if (amountOut > best)
|
|
213
|
+
best = amountOut;
|
|
214
|
+
}));
|
|
215
|
+
};
|
|
216
|
+
await Promise.all([
|
|
217
|
+
quoteStandardV2(poolHelper.addresses.factories.uniswapV2, poolHelper.addresses.routers.uniswapV2),
|
|
218
|
+
quoteStandardV2(poolHelper.addresses.factories.pancakeV2, poolHelper.addresses.routers.pancakeV2),
|
|
219
|
+
quoteAerodromeV2(poolHelper.addresses.factories.aerodromeV2, poolHelper.addresses.routers.aerodromeV2),
|
|
220
|
+
]);
|
|
221
|
+
return best;
|
|
222
|
+
}
|
|
223
|
+
async function _quoteDirectV3StablePools(token, stable, oneToken, poolHelper, provider) {
|
|
224
|
+
let best = 0n;
|
|
225
|
+
const quoteFactory = async (factoryAddress, quoterAddress, fees) => {
|
|
226
|
+
if (!factoryAddress || !quoterAddress)
|
|
227
|
+
return;
|
|
228
|
+
const factory = new Contract(factoryAddress, V3_FACTORY_ABI, provider);
|
|
229
|
+
const quoter = new Contract(quoterAddress, V3_QUOTER_ABI, provider);
|
|
230
|
+
await Promise.all(fees.map(async (fee) => {
|
|
231
|
+
const poolAddress = await factory
|
|
232
|
+
.getPool(token, stable, fee)
|
|
233
|
+
.catch(() => ZeroAddress);
|
|
234
|
+
if (poolAddress === ZeroAddress)
|
|
235
|
+
return;
|
|
236
|
+
const quote = await quoter.quoteExactInputSingle.staticCall({
|
|
237
|
+
tokenIn: token,
|
|
238
|
+
tokenOut: stable,
|
|
239
|
+
amountIn: oneToken,
|
|
240
|
+
fee: BigInt(fee),
|
|
241
|
+
sqrtPriceLimitX96: 0n,
|
|
242
|
+
}).catch(() => null);
|
|
243
|
+
const amountOut = quote?.amountOut ?? 0n;
|
|
244
|
+
if (amountOut > best)
|
|
245
|
+
best = amountOut;
|
|
246
|
+
}));
|
|
247
|
+
};
|
|
248
|
+
await Promise.all([
|
|
249
|
+
quoteFactory(poolHelper.addresses.factories.uniswapV3, poolHelper.addresses.quoters.uniswapV3, UNISWAP_V3_FEES),
|
|
250
|
+
quoteFactory(poolHelper.addresses.factories.pancakeV3, poolHelper.addresses.quoters.pancakeV3, PANCAKE_V3_FEES),
|
|
251
|
+
]);
|
|
252
|
+
return best;
|
|
253
|
+
}
|
|
254
|
+
async function _quoteDirectAerodromeV3StablePool(token, stable, oneToken, poolHelper, provider) {
|
|
255
|
+
const quoterAddress = poolHelper.addresses.quoters.aerodromeV3;
|
|
256
|
+
if (!quoterAddress)
|
|
257
|
+
return 0n;
|
|
258
|
+
const factories = [
|
|
259
|
+
poolHelper.addresses.factories.aerodromeV3,
|
|
260
|
+
poolHelper.addresses.factories.aerodromeV3Bis,
|
|
261
|
+
].filter((factory) => Boolean(factory));
|
|
262
|
+
if (factories.length === 0)
|
|
263
|
+
return 0n;
|
|
264
|
+
const quoter = new Contract(quoterAddress, AERO_V3_QUOTER_ABI, provider);
|
|
265
|
+
let best = 0n;
|
|
266
|
+
await Promise.all(factories.flatMap((factoryAddress) => {
|
|
267
|
+
const factory = new Contract(factoryAddress, AERO_V3_FACTORY_ABI, provider);
|
|
268
|
+
return AERODROME_V3_TICK_SPACINGS.map(async (tickSpacing) => {
|
|
269
|
+
const poolAddress = await factory
|
|
270
|
+
.getPool(token, stable, tickSpacing)
|
|
271
|
+
.catch(() => ZeroAddress);
|
|
272
|
+
if (poolAddress === ZeroAddress)
|
|
273
|
+
return;
|
|
274
|
+
const quote = await quoter.quoteExactInputSingle.staticCall({
|
|
275
|
+
tokenIn: token,
|
|
276
|
+
tokenOut: stable,
|
|
277
|
+
amountIn: oneToken,
|
|
278
|
+
tickSpacing: BigInt(tickSpacing),
|
|
279
|
+
sqrtPriceLimitX96: 0n,
|
|
280
|
+
}).catch(() => null);
|
|
281
|
+
const amountOut = quote?.amountOut ?? 0n;
|
|
282
|
+
if (amountOut > best)
|
|
283
|
+
best = amountOut;
|
|
284
|
+
});
|
|
285
|
+
}));
|
|
286
|
+
return best;
|
|
138
287
|
}
|
|
139
288
|
async function _priceViaV4Pools(token, oneToken, getWethUsd, ctx) {
|
|
140
289
|
const addresses = getGuruProtocolAddresses(ctx.chainId);
|
package/dist/router/pathCache.js
CHANGED
|
@@ -32,10 +32,18 @@ export function extractPathFromResponse(dex, response) {
|
|
|
32
32
|
const swapPath = swap.swapExchanges[0].data.path;
|
|
33
33
|
if (!Array.isArray(swapPath) ||
|
|
34
34
|
typeof swapPath[0]?.tokenIn !== 'string' ||
|
|
35
|
-
|
|
35
|
+
swapPath[0]?.fee == null) {
|
|
36
36
|
throw new Error('Unexpected V3 path structure');
|
|
37
37
|
}
|
|
38
|
-
combinedPath.push(...swapPath)
|
|
38
|
+
combinedPath.push(...swapPath.map((hop) => ({
|
|
39
|
+
tokenIn: hop.tokenIn,
|
|
40
|
+
tokenOut: hop.tokenOut,
|
|
41
|
+
fee: String(dex === 'AerodromeV3'
|
|
42
|
+
? (hop.tickSpacing ?? hop.fee)
|
|
43
|
+
: hop.fee),
|
|
44
|
+
currentFee: hop.currentFee,
|
|
45
|
+
tickSpacing: hop.tickSpacing,
|
|
46
|
+
})));
|
|
39
47
|
}
|
|
40
48
|
return { type: 'v3', path: combinedPath, hops: combinedPath.length };
|
|
41
49
|
}
|
|
@@ -21,6 +21,9 @@ const V2_PAIR_ABI = [
|
|
|
21
21
|
const V3_FACTORY_ABI = [
|
|
22
22
|
'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)',
|
|
23
23
|
];
|
|
24
|
+
const AERO_V3_FACTORY_ABI = [
|
|
25
|
+
'function getPool(address tokenA, address tokenB, int24 tickSpacing) view returns (address pool)',
|
|
26
|
+
];
|
|
24
27
|
const V3_POOL_ABI = ['function fee() view returns (uint24)'];
|
|
25
28
|
const AERO_FACTORY_ABI = [
|
|
26
29
|
'function getPool(address tokenA, address tokenB, bool stable) view returns (address)',
|
|
@@ -260,7 +263,9 @@ export default class PoolHelper {
|
|
|
260
263
|
}
|
|
261
264
|
}
|
|
262
265
|
else {
|
|
263
|
-
const factory =
|
|
266
|
+
const factory = dex.kind === 'aerodrome'
|
|
267
|
+
? connect(factoryAddress, AERO_V3_FACTORY_ABI, this.provider)
|
|
268
|
+
: connect(factoryAddress, V3_FACTORY_ABI, this.provider);
|
|
264
269
|
await Promise.all(dex.feeTiers.map(async (feeTier) => {
|
|
265
270
|
try {
|
|
266
271
|
const poolAddress = await factory.getPool(token, weth, feeTier);
|
|
@@ -491,6 +496,7 @@ export default class PoolHelper {
|
|
|
491
496
|
amountToSend: inputAmount,
|
|
492
497
|
amountQuoted: lastAmount(amounts, exchangeFactory),
|
|
493
498
|
initialTollAmount: swapFee,
|
|
499
|
+
outputTollE3: 0n,
|
|
494
500
|
slippage,
|
|
495
501
|
buildSwap: (amountToReceive) => {
|
|
496
502
|
const data = {
|
|
@@ -522,6 +528,7 @@ export default class PoolHelper {
|
|
|
522
528
|
amountToSend: inputAmount,
|
|
523
529
|
amountQuoted: lastAmount(amounts, exchangeFactory),
|
|
524
530
|
initialTollAmount: swapFee,
|
|
531
|
+
outputTollE3: 0n,
|
|
525
532
|
slippage,
|
|
526
533
|
buildSwap: (amountToReceive) => {
|
|
527
534
|
const data = {
|
|
@@ -585,6 +592,7 @@ export default class PoolHelper {
|
|
|
585
592
|
amountToSend: inputAmount,
|
|
586
593
|
amountQuoted: amountOut,
|
|
587
594
|
initialTollAmount: swapFee,
|
|
595
|
+
outputTollE3: 0n,
|
|
588
596
|
slippage,
|
|
589
597
|
buildSwap: (amountToReceive) => {
|
|
590
598
|
const data = {
|
package/dist/router/types.d.ts
CHANGED
package/dist/router/velora.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ interface V3DexEntry {
|
|
|
14
14
|
interface VeloraDexConfig {
|
|
15
15
|
UniswapV2?: V2DexEntry;
|
|
16
16
|
AerodromeV2?: V2DexEntry;
|
|
17
|
+
AerodromeV3?: V3DexEntry;
|
|
17
18
|
PancakeSwapV2?: V2DexEntry;
|
|
18
19
|
UniswapV3?: V3DexEntry;
|
|
19
20
|
PancakeSwapV3?: V3DexEntry;
|
|
@@ -29,6 +30,7 @@ export interface GetRouteFromPathParams {
|
|
|
29
30
|
dex: SupportedDex;
|
|
30
31
|
cachedPath: CachedPath;
|
|
31
32
|
amountIn: bigint;
|
|
33
|
+
tollIn: boolean;
|
|
32
34
|
toll: {
|
|
33
35
|
currency: string;
|
|
34
36
|
amount: bigint;
|
|
@@ -39,5 +41,5 @@ export interface GetRouteFromPathParams {
|
|
|
39
41
|
maxSlippageE3?: bigint;
|
|
40
42
|
prefixTxs?: PrefixTx[];
|
|
41
43
|
}
|
|
42
|
-
export declare function getRouteFromPath({ chainId, addresses, provider, simulator, dex, cachedPath, amountIn, toll, vault, controller, account, maxSlippageE3, prefixTxs, }: GetRouteFromPathParams): Promise<Route>;
|
|
44
|
+
export declare function getRouteFromPath({ chainId, addresses, provider, simulator, dex, cachedPath, amountIn, tollIn, toll, vault, controller, account, maxSlippageE3, prefixTxs, }: GetRouteFromPathParams): Promise<Route>;
|
|
43
45
|
export {};
|
package/dist/router/velora.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Contract, solidityPacked } from 'ethers';
|
|
2
2
|
import { TOLL_DIVISOR_20BPS } from '../constants.js';
|
|
3
|
-
import { AerodromeV2Adapter__factory, IPancakeQuoterV2__factory, UniswapV2Adapter__factory, UniswapV3Adapter__factory, UniswapV4Adapter__factory, } from '../typechain/index.js';
|
|
3
|
+
import { AerodromeV3Adapter__factory, AerodromeV2Adapter__factory, IPancakeQuoterV2__factory, UniswapV2Adapter__factory, UniswapV3Adapter__factory, UniswapV4Adapter__factory, } from '../typechain/index.js';
|
|
4
4
|
import { connectV4Quoter, toAdapterPathKeys } from './getUniswapV4Route.js';
|
|
5
5
|
import { SWAP_DEADLINE_SECONDS } from './constants.js';
|
|
6
6
|
import { finalizeRouteQuote } from './finalizeRoute.js';
|
|
@@ -10,12 +10,18 @@ const V2_ROUTER_ABI = [
|
|
|
10
10
|
const AERO_V2_ROUTER_ABI = [
|
|
11
11
|
'function getAmountsOut(uint256 amountIn, (address from, address to, bool stable, address factory)[] routes) external view returns (uint256[] amounts)',
|
|
12
12
|
];
|
|
13
|
+
const AERO_V3_QUOTER_ABI = [
|
|
14
|
+
'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, int24 tickSpacing, uint160 sqrtPriceLimitX96) params) returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
|
|
15
|
+
];
|
|
13
16
|
function connectV2Router(address, provider) {
|
|
14
17
|
return new Contract(address, V2_ROUTER_ABI, provider);
|
|
15
18
|
}
|
|
16
19
|
function connectAeroV2Router(address, provider) {
|
|
17
20
|
return new Contract(address, AERO_V2_ROUTER_ABI, provider);
|
|
18
21
|
}
|
|
22
|
+
function connectAeroV3Quoter(address, provider) {
|
|
23
|
+
return new Contract(address, AERO_V3_QUOTER_ABI, provider);
|
|
24
|
+
}
|
|
19
25
|
function connectV3Quoter(address, provider) {
|
|
20
26
|
return IPancakeQuoterV2__factory.connect(address, provider);
|
|
21
27
|
}
|
|
@@ -33,6 +39,12 @@ export function buildVeloraDexConfig(addresses) {
|
|
|
33
39
|
routerAddress: addresses.routers.aerodromeV2,
|
|
34
40
|
};
|
|
35
41
|
}
|
|
42
|
+
if (addresses.adapters.aerodromeV3 && addresses.quoters.aerodromeV3) {
|
|
43
|
+
cfg.AerodromeV3 = {
|
|
44
|
+
adapter: addresses.adapters.aerodromeV3,
|
|
45
|
+
quoterAddress: addresses.quoters.aerodromeV3,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
36
48
|
if (addresses.adapters.pancakeV2 && addresses.routers.pancakeV2) {
|
|
37
49
|
cfg.PancakeSwapV2 = {
|
|
38
50
|
adapter: addresses.adapters.pancakeV2,
|
|
@@ -71,7 +83,7 @@ function encodeV3Path(path) {
|
|
|
71
83
|
}
|
|
72
84
|
return solidityPacked(pathEncoding, pathToEncode);
|
|
73
85
|
}
|
|
74
|
-
export async function getRouteFromPath({ chainId, addresses, provider, simulator, dex, cachedPath, amountIn, toll, vault, controller, account, maxSlippageE3, prefixTxs, }) {
|
|
86
|
+
export async function getRouteFromPath({ chainId, addresses, provider, simulator, dex, cachedPath, amountIn, tollIn, toll, vault, controller, account, maxSlippageE3, prefixTxs, }) {
|
|
75
87
|
if (!cachedPath) {
|
|
76
88
|
throw new Error('No cached path');
|
|
77
89
|
}
|
|
@@ -95,7 +107,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
95
107
|
]);
|
|
96
108
|
let amountQuoted = grossAmountToReceive;
|
|
97
109
|
let amountToSend = amountIn;
|
|
98
|
-
if (toll.amount === 0n) {
|
|
110
|
+
if (!tollIn && toll.amount === 0n) {
|
|
99
111
|
toll.amount = grossAmountToReceive / TOLL_DIVISOR_20BPS;
|
|
100
112
|
amountQuoted -= toll.amount;
|
|
101
113
|
}
|
|
@@ -128,6 +140,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
128
140
|
amountToSend,
|
|
129
141
|
amountQuoted,
|
|
130
142
|
initialTollAmount: toll.amount,
|
|
143
|
+
outputTollE3: tollIn ? 0n : undefined,
|
|
131
144
|
maxSlippageE3,
|
|
132
145
|
});
|
|
133
146
|
return {
|
|
@@ -163,7 +176,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
163
176
|
]);
|
|
164
177
|
let amountQuoted = grossAmountToReceive;
|
|
165
178
|
let amountToSend = amountIn;
|
|
166
|
-
if (toll.amount === 0n) {
|
|
179
|
+
if (!tollIn && toll.amount === 0n) {
|
|
167
180
|
toll.amount = grossAmountToReceive / TOLL_DIVISOR_20BPS;
|
|
168
181
|
amountQuoted -= toll.amount;
|
|
169
182
|
}
|
|
@@ -196,6 +209,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
196
209
|
amountToSend,
|
|
197
210
|
amountQuoted,
|
|
198
211
|
initialTollAmount: toll.amount,
|
|
212
|
+
outputTollE3: tollIn ? 0n : undefined,
|
|
199
213
|
maxSlippageE3,
|
|
200
214
|
});
|
|
201
215
|
return {
|
|
@@ -212,6 +226,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
212
226
|
effectiveSlippageBps,
|
|
213
227
|
};
|
|
214
228
|
}
|
|
229
|
+
case 'AerodromeV3':
|
|
215
230
|
case 'PancakeSwapV3':
|
|
216
231
|
case 'UniswapV3': {
|
|
217
232
|
if (cachedPath.type !== 'v3') {
|
|
@@ -222,15 +237,43 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
222
237
|
throw new Error(`DEX ${dex} not supported on chainId ${chainId}`);
|
|
223
238
|
}
|
|
224
239
|
const adapter = entry.adapter;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
let encodedPath;
|
|
241
|
+
let grossAmountToReceive;
|
|
242
|
+
let blockNumber;
|
|
243
|
+
if (dex === 'AerodromeV3') {
|
|
244
|
+
if (cachedPath.path.length !== 1) {
|
|
245
|
+
throw new Error('Aerodrome V3 multi-hop not supported');
|
|
246
|
+
}
|
|
247
|
+
const [hop] = cachedPath.path;
|
|
248
|
+
const tickSpacing = BigInt(hop.fee);
|
|
249
|
+
encodedPath = solidityPacked(['address', 'uint24', 'address'], [hop.tokenIn, Number(tickSpacing), hop.tokenOut]);
|
|
250
|
+
const quoter = connectAeroV3Quoter(entry.quoterAddress, provider);
|
|
251
|
+
const [quote, currentBlockNumber] = await Promise.all([
|
|
252
|
+
quoter.quoteExactInputSingle.staticCall({
|
|
253
|
+
tokenIn: hop.tokenIn,
|
|
254
|
+
tokenOut: hop.tokenOut,
|
|
255
|
+
amountIn,
|
|
256
|
+
tickSpacing,
|
|
257
|
+
sqrtPriceLimitX96: 0n,
|
|
258
|
+
}),
|
|
259
|
+
provider.getBlockNumber(),
|
|
260
|
+
]);
|
|
261
|
+
grossAmountToReceive = quote.amountOut;
|
|
262
|
+
blockNumber = currentBlockNumber;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const quoter = connectV3Quoter(entry.quoterAddress, provider);
|
|
266
|
+
encodedPath = encodeV3Path(cachedPath.path);
|
|
267
|
+
const [quote, currentBlockNumber] = await Promise.all([
|
|
268
|
+
quoter.quoteExactInput.staticCall(encodedPath, amountIn),
|
|
269
|
+
provider.getBlockNumber(),
|
|
270
|
+
]);
|
|
271
|
+
grossAmountToReceive = quote.amountOut;
|
|
272
|
+
blockNumber = currentBlockNumber;
|
|
273
|
+
}
|
|
231
274
|
let amountQuoted = grossAmountToReceive;
|
|
232
275
|
let amountToSend = amountIn;
|
|
233
|
-
if (toll.amount === 0n) {
|
|
276
|
+
if (!tollIn && toll.amount === 0n) {
|
|
234
277
|
toll.amount = grossAmountToReceive / TOLL_DIVISOR_20BPS;
|
|
235
278
|
amountQuoted -= toll.amount;
|
|
236
279
|
}
|
|
@@ -238,7 +281,10 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
238
281
|
amountToSend += toll.amount;
|
|
239
282
|
}
|
|
240
283
|
const deadline = Math.floor(Date.now() / 1000) + SWAP_DEADLINE_SECONDS;
|
|
241
|
-
const
|
|
284
|
+
const swapInterface = dex === 'AerodromeV3'
|
|
285
|
+
? AerodromeV3Adapter__factory.createInterface()
|
|
286
|
+
: UniswapV3Adapter__factory.createInterface();
|
|
287
|
+
const buildCallDataForAmount = (amountToReceive) => swapInterface.encodeFunctionData('executeSwap', [
|
|
242
288
|
{
|
|
243
289
|
amountToSend,
|
|
244
290
|
amountToReceive,
|
|
@@ -263,6 +309,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
263
309
|
amountToSend,
|
|
264
310
|
amountQuoted,
|
|
265
311
|
initialTollAmount: toll.amount,
|
|
312
|
+
outputTollE3: tollIn ? 0n : undefined,
|
|
266
313
|
maxSlippageE3,
|
|
267
314
|
});
|
|
268
315
|
return {
|
|
@@ -301,7 +348,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
301
348
|
]);
|
|
302
349
|
let amountQuoted = grossAmountToReceive;
|
|
303
350
|
let amountToSend = amountIn;
|
|
304
|
-
if (toll.amount === 0n) {
|
|
351
|
+
if (!tollIn && toll.amount === 0n) {
|
|
305
352
|
toll.amount = grossAmountToReceive / TOLL_DIVISOR_20BPS;
|
|
306
353
|
amountQuoted -= toll.amount;
|
|
307
354
|
}
|
|
@@ -335,6 +382,7 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
335
382
|
amountToSend,
|
|
336
383
|
amountQuoted,
|
|
337
384
|
initialTollAmount: toll.amount,
|
|
385
|
+
outputTollE3: tollIn ? 0n : undefined,
|
|
338
386
|
maxSlippageE3,
|
|
339
387
|
});
|
|
340
388
|
return {
|