@guru-fund/sdk 0.2.1 → 0.2.2
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/router/constants.d.ts +1 -1
- package/dist/router/constants.js +1 -0
- package/dist/router/getFallbackRoutes.js +54 -0
- package/dist/router/index.js +48 -12
- package/dist/router/pathCache.js +15 -0
- package/dist/router/poolHelper.d.ts +1 -1
- package/dist/router/poolHelper.js +33 -19
- package/dist/router/types.d.ts +20 -0
- package/dist/router/velora.d.ts +1 -0
- package/dist/router/velora.js +80 -1
- package/package.json +1 -1
|
@@ -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", "PancakeSwapV2", "PancakeSwapV3"];
|
|
2
|
+
export declare const SUPPORTED_DEXS: readonly ["UniswapV2", "UniswapV3", "UniswapV4", "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
|
@@ -2,6 +2,42 @@ import compareAddresses from '../helpers/compareAddresses.js';
|
|
|
2
2
|
import { FundVault__factory } from '../typechain/index.js';
|
|
3
3
|
import PoolHelper from './poolHelper.js';
|
|
4
4
|
import { quoteWethTrade } from './quoteWethTrade.js';
|
|
5
|
+
async function tryDirectAerodromeV2RouteIn(poolHelper, { tokenIn, tokenOut, amountIn, slippage, finalization, }) {
|
|
6
|
+
const factory = poolHelper.addresses.factories.aerodromeV2;
|
|
7
|
+
if (!factory || !poolHelper.addresses.adapters.aerodromeV2)
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const route = await poolHelper.getStableForTokenQuote({
|
|
11
|
+
path: [tokenIn, tokenOut],
|
|
12
|
+
inputAmount: amountIn,
|
|
13
|
+
slippage,
|
|
14
|
+
exchangeFactory: factory,
|
|
15
|
+
finalization,
|
|
16
|
+
});
|
|
17
|
+
return { ...route, hops: 1 };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function tryDirectAerodromeV2RouteOut(poolHelper, { tokenIn, tokenOut, amountIn, slippage, finalization, }) {
|
|
24
|
+
const factory = poolHelper.addresses.factories.aerodromeV2;
|
|
25
|
+
if (!factory || !poolHelper.addresses.adapters.aerodromeV2)
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
const route = await poolHelper.getTokensForStableQuote({
|
|
29
|
+
path: [tokenIn, tokenOut],
|
|
30
|
+
inputAmount: amountIn,
|
|
31
|
+
slippage,
|
|
32
|
+
exchangeFactory: factory,
|
|
33
|
+
finalization,
|
|
34
|
+
});
|
|
35
|
+
return { ...route, hops: 1 };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
5
41
|
export async function getFallbackRouteIn(params, ctx) {
|
|
6
42
|
const { chainId, tokenIn, tokenOut, amountIn, slippageE2 } = params;
|
|
7
43
|
const slippage = BigInt(slippageE2 ?? 500) * 10n;
|
|
@@ -34,6 +70,15 @@ export async function getFallbackRouteIn(params, ctx) {
|
|
|
34
70
|
finalization,
|
|
35
71
|
});
|
|
36
72
|
}
|
|
73
|
+
const directAerodrome = await tryDirectAerodromeV2RouteIn(poolHelper, {
|
|
74
|
+
tokenIn,
|
|
75
|
+
tokenOut,
|
|
76
|
+
amountIn,
|
|
77
|
+
slippage,
|
|
78
|
+
finalization,
|
|
79
|
+
});
|
|
80
|
+
if (directAerodrome)
|
|
81
|
+
return directAerodrome;
|
|
37
82
|
const pool = await poolHelper.getUniswapCompatibleTokenPool(tokenOut);
|
|
38
83
|
const path = [tokenIn, weth, tokenOut];
|
|
39
84
|
const route = await poolHelper.getStableForTokenQuote({
|
|
@@ -78,6 +123,15 @@ export async function getFallbackRouteOut(params, ctx) {
|
|
|
78
123
|
});
|
|
79
124
|
return { ...route, hops: 1 };
|
|
80
125
|
}
|
|
126
|
+
const directAerodrome = await tryDirectAerodromeV2RouteOut(poolHelper, {
|
|
127
|
+
tokenIn,
|
|
128
|
+
tokenOut,
|
|
129
|
+
amountIn,
|
|
130
|
+
slippage,
|
|
131
|
+
finalization,
|
|
132
|
+
});
|
|
133
|
+
if (directAerodrome)
|
|
134
|
+
return directAerodrome;
|
|
81
135
|
const pool = await poolHelper.getUniswapCompatibleTokenPool(tokenIn);
|
|
82
136
|
const path = [tokenIn, weth, tokenOut];
|
|
83
137
|
const route = await poolHelper.getTokensForStableQuote({
|
package/dist/router/index.js
CHANGED
|
@@ -65,26 +65,44 @@ export async function getRouteOut(params, ctx) {
|
|
|
65
65
|
}
|
|
66
66
|
export async function getPriceUsd1e18(token, ctx) {
|
|
67
67
|
const addresses = getGuruProtocolAddresses(ctx.chainId);
|
|
68
|
+
if (compareAddresses(token, addresses.tokens.USDC) ||
|
|
69
|
+
compareAddresses(token, addresses.tokens.USDT)) {
|
|
70
|
+
return 10n ** 18n;
|
|
71
|
+
}
|
|
68
72
|
const poolHelper = new PoolHelper({
|
|
69
73
|
chainId: ctx.chainId,
|
|
70
74
|
provider: ctx.provider,
|
|
71
75
|
getSwapFeePercentage: ctx.getSwapFeePercentage,
|
|
72
76
|
});
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
77
|
+
const stable = addresses.tokens.USDC;
|
|
78
|
+
const { decimals: stableDecimals } = await new Token(stable, ctx.provider)
|
|
79
|
+
.metadata()
|
|
80
|
+
.catch(() => ({ decimals: 6 }));
|
|
81
|
+
const stableUnit = Token.unitFor(stableDecimals);
|
|
82
|
+
const stableAmountToUsd1e18 = (amount) => (amount * 10n ** 18n) / stableUnit;
|
|
83
|
+
let wethUsdCache;
|
|
84
|
+
const getWethUsd = async () => {
|
|
85
|
+
if (wethUsdCache != null)
|
|
86
|
+
return wethUsdCache;
|
|
87
|
+
const { amount: wethStable } = await poolHelper.getBestQuote({
|
|
88
|
+
tokenAmount: parseEther('1'),
|
|
89
|
+
path: [addresses.tokens.WETH, stable],
|
|
90
|
+
requireSwappable: false,
|
|
91
|
+
});
|
|
92
|
+
wethUsdCache = stableAmountToUsd1e18(wethStable);
|
|
93
|
+
return wethUsdCache;
|
|
94
|
+
};
|
|
79
95
|
if (compareAddresses(token, addresses.tokens.WETH)) {
|
|
80
|
-
return
|
|
96
|
+
return getWethUsd();
|
|
81
97
|
}
|
|
82
|
-
const { decimals } = await new Token(token, ctx.provider)
|
|
83
|
-
.metadata()
|
|
84
|
-
.catch(() => ({ decimals: 0 }));
|
|
98
|
+
const { decimals } = await new Token(token, ctx.provider).metadata();
|
|
85
99
|
const oneToken = 10n ** BigInt(decimals);
|
|
100
|
+
const stableUsd = await _priceViaDirectStablePool(token, oneToken, poolHelper, stable, stableDecimals);
|
|
101
|
+
if (stableUsd !== null)
|
|
102
|
+
return stableUsd;
|
|
86
103
|
const WETH_UNIT = 10n ** 18n;
|
|
87
104
|
try {
|
|
105
|
+
const wethUsd = await getWethUsd();
|
|
88
106
|
const { amount: oneTokenInWeth } = await poolHelper.getBestQuote({
|
|
89
107
|
tokenAmount: oneToken,
|
|
90
108
|
path: [token, addresses.tokens.WETH],
|
|
@@ -93,13 +111,30 @@ export async function getPriceUsd1e18(token, ctx) {
|
|
|
93
111
|
return (oneTokenInWeth * wethUsd) / WETH_UNIT;
|
|
94
112
|
}
|
|
95
113
|
catch (error) {
|
|
96
|
-
const v4Usd = await _priceViaV4Pools(token, oneToken,
|
|
114
|
+
const v4Usd = await _priceViaV4Pools(token, oneToken, getWethUsd, ctx);
|
|
97
115
|
if (v4Usd !== null)
|
|
98
116
|
return v4Usd;
|
|
99
117
|
throw error;
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
|
-
async function
|
|
120
|
+
async function _priceViaDirectStablePool(token, oneToken, poolHelper, stable, stableDecimals) {
|
|
121
|
+
const factory = poolHelper.addresses.factories.aerodromeV2;
|
|
122
|
+
if (!factory)
|
|
123
|
+
return null;
|
|
124
|
+
const route = await poolHelper
|
|
125
|
+
.getTokensForStableQuote({
|
|
126
|
+
path: [token, stable],
|
|
127
|
+
inputAmount: oneToken,
|
|
128
|
+
slippage: 0n,
|
|
129
|
+
exchangeFactory: factory,
|
|
130
|
+
})
|
|
131
|
+
.catch(() => null);
|
|
132
|
+
if (!route)
|
|
133
|
+
return null;
|
|
134
|
+
return (BigInt(route.data.amountToReceive) * 10n ** 18n) /
|
|
135
|
+
Token.unitFor(stableDecimals);
|
|
136
|
+
}
|
|
137
|
+
async function _priceViaV4Pools(token, oneToken, getWethUsd, ctx) {
|
|
103
138
|
const addresses = getGuruProtocolAddresses(ctx.chainId);
|
|
104
139
|
const quoterAddress = addresses.quoters.uniswapV4;
|
|
105
140
|
if (!quoterAddress)
|
|
@@ -129,6 +164,7 @@ async function _priceViaV4Pools(token, oneToken, wethUsd, ctx) {
|
|
|
129
164
|
}
|
|
130
165
|
const inWeth = await quoteVia(addresses.tokens.WETH);
|
|
131
166
|
if (inWeth !== null) {
|
|
167
|
+
const wethUsd = await getWethUsd();
|
|
132
168
|
return (inWeth * wethUsd) / WETH_UNIT;
|
|
133
169
|
}
|
|
134
170
|
return null;
|
package/dist/router/pathCache.js
CHANGED
|
@@ -4,6 +4,21 @@ export function extractPathFromResponse(dex, response) {
|
|
|
4
4
|
if (dex === 'UniswapV4') {
|
|
5
5
|
return extractV4Path(response);
|
|
6
6
|
}
|
|
7
|
+
if (dex === 'AerodromeV2') {
|
|
8
|
+
const v2Response = response;
|
|
9
|
+
const exchange = v2Response.priceRoute.bestRoute[0].swaps[0].swapExchanges[0];
|
|
10
|
+
const path = exchange.data.path;
|
|
11
|
+
if (!Array.isArray(path) || typeof path[0] !== 'string') {
|
|
12
|
+
throw new Error('Unexpected Aerodrome V2 path structure');
|
|
13
|
+
}
|
|
14
|
+
const routes = path.slice(0, -1).map((from, i) => ({
|
|
15
|
+
from,
|
|
16
|
+
to: path[i + 1],
|
|
17
|
+
stable: Boolean(exchange.data.pools?.[i]?.stable),
|
|
18
|
+
factory: exchange.data.factory,
|
|
19
|
+
}));
|
|
20
|
+
return { type: 'aerodromeV2', path, routes, hops: path.length - 1 };
|
|
21
|
+
}
|
|
7
22
|
if (dex === 'UniswapV2' || dex === 'PancakeSwapV2') {
|
|
8
23
|
const v2Response = response;
|
|
9
24
|
const path = v2Response.priceRoute.bestRoute[0].swaps[0].swapExchanges[0]
|
|
@@ -61,7 +61,7 @@ type VersionSpecificQuoteRequest = Omit<QuoteRequest, 'path' | 'slippage'> & {
|
|
|
61
61
|
poolAddress?: string;
|
|
62
62
|
};
|
|
63
63
|
export type StableQuoteRequest = {
|
|
64
|
-
path: [string, string, string];
|
|
64
|
+
path: [string, string] | [string, string, string];
|
|
65
65
|
inputAmount: bigint;
|
|
66
66
|
slippage: bigint;
|
|
67
67
|
exchangeFactory: string;
|
|
@@ -547,20 +547,24 @@ export default class PoolHelper {
|
|
|
547
547
|
const quoter = dexData.kind === 'aerodrome'
|
|
548
548
|
? connect(dexData.quoter, AERO_V3_QUOTER_ABI, this.provider)
|
|
549
549
|
: IPancakeQuoterV2__factory.connect(dexData.quoter, this.provider);
|
|
550
|
+
const [stable, intermediate, token] = path;
|
|
551
|
+
if (!token) {
|
|
552
|
+
throw new Error(`${PoolHelperError.EXCHANGE_INCOMPATIBLE}: direct V3 fallback not supported (factory=${exchangeFactory})`);
|
|
553
|
+
}
|
|
550
554
|
const { feeTier: stableFeeTier } = await this.getUniswapCompatibleTokenPool(path[0], {
|
|
551
555
|
exchangeFactory,
|
|
552
556
|
});
|
|
553
|
-
const { feeTier: tokenFeeTier } = await this.getUniswapCompatibleTokenPool(
|
|
557
|
+
const { feeTier: tokenFeeTier } = await this.getUniswapCompatibleTokenPool(token, {
|
|
554
558
|
exchangeFactory,
|
|
555
559
|
});
|
|
556
|
-
const pathBytes = solidityPacked(['address', 'uint24', 'address', 'uint24', 'address'], [
|
|
560
|
+
const pathBytes = solidityPacked(['address', 'uint24', 'address', 'uint24', 'address'], [stable, stableFeeTier, intermediate, tokenFeeTier, token]);
|
|
557
561
|
if (dexData.kind === 'aerodrome') {
|
|
558
562
|
throw new Error(`${PoolHelperError.EXCHANGE_INCOMPATIBLE}: Aerodrome V3 multi-hop not supported (factory=${exchangeFactory})`);
|
|
559
563
|
}
|
|
560
564
|
const univ3Quoter = quoter;
|
|
561
565
|
const amountOut = await this._quoteV3MultiHop(univ3Quoter, adjustedInputAmount, pathBytes, [
|
|
562
|
-
{ tokenIn:
|
|
563
|
-
{ tokenIn:
|
|
566
|
+
{ tokenIn: stable, tokenOut: intermediate, fee: stableFeeTier },
|
|
567
|
+
{ tokenIn: intermediate, tokenOut: token, fee: tokenFeeTier },
|
|
564
568
|
]);
|
|
565
569
|
const deadline = Math.floor((Date.now() + 1000 * 60 * 5) / 1000);
|
|
566
570
|
const finalized = await this._finalizeExecutableQuote({
|
|
@@ -568,13 +572,13 @@ export default class PoolHelper {
|
|
|
568
572
|
adapter,
|
|
569
573
|
path: [
|
|
570
574
|
{
|
|
571
|
-
tokenIn:
|
|
572
|
-
tokenOut:
|
|
575
|
+
tokenIn: stable,
|
|
576
|
+
tokenOut: intermediate,
|
|
573
577
|
fee: String(stableFeeTier),
|
|
574
578
|
},
|
|
575
579
|
{
|
|
576
|
-
tokenIn:
|
|
577
|
-
tokenOut:
|
|
580
|
+
tokenIn: intermediate,
|
|
581
|
+
tokenOut: token,
|
|
578
582
|
fee: String(tokenFeeTier),
|
|
579
583
|
},
|
|
580
584
|
],
|
|
@@ -662,7 +666,10 @@ export default class PoolHelper {
|
|
|
662
666
|
adapter,
|
|
663
667
|
data: finalized.data,
|
|
664
668
|
callData: finalized.callData,
|
|
665
|
-
toll: {
|
|
669
|
+
toll: {
|
|
670
|
+
currency: path[path.length - 1],
|
|
671
|
+
amount: finalized.tollAmount,
|
|
672
|
+
},
|
|
666
673
|
effectiveSlippageBps: finalized.effectiveSlippageBps,
|
|
667
674
|
};
|
|
668
675
|
}
|
|
@@ -697,7 +704,10 @@ export default class PoolHelper {
|
|
|
697
704
|
adapter,
|
|
698
705
|
data: finalized.data,
|
|
699
706
|
callData: finalized.callData,
|
|
700
|
-
toll: {
|
|
707
|
+
toll: {
|
|
708
|
+
currency: path[path.length - 1],
|
|
709
|
+
amount: finalized.tollAmount,
|
|
710
|
+
},
|
|
701
711
|
effectiveSlippageBps: finalized.effectiveSlippageBps,
|
|
702
712
|
};
|
|
703
713
|
}
|
|
@@ -705,16 +715,20 @@ export default class PoolHelper {
|
|
|
705
715
|
throw new Error(`${PoolHelperError.EXCHANGE_INCOMPATIBLE}: Aerodrome V3 multi-hop not supported (factory=${exchangeFactory})`);
|
|
706
716
|
}
|
|
707
717
|
const v3Quoter = IPancakeQuoterV2__factory.connect(dexData.quoter, this.provider);
|
|
718
|
+
const [token, intermediate, stable] = path;
|
|
719
|
+
if (!stable) {
|
|
720
|
+
throw new Error(`${PoolHelperError.EXCHANGE_INCOMPATIBLE}: direct V3 fallback not supported (factory=${exchangeFactory})`);
|
|
721
|
+
}
|
|
708
722
|
const { feeTier: tokenFeeTier } = await this.getUniswapCompatibleTokenPool(path[0], {
|
|
709
723
|
exchangeFactory,
|
|
710
724
|
});
|
|
711
|
-
const { feeTier: stableFeeTier } = await this.getUniswapCompatibleTokenPool(
|
|
725
|
+
const { feeTier: stableFeeTier } = await this.getUniswapCompatibleTokenPool(stable, {
|
|
712
726
|
exchangeFactory,
|
|
713
727
|
});
|
|
714
|
-
const pathBytes = solidityPacked(['address', 'uint24', 'address', 'uint24', 'address'], [
|
|
728
|
+
const pathBytes = solidityPacked(['address', 'uint24', 'address', 'uint24', 'address'], [token, tokenFeeTier, intermediate, stableFeeTier, stable]);
|
|
715
729
|
const amountOut = await this._quoteV3MultiHop(v3Quoter, inputAmount, pathBytes, [
|
|
716
|
-
{ tokenIn:
|
|
717
|
-
{ tokenIn:
|
|
730
|
+
{ tokenIn: token, tokenOut: intermediate, fee: tokenFeeTier },
|
|
731
|
+
{ tokenIn: intermediate, tokenOut: stable, fee: stableFeeTier },
|
|
718
732
|
]);
|
|
719
733
|
const deadline = Math.floor((Date.now() + 1000 * 60 * 5) / 1000);
|
|
720
734
|
const netAmountOut = amountOut - (amountOut * swapFeePercentage) / PERCENT_DENOMINATOR;
|
|
@@ -723,13 +737,13 @@ export default class PoolHelper {
|
|
|
723
737
|
adapter,
|
|
724
738
|
path: [
|
|
725
739
|
{
|
|
726
|
-
tokenIn:
|
|
727
|
-
tokenOut:
|
|
740
|
+
tokenIn: token,
|
|
741
|
+
tokenOut: intermediate,
|
|
728
742
|
fee: String(tokenFeeTier),
|
|
729
743
|
},
|
|
730
744
|
{
|
|
731
|
-
tokenIn:
|
|
732
|
-
tokenOut:
|
|
745
|
+
tokenIn: intermediate,
|
|
746
|
+
tokenOut: stable,
|
|
733
747
|
fee: String(stableFeeTier),
|
|
734
748
|
},
|
|
735
749
|
],
|
|
@@ -755,7 +769,7 @@ export default class PoolHelper {
|
|
|
755
769
|
adapter,
|
|
756
770
|
data: finalized.data,
|
|
757
771
|
callData: finalized.callData,
|
|
758
|
-
toll: { currency:
|
|
772
|
+
toll: { currency: stable, amount: finalized.tollAmount },
|
|
759
773
|
effectiveSlippageBps: finalized.effectiveSlippageBps,
|
|
760
774
|
};
|
|
761
775
|
}
|
package/dist/router/types.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ import type { SwapV4Struct } from '../typechain/out/UniswapV4Adapter';
|
|
|
5
5
|
import type { GuruProtocolChainId } from '../addresses';
|
|
6
6
|
import type { PrefixTx } from './simulation';
|
|
7
7
|
export type V2Path = string[];
|
|
8
|
+
export type AeroV2Route = {
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
stable: boolean;
|
|
12
|
+
factory: string;
|
|
13
|
+
};
|
|
8
14
|
export type V4PathHop = {
|
|
9
15
|
tokenIn: string;
|
|
10
16
|
tokenOut: string;
|
|
@@ -40,6 +46,11 @@ export type CachedPath = {
|
|
|
40
46
|
type: 'v2';
|
|
41
47
|
path: V2Path;
|
|
42
48
|
hops: number;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'aerodromeV2';
|
|
51
|
+
path: V2Path;
|
|
52
|
+
routes: AeroV2Route[];
|
|
53
|
+
hops: number;
|
|
43
54
|
} | {
|
|
44
55
|
type: 'v3';
|
|
45
56
|
path: V3Path;
|
|
@@ -121,6 +132,14 @@ export interface VeloraDataV2 {
|
|
|
121
132
|
feeFactor: number;
|
|
122
133
|
gasUSD: string;
|
|
123
134
|
}
|
|
135
|
+
export interface VeloraDataAerodromeV2 extends VeloraDataV2 {
|
|
136
|
+
pools?: {
|
|
137
|
+
stable?: boolean;
|
|
138
|
+
address?: string;
|
|
139
|
+
fee?: number;
|
|
140
|
+
direction?: boolean;
|
|
141
|
+
}[];
|
|
142
|
+
}
|
|
124
143
|
export interface VeloraDataV3 {
|
|
125
144
|
path: V3PathHop[];
|
|
126
145
|
gasUSD: string;
|
|
@@ -145,5 +164,6 @@ export interface VeloraDataV4 {
|
|
|
145
164
|
gasUSD?: string;
|
|
146
165
|
}
|
|
147
166
|
export type VeloraRouteResponseV2 = VeloraRouteResponse<VeloraDataV2>;
|
|
167
|
+
export type VeloraRouteResponseAerodromeV2 = VeloraRouteResponse<VeloraDataAerodromeV2>;
|
|
148
168
|
export type VeloraRouteResponseV3 = VeloraRouteResponse<VeloraDataV3>;
|
|
149
169
|
export type VeloraRouteResponseV4 = VeloraRouteResponse<VeloraDataV4>;
|
package/dist/router/velora.d.ts
CHANGED
package/dist/router/velora.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { Contract, solidityPacked } from 'ethers';
|
|
2
2
|
import { TOLL_DIVISOR_20BPS } from '../constants.js';
|
|
3
|
-
import { IPancakeQuoterV2__factory, UniswapV2Adapter__factory, UniswapV3Adapter__factory, UniswapV4Adapter__factory, } from '../typechain/index.js';
|
|
3
|
+
import { 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';
|
|
7
7
|
const V2_ROUTER_ABI = [
|
|
8
8
|
'function getAmountsOut(uint256 amountIn, address[] memory path) external view returns (uint256[] memory amounts)',
|
|
9
9
|
];
|
|
10
|
+
const AERO_V2_ROUTER_ABI = [
|
|
11
|
+
'function getAmountsOut(uint256 amountIn, (address from, address to, bool stable, address factory)[] routes) external view returns (uint256[] amounts)',
|
|
12
|
+
];
|
|
10
13
|
function connectV2Router(address, provider) {
|
|
11
14
|
return new Contract(address, V2_ROUTER_ABI, provider);
|
|
12
15
|
}
|
|
16
|
+
function connectAeroV2Router(address, provider) {
|
|
17
|
+
return new Contract(address, AERO_V2_ROUTER_ABI, provider);
|
|
18
|
+
}
|
|
13
19
|
function connectV3Quoter(address, provider) {
|
|
14
20
|
return IPancakeQuoterV2__factory.connect(address, provider);
|
|
15
21
|
}
|
|
@@ -21,6 +27,12 @@ export function buildVeloraDexConfig(addresses) {
|
|
|
21
27
|
routerAddress: addresses.routers.uniswapV2,
|
|
22
28
|
};
|
|
23
29
|
}
|
|
30
|
+
if (addresses.adapters.aerodromeV2 && addresses.routers.aerodromeV2) {
|
|
31
|
+
cfg.AerodromeV2 = {
|
|
32
|
+
adapter: addresses.adapters.aerodromeV2,
|
|
33
|
+
routerAddress: addresses.routers.aerodromeV2,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
24
36
|
if (addresses.adapters.pancakeV2 && addresses.routers.pancakeV2) {
|
|
25
37
|
cfg.PancakeSwapV2 = {
|
|
26
38
|
adapter: addresses.adapters.pancakeV2,
|
|
@@ -65,6 +77,73 @@ export async function getRouteFromPath({ chainId, addresses, provider, simulator
|
|
|
65
77
|
}
|
|
66
78
|
const dexConfig = buildVeloraDexConfig(addresses);
|
|
67
79
|
switch (dex) {
|
|
80
|
+
case 'AerodromeV2': {
|
|
81
|
+
if (cachedPath.type !== 'aerodromeV2') {
|
|
82
|
+
throw new Error('Expected Aerodrome V2 path');
|
|
83
|
+
}
|
|
84
|
+
const entry = dexConfig[dex];
|
|
85
|
+
if (!entry) {
|
|
86
|
+
throw new Error(`DEX ${dex} not supported on chainId ${chainId}`);
|
|
87
|
+
}
|
|
88
|
+
const router = connectAeroV2Router(entry.routerAddress, provider);
|
|
89
|
+
const adapter = entry.adapter;
|
|
90
|
+
const [grossAmountToReceive, blockNumber] = await Promise.all([
|
|
91
|
+
router
|
|
92
|
+
.getAmountsOut(amountIn, cachedPath.routes)
|
|
93
|
+
.then((amounts) => amounts.at(-1)),
|
|
94
|
+
provider.getBlockNumber(),
|
|
95
|
+
]);
|
|
96
|
+
let amountQuoted = grossAmountToReceive;
|
|
97
|
+
let amountToSend = amountIn;
|
|
98
|
+
if (toll.amount === 0n) {
|
|
99
|
+
toll.amount = grossAmountToReceive / TOLL_DIVISOR_20BPS;
|
|
100
|
+
amountQuoted -= toll.amount;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
amountToSend += toll.amount;
|
|
104
|
+
}
|
|
105
|
+
const deadline = Math.floor(Date.now() / 1000) + SWAP_DEADLINE_SECONDS;
|
|
106
|
+
const buildCallDataForAmount = (amountToReceive) => AerodromeV2Adapter__factory.createInterface().encodeFunctionData('executeSwap', [
|
|
107
|
+
{
|
|
108
|
+
amountToSend,
|
|
109
|
+
amountToReceive,
|
|
110
|
+
routes: cachedPath.routes,
|
|
111
|
+
deadline,
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
const context = {
|
|
115
|
+
chainId,
|
|
116
|
+
blockNumber,
|
|
117
|
+
controller,
|
|
118
|
+
vault,
|
|
119
|
+
adapter,
|
|
120
|
+
account,
|
|
121
|
+
path: cachedPath.path,
|
|
122
|
+
buildCallDataForAmount,
|
|
123
|
+
simulator,
|
|
124
|
+
prefixTxs,
|
|
125
|
+
};
|
|
126
|
+
const { finalAmountToReceive, callData, effectiveSlippageBps, finalTollAmount, } = await finalizeRouteQuote({
|
|
127
|
+
context,
|
|
128
|
+
amountToSend,
|
|
129
|
+
amountQuoted,
|
|
130
|
+
initialTollAmount: toll.amount,
|
|
131
|
+
maxSlippageE3,
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
adapter,
|
|
135
|
+
data: {
|
|
136
|
+
amountToSend,
|
|
137
|
+
amountToReceive: finalAmountToReceive,
|
|
138
|
+
routes: cachedPath.routes,
|
|
139
|
+
deadline,
|
|
140
|
+
},
|
|
141
|
+
callData,
|
|
142
|
+
toll: { ...toll, amount: finalTollAmount },
|
|
143
|
+
hops: cachedPath.hops,
|
|
144
|
+
effectiveSlippageBps,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
68
147
|
case 'PancakeSwapV2':
|
|
69
148
|
case 'UniswapV2': {
|
|
70
149
|
if (cachedPath.type !== 'v2') {
|