@guru-fund/sdk 0.2.9 → 0.2.11
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/addresses.d.ts +1 -0
- package/dist/addresses.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/quotes/quoteTrade.d.ts +2 -0
- package/dist/quotes/quoteTrade.js +71 -9
- package/dist/router/getFallbackRoutes.js +54 -17
- package/dist/router/index.js +6 -0
- package/dist/router/poolHelper.d.ts +4 -3
- package/dist/router/poolHelper.js +9 -3
- package/dist/router/quoteWethTrade.d.ts +2 -0
- package/dist/router/quoteWethTrade.js +6 -4
- package/dist/router/simulation.js +8 -4
- package/dist/router/v4PoolDiscovery.d.ts +2 -0
- package/dist/router/v4PoolDiscovery.js +96 -7
- package/dist/txBuilders/buildTradesTx.d.ts +9 -0
- package/dist/txBuilders/buildTradesTx.js +9 -0
- package/dist/txBuilders/index.d.ts +2 -0
- package/dist/txBuilders/index.js +1 -0
- package/package.json +1 -1
package/dist/addresses.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type GuruProtocolAddresses = {
|
|
|
22
22
|
readonly factories: Readonly<Partial<Record<FactoryKey, string>>>;
|
|
23
23
|
readonly routers: Readonly<Partial<Record<RouterKey, string>>>;
|
|
24
24
|
readonly quoters: Readonly<Partial<Record<QuoterKey, string>>>;
|
|
25
|
+
readonly routeBridges?: readonly string[];
|
|
25
26
|
readonly tokens: Readonly<{
|
|
26
27
|
WETH: string;
|
|
27
28
|
USDC: string;
|
package/dist/addresses.js
CHANGED
|
@@ -121,6 +121,7 @@ const ROBINHOOD = {
|
|
|
121
121
|
uniswapV3: '0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7',
|
|
122
122
|
uniswapV4: '0x8dc178efb8111bb0973dd9d722ebeff267c98f94',
|
|
123
123
|
},
|
|
124
|
+
routeBridges: ['0xc6911796042b15d7fa4f6cde69e245ddcd3d9c31'],
|
|
124
125
|
tokens: {
|
|
125
126
|
WETH: '0x0bd7d308f8e1639fab988df18a8011f41eacad73',
|
|
126
127
|
USDC: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type { GuruProtocolAddresses, GuruProtocolChainId } from './addresses';
|
|
|
4
4
|
export { GuruProtocol } from './GuruProtocol';
|
|
5
5
|
export type { GuruProtocolOptions } from './GuruProtocol';
|
|
6
6
|
export type { BuildDepositTxParams, BuildCloseTxParams, BuildHarvestTxParams, BuildTradeTxParams, BuildWithdrawTxParams, } from './txBuilders';
|
|
7
|
+
export type { BuildTradesTxParams } from './txBuilders/buildTradesTx';
|
|
7
8
|
export type { QuoteDepositParams, QuoteDepositResult, QuoteDepositLogs, } from './quotes/quoteDeposit';
|
|
8
9
|
export type { QuoteWithdrawalParams, QuoteWithdrawalResult, QuoteWithdrawalLogs, } from './quotes/quoteWithdrawal';
|
|
9
10
|
export type { QuoteTradeParams, QuoteTradeResult } from './quotes/quoteTrade';
|
|
@@ -13,6 +13,8 @@ export interface QuoteTradeContext extends RouterContext {
|
|
|
13
13
|
chainId: GuruProtocolChainId;
|
|
14
14
|
}
|
|
15
15
|
export interface QuoteTradeResult extends Route {
|
|
16
|
+
routes: Route[];
|
|
16
17
|
txData: TransactionRequest;
|
|
17
18
|
}
|
|
19
|
+
export declare function bestOfDirectAndComposite(direct: () => Promise<Route>, composite: () => Promise<Route[]>): Promise<Route[]>;
|
|
18
20
|
export default function quoteTrade(params: QuoteTradeParams, ctx: QuoteTradeContext): Promise<QuoteTradeResult>;
|
|
@@ -1,9 +1,53 @@
|
|
|
1
1
|
import { getGuruProtocolAddresses, } from '../addresses.js';
|
|
2
2
|
import compareAddresses from '../helpers/compareAddresses.js';
|
|
3
3
|
import { getRouteIn, getRouteOut } from '../router/index.js';
|
|
4
|
+
import { encodeVaultExecute } from '../router/simulation.js';
|
|
4
5
|
import { quoteTradeSchema } from '../schemas/quoteTrade.js';
|
|
5
6
|
import buildTradeTx from '../txBuilders/buildTradeTx.js';
|
|
7
|
+
import buildTradesTx from '../txBuilders/buildTradesTx.js';
|
|
6
8
|
import { FundLedger__factory, Protocol__factory } from '../typechain/index.js';
|
|
9
|
+
async function buildCompositeWethRoute(routeParams, weth, controllerAddress, vaultAddress, ctx) {
|
|
10
|
+
const first = await getRouteIn({ ...routeParams, tokenOut: weth }, ctx);
|
|
11
|
+
const prefixTxs = [
|
|
12
|
+
{
|
|
13
|
+
from: controllerAddress,
|
|
14
|
+
to: vaultAddress,
|
|
15
|
+
callData: encodeVaultExecute(String(first.adapter), String(first.callData)),
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
const second = await getRouteIn({
|
|
19
|
+
...routeParams,
|
|
20
|
+
tokenIn: weth,
|
|
21
|
+
amountIn: BigInt(first.data.amountToReceive),
|
|
22
|
+
prefixTxs,
|
|
23
|
+
}, ctx);
|
|
24
|
+
return [first, second];
|
|
25
|
+
}
|
|
26
|
+
export async function bestOfDirectAndComposite(direct, composite) {
|
|
27
|
+
const [directResult, compositeResult] = await Promise.allSettled([
|
|
28
|
+
direct(),
|
|
29
|
+
composite(),
|
|
30
|
+
]);
|
|
31
|
+
if (directResult.status === 'rejected' &&
|
|
32
|
+
directResult.reason instanceof Error &&
|
|
33
|
+
(directResult.reason.message.includes('TOKEN_LOCKED_BY_V4_HOOK') ||
|
|
34
|
+
directResult.reason.message.includes('UNISWAP_V4_HOOK_ROUTE_REVERTED'))) {
|
|
35
|
+
throw directResult.reason;
|
|
36
|
+
}
|
|
37
|
+
if (directResult.status === 'fulfilled' &&
|
|
38
|
+
compositeResult.status === 'fulfilled') {
|
|
39
|
+
const compositeRoute = compositeResult.value.at(-1);
|
|
40
|
+
return BigInt(directResult.value.data.amountToReceive) >=
|
|
41
|
+
BigInt(compositeRoute.data.amountToReceive)
|
|
42
|
+
? [directResult.value]
|
|
43
|
+
: compositeResult.value;
|
|
44
|
+
}
|
|
45
|
+
if (directResult.status === 'fulfilled')
|
|
46
|
+
return [directResult.value];
|
|
47
|
+
if (compositeResult.status === 'fulfilled')
|
|
48
|
+
return compositeResult.value;
|
|
49
|
+
throw directResult.reason;
|
|
50
|
+
}
|
|
7
51
|
export default async function quoteTrade(params, ctx) {
|
|
8
52
|
const parsed = quoteTradeSchema.parse(params);
|
|
9
53
|
const provider = ctx.provider;
|
|
@@ -27,25 +71,43 @@ export default async function quoteTrade(params, ctx) {
|
|
|
27
71
|
ledger.controller(),
|
|
28
72
|
ledger.manager(),
|
|
29
73
|
]);
|
|
30
|
-
const
|
|
74
|
+
const slippageE2 = parsed.maxSlippage != null
|
|
75
|
+
? Number(parsed.maxSlippage / 10n)
|
|
76
|
+
: undefined;
|
|
77
|
+
const routeParams = {
|
|
31
78
|
chainId: ctx.chainId,
|
|
32
79
|
tokenIn: parsed.tokenIn,
|
|
33
80
|
tokenOut: parsed.tokenOut,
|
|
34
81
|
amountIn: parsed.amountIn,
|
|
35
|
-
slippageE2
|
|
36
|
-
? Number(parsed.maxSlippage / 10n)
|
|
37
|
-
: undefined,
|
|
82
|
+
slippageE2,
|
|
38
83
|
account: vaultAddress,
|
|
39
84
|
vault: vaultAddress,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
85
|
+
};
|
|
86
|
+
const weth = addresses.tokens.WETH;
|
|
87
|
+
const routes = compareAddresses(parsed.tokenIn, weth) ||
|
|
88
|
+
compareAddresses(parsed.tokenOut, weth)
|
|
89
|
+
? [await getRoute(routeParams, ctx)]
|
|
90
|
+
: await bestOfDirectAndComposite(() => getRoute(routeParams, ctx), () => buildCompositeWethRoute(routeParams, weth, controllerAddress, vaultAddress, ctx));
|
|
91
|
+
const route = routes.at(-1);
|
|
92
|
+
const txData = routes.length === 1
|
|
93
|
+
? buildTradeTx({
|
|
44
94
|
controller: controllerAddress,
|
|
45
95
|
ledger: parsed.ledger,
|
|
46
96
|
adapter: String(route.adapter),
|
|
47
97
|
callData: String(route.callData),
|
|
48
98
|
from: manager,
|
|
49
|
-
})
|
|
99
|
+
})
|
|
100
|
+
: buildTradesTx({
|
|
101
|
+
controller: controllerAddress,
|
|
102
|
+
ledger: parsed.ledger,
|
|
103
|
+
adapters: routes.map((item) => String(item.adapter)),
|
|
104
|
+
callData: routes.map((item) => String(item.callData)),
|
|
105
|
+
from: manager,
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
...route,
|
|
109
|
+
routes,
|
|
110
|
+
hops: routes.reduce((sum, item) => sum + item.hops, 0),
|
|
111
|
+
txData,
|
|
50
112
|
};
|
|
51
113
|
}
|
|
@@ -2,6 +2,28 @@ 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 tryWethBridgeV2Route(poolHelper, input, finalization) {
|
|
6
|
+
const factory = poolHelper.addresses.factories.uniswapV2;
|
|
7
|
+
if (!factory || !poolHelper.addresses.adapters.uniswapV2)
|
|
8
|
+
return null;
|
|
9
|
+
for (const bridge of poolHelper.addresses.routeBridges ?? []) {
|
|
10
|
+
try {
|
|
11
|
+
return await quoteWethTrade({
|
|
12
|
+
feeTier: 0,
|
|
13
|
+
input: {
|
|
14
|
+
...input,
|
|
15
|
+
path: [input.tokenIn, bridge, input.tokenOut],
|
|
16
|
+
exchangeFactory: factory,
|
|
17
|
+
},
|
|
18
|
+
poolHelper,
|
|
19
|
+
finalization,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
5
27
|
async function tryDirectAerodromeV2RouteIn(poolHelper, { tokenIn, tokenOut, amountIn, slippage, finalization, }) {
|
|
6
28
|
const factory = poolHelper.addresses.factories.aerodromeV2;
|
|
7
29
|
if (!factory || !poolHelper.addresses.adapters.aerodromeV2)
|
|
@@ -61,14 +83,22 @@ export async function getFallbackRouteIn(params, ctx) {
|
|
|
61
83
|
maxSlippageE3: slippage,
|
|
62
84
|
};
|
|
63
85
|
if (compareAddresses(tokenIn, weth) || compareAddresses(tokenOut, weth)) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
try {
|
|
87
|
+
const pairedToken = compareAddresses(tokenIn, weth) ? tokenOut : tokenIn;
|
|
88
|
+
const coinTokenPool = await poolHelper.getUniswapCompatibleTokenPool(pairedToken);
|
|
89
|
+
return await quoteWethTrade({
|
|
90
|
+
feeTier: coinTokenPool.feeTier,
|
|
91
|
+
input: { tokenIn, tokenOut, amountIn, slippage },
|
|
92
|
+
poolHelper,
|
|
93
|
+
finalization,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (directError) {
|
|
97
|
+
const bridged = await tryWethBridgeV2Route(poolHelper, { tokenIn, tokenOut, amountIn, slippage }, finalization);
|
|
98
|
+
if (bridged)
|
|
99
|
+
return bridged;
|
|
100
|
+
throw directError;
|
|
101
|
+
}
|
|
72
102
|
}
|
|
73
103
|
const directAerodrome = await tryDirectAerodromeV2RouteIn(poolHelper, {
|
|
74
104
|
tokenIn,
|
|
@@ -113,15 +143,22 @@ export async function getFallbackRouteOut(params, ctx) {
|
|
|
113
143
|
maxSlippageE3: slippage,
|
|
114
144
|
};
|
|
115
145
|
if (compareAddresses(tokenIn, weth) || compareAddresses(tokenOut, weth)) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
try {
|
|
147
|
+
const pairedToken = compareAddresses(tokenIn, weth) ? tokenOut : tokenIn;
|
|
148
|
+
const coinTokenPool = await poolHelper.getUniswapCompatibleTokenPool(pairedToken);
|
|
149
|
+
return await quoteWethTrade({
|
|
150
|
+
feeTier: coinTokenPool.feeTier,
|
|
151
|
+
input: { tokenIn, tokenOut, amountIn, slippage },
|
|
152
|
+
poolHelper,
|
|
153
|
+
finalization,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (directError) {
|
|
157
|
+
const bridged = await tryWethBridgeV2Route(poolHelper, { tokenIn, tokenOut, amountIn, slippage }, finalization);
|
|
158
|
+
if (bridged)
|
|
159
|
+
return bridged;
|
|
160
|
+
throw directError;
|
|
161
|
+
}
|
|
125
162
|
}
|
|
126
163
|
const directAerodrome = await tryDirectAerodromeV2RouteOut(poolHelper, {
|
|
127
164
|
tokenIn,
|
package/dist/router/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import getUniswapV4Route, { connectV4Quoter, toAdapterPathKeys, } from './getUni
|
|
|
7
7
|
import getVeloraRoute from './getVeloraRoute.js';
|
|
8
8
|
import { stablecoinAddresses } from './helpers.js';
|
|
9
9
|
import PoolHelper from './poolHelper.js';
|
|
10
|
+
import { V4_ZERO_ADDRESS } from './constants.js';
|
|
10
11
|
import { discoverV4Paths } from './v4PoolDiscovery.js';
|
|
11
12
|
const AERO_V3_FACTORY_ABI = [
|
|
12
13
|
'function getPool(address tokenA, address tokenB, int24 tickSpacing) view returns (address pool)',
|
|
@@ -342,6 +343,11 @@ async function _priceViaV4Pools(token, oneToken, getWethUsd, ctx) {
|
|
|
342
343
|
}
|
|
343
344
|
return best > 0n ? best : null;
|
|
344
345
|
};
|
|
346
|
+
const inNative = await quoteVia(V4_ZERO_ADDRESS);
|
|
347
|
+
if (inNative !== null) {
|
|
348
|
+
const wethUsd = await getWethUsd();
|
|
349
|
+
return (inNative * wethUsd) / WETH_UNIT;
|
|
350
|
+
}
|
|
345
351
|
const inUsdc = await quoteVia(addresses.tokens.USDG ?? addresses.tokens.USDC);
|
|
346
352
|
if (inUsdc !== null) {
|
|
347
353
|
return inUsdc * 10n ** 12n;
|
|
@@ -52,13 +52,14 @@ export type QuoteRequest = {
|
|
|
52
52
|
swapType?: SwapType;
|
|
53
53
|
tokenAmount: bigint;
|
|
54
54
|
slippage?: bigint;
|
|
55
|
-
path: [
|
|
55
|
+
path: string[];
|
|
56
|
+
exchangeFactory?: string;
|
|
56
57
|
requireSwappable?: boolean;
|
|
57
58
|
};
|
|
58
59
|
type VersionSpecificQuoteRequest = Omit<QuoteRequest, 'path' | 'slippage'> & {
|
|
59
60
|
exchangeFactory: string;
|
|
60
61
|
feeTier: FeeTier;
|
|
61
|
-
path: [
|
|
62
|
+
path: string[];
|
|
62
63
|
slippage: bigint;
|
|
63
64
|
poolAddress?: string;
|
|
64
65
|
};
|
|
@@ -115,7 +116,7 @@ export default class PoolHelper {
|
|
|
115
116
|
exchangeFactory?: string;
|
|
116
117
|
swappableOnly?: boolean;
|
|
117
118
|
}): Promise<Pool>;
|
|
118
|
-
getBestQuote({ swapType, tokenAmount, slippage, path, requireSwappable, }: QuoteRequest): Promise<Quote>;
|
|
119
|
+
getBestQuote({ swapType, tokenAmount, slippage, path, requireSwappable, exchangeFactory, }: QuoteRequest): Promise<Quote>;
|
|
119
120
|
getV2Quote({ swapType, tokenAmount, exchangeFactory, path, feeTier, slippage, }: VersionSpecificQuoteRequest): Promise<Quote>;
|
|
120
121
|
getV3Quote({ swapType, tokenAmount, exchangeFactory, path, feeTier, slippage, poolAddress, }: VersionSpecificQuoteRequest): Promise<Quote>;
|
|
121
122
|
getStableForTokenQuote({ path, inputAmount, slippage, exchangeFactory, finalization, }: StableQuoteRequest): Promise<StableQuoteResult>;
|
|
@@ -327,7 +327,7 @@ export default class PoolHelper {
|
|
|
327
327
|
}
|
|
328
328
|
return pools[0];
|
|
329
329
|
}
|
|
330
|
-
async getBestQuote({ swapType = SwapType.EXACT_INPUT, tokenAmount, slippage = 500n, path, requireSwappable = true, }) {
|
|
330
|
+
async getBestQuote({ swapType = SwapType.EXACT_INPUT, tokenAmount, slippage = 500n, path, requireSwappable = true, exchangeFactory, }) {
|
|
331
331
|
const weth = this.addresses.tokens.WETH;
|
|
332
332
|
if (!path.some((t) => compareAddresses(t, weth))) {
|
|
333
333
|
throw new Error(`${PoolHelperError.NO_ROUTE_FOUND}: WETH not in path`);
|
|
@@ -335,6 +335,7 @@ export default class PoolHelper {
|
|
|
335
335
|
const token = path.find((t) => !compareAddresses(t, weth));
|
|
336
336
|
const pools = await this.getUniswapCompatibleTokenPools(token, {
|
|
337
337
|
swappableOnly: requireSwappable,
|
|
338
|
+
exchangeFactory,
|
|
338
339
|
});
|
|
339
340
|
if (pools.length === 0) {
|
|
340
341
|
throw new Error(`${PoolHelperError.NO_ROUTE_FOUND}: token=${token}`);
|
|
@@ -406,6 +407,9 @@ export default class PoolHelper {
|
|
|
406
407
|
};
|
|
407
408
|
}
|
|
408
409
|
async getV3Quote({ swapType = SwapType.EXACT_INPUT, tokenAmount, exchangeFactory, path, feeTier, slippage, poolAddress, }) {
|
|
410
|
+
if (path.length !== 2) {
|
|
411
|
+
throw new Error(`${PoolHelperError.EXCHANGE_INCOMPATIBLE}: V3 multi-hop requires per-hop fee tiers`);
|
|
412
|
+
}
|
|
409
413
|
const [$INPUT, $OUTPUT] = path;
|
|
410
414
|
const dex = this.getDexData(exchangeFactory);
|
|
411
415
|
if (dex.type !== 'v3') {
|
|
@@ -811,7 +815,8 @@ export default class PoolHelper {
|
|
|
811
815
|
}
|
|
812
816
|
_adjustTokenAmountForSwapFee(tokenAmount, swapFeePercentage, swapType, path) {
|
|
813
817
|
const weth = this.addresses.tokens.WETH;
|
|
814
|
-
const
|
|
818
|
+
const tokenEndpoint = swapType === SwapType.EXACT_INPUT ? path[0] : path.at(-1);
|
|
819
|
+
const shouldAdjust = compareAddresses(tokenEndpoint, weth);
|
|
815
820
|
if (!shouldAdjust) {
|
|
816
821
|
return { adjustedTokenAmount: tokenAmount, swapFee: 0n };
|
|
817
822
|
}
|
|
@@ -826,7 +831,8 @@ export default class PoolHelper {
|
|
|
826
831
|
const sign = swapType === SwapType.EXACT_INPUT ? -1n : 1n;
|
|
827
832
|
const slippageTolerance = calcSlippageTolerance(quote, slippage) * sign;
|
|
828
833
|
const quoteWithSlippage = quote + slippageTolerance;
|
|
829
|
-
const
|
|
834
|
+
const quoteEndpoint = swapType === SwapType.EXACT_INPUT ? path.at(-1) : path[0];
|
|
835
|
+
const shouldAdjustQuote = compareAddresses(quoteEndpoint, weth);
|
|
830
836
|
if (!shouldAdjustQuote) {
|
|
831
837
|
return { adjustedQuote: quoteWithSlippage, swapFee: 0n };
|
|
832
838
|
}
|
|
@@ -4,6 +4,7 @@ import { AerodromeV2Adapter__factory, UniswapV2Adapter__factory, UniswapV3Adapte
|
|
|
4
4
|
import { finalizeRouteQuote } from './finalizeRoute.js';
|
|
5
5
|
export async function quoteWethTrade({ feeTier, input, poolHelper, finalization, }) {
|
|
6
6
|
const weth = poolHelper.addresses.tokens.WETH;
|
|
7
|
+
const routePath = input.path ?? [input.tokenIn, input.tokenOut];
|
|
7
8
|
const wethIn = compareAddresses(input.tokenIn, weth);
|
|
8
9
|
const wethOut = compareAddresses(input.tokenOut, weth);
|
|
9
10
|
if (!wethIn && !wethOut) {
|
|
@@ -11,8 +12,9 @@ export async function quoteWethTrade({ feeTier, input, poolHelper, finalization,
|
|
|
11
12
|
}
|
|
12
13
|
const quote = await poolHelper.getBestQuote({
|
|
13
14
|
tokenAmount: input.amountIn,
|
|
14
|
-
path:
|
|
15
|
+
path: routePath,
|
|
15
16
|
slippage: finalization ? 0n : input.slippage,
|
|
17
|
+
exchangeFactory: input.exchangeFactory,
|
|
16
18
|
});
|
|
17
19
|
const adapter = poolHelper.getLotusAdapter(quote.exchangeFactory);
|
|
18
20
|
const dex = poolHelper.getDexData(quote.exchangeFactory);
|
|
@@ -46,7 +48,7 @@ export async function quoteWethTrade({ feeTier, input, poolHelper, finalization,
|
|
|
46
48
|
const data = {
|
|
47
49
|
amountToSend: input.amountIn,
|
|
48
50
|
amountToReceive,
|
|
49
|
-
path:
|
|
51
|
+
path: routePath,
|
|
50
52
|
deadline,
|
|
51
53
|
};
|
|
52
54
|
return {
|
|
@@ -86,7 +88,7 @@ export async function quoteWethTrade({ feeTier, input, poolHelper, finalization,
|
|
|
86
88
|
adapter,
|
|
87
89
|
account: finalization.account,
|
|
88
90
|
path: dex.type === 'v2'
|
|
89
|
-
?
|
|
91
|
+
? routePath
|
|
90
92
|
: [
|
|
91
93
|
{
|
|
92
94
|
tokenIn: input.tokenIn,
|
|
@@ -119,7 +121,7 @@ export async function quoteWethTrade({ feeTier, input, poolHelper, finalization,
|
|
|
119
121
|
currency: weth,
|
|
120
122
|
amount: finalized.finalTollAmount,
|
|
121
123
|
},
|
|
122
|
-
hops: 1,
|
|
124
|
+
hops: routePath.length - 1,
|
|
123
125
|
effectiveSlippageBps: finalized.effectiveSlippageBps,
|
|
124
126
|
};
|
|
125
127
|
}
|
|
@@ -40,10 +40,14 @@ export async function findMaxPassingAmountToReceive({ chainId, blockNumber, cont
|
|
|
40
40
|
return success ? high : amountWithSlippage;
|
|
41
41
|
}
|
|
42
42
|
const candidates = generateCandidates(high, low, BUNDLE_SIMULATION_CANDIDATES);
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
.
|
|
43
|
+
const successIndices = [];
|
|
44
|
+
for (const [index, amount] of candidates.entries()) {
|
|
45
|
+
const result = await simulate(encodeVaultExecute(adapter, buildCallDataForAmount(amount)));
|
|
46
|
+
if (result.success)
|
|
47
|
+
successIndices.push(index);
|
|
48
|
+
if (successIndices.length === 2)
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
47
51
|
if (successIndices.length === 0) {
|
|
48
52
|
console.warn('Found no successful simulations for executable route quote', {
|
|
49
53
|
chainId,
|
|
@@ -23,9 +23,11 @@ interface DexscreenerPair {
|
|
|
23
23
|
liquidity?: {
|
|
24
24
|
usd?: number;
|
|
25
25
|
};
|
|
26
|
+
pairCreatedAt?: number;
|
|
26
27
|
}
|
|
27
28
|
export declare function selectV4PairPoolIds(pairs: DexscreenerPair[], tokenIn: string, tokenOut: string): string[];
|
|
28
29
|
export declare function stitchNativeBridgeV4Paths(firstLegs: V4Path[], secondLegs: V4Path[]): V4Path[];
|
|
29
30
|
export declare function discoverV4Paths(chainId: GuruProtocolChainId, tokenIn: string, tokenOut: string, provider: Provider, endpoint?: string): Promise<V4Path[]>;
|
|
31
|
+
export declare function findKnownV4BridgeKeys(chainId: GuruProtocolChainId, tokenIn: string, tokenOut: string): V4PoolKey[];
|
|
30
32
|
export declare function _clearV4DiscoveryCache(): void;
|
|
31
33
|
export {};
|
|
@@ -19,8 +19,37 @@ const V4_CHAIN_CONFIG = {
|
|
|
19
19
|
deployBlock: 0,
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
|
+
const KNOWN_V4_BRIDGE_KEYS = {
|
|
23
|
+
4663: [
|
|
24
|
+
{
|
|
25
|
+
currency0: V4_ZERO_ADDRESS,
|
|
26
|
+
currency1: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
|
|
27
|
+
fee: 25,
|
|
28
|
+
tickSpacing: 1,
|
|
29
|
+
hooks: V4_ZERO_ADDRESS,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
currency0: V4_ZERO_ADDRESS,
|
|
33
|
+
currency1: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
|
|
34
|
+
fee: 100,
|
|
35
|
+
tickSpacing: 1,
|
|
36
|
+
hooks: V4_ZERO_ADDRESS,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
currency0: V4_ZERO_ADDRESS,
|
|
40
|
+
currency1: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
|
|
41
|
+
fee: 500,
|
|
42
|
+
tickSpacing: 10,
|
|
43
|
+
hooks: V4_ZERO_ADDRESS,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
22
47
|
const POOL_INITIALIZE_TOPIC = '0xdd466e674ea557f56295e2d0218a125ea4b4f0f6f3307b95f85e6110838d6438';
|
|
23
48
|
export function isSafeDiscoveredV4PoolKey(key) {
|
|
49
|
+
const dynamicFeeFlag = 0x800000;
|
|
50
|
+
if (key.fee === dynamicFeeFlag) {
|
|
51
|
+
return !compareAddresses(key.hooks, V4_ZERO_ADDRESS);
|
|
52
|
+
}
|
|
24
53
|
return key.fee >= 0 && key.fee <= V4_DISCOVERY_MAX_LP_FEE;
|
|
25
54
|
}
|
|
26
55
|
export function computeV4PoolId(key) {
|
|
@@ -66,18 +95,63 @@ export function stitchNativeBridgeV4Paths(firstLegs, secondLegs) {
|
|
|
66
95
|
return paths;
|
|
67
96
|
}
|
|
68
97
|
const poolKeyCache = new Map();
|
|
69
|
-
async function resolvePoolKey(chainId, poolId, provider) {
|
|
98
|
+
async function resolvePoolKey(chainId, poolId, provider, pairCreatedAt) {
|
|
70
99
|
const cacheKey = `${chainId}:${poolId}`;
|
|
71
100
|
const cached = poolKeyCache.get(cacheKey);
|
|
72
101
|
if (cached)
|
|
73
102
|
return cached;
|
|
74
103
|
const config = V4_CHAIN_CONFIG[chainId];
|
|
75
|
-
const
|
|
104
|
+
const filter = {
|
|
76
105
|
address: config.poolManager,
|
|
77
106
|
topics: [POOL_INITIALIZE_TOPIC, poolId],
|
|
78
107
|
fromBlock: config.deployBlock,
|
|
79
108
|
toBlock: 'latest',
|
|
80
|
-
}
|
|
109
|
+
};
|
|
110
|
+
let logs;
|
|
111
|
+
try {
|
|
112
|
+
logs = await provider.getLogs(filter);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
logs = [];
|
|
116
|
+
const latestBlock = await provider.getBlockNumber();
|
|
117
|
+
const chunkSize = 9_999;
|
|
118
|
+
if (pairCreatedAt) {
|
|
119
|
+
let low = config.deployBlock;
|
|
120
|
+
let high = latestBlock;
|
|
121
|
+
const targetTimestamp = Math.floor(pairCreatedAt / 1_000);
|
|
122
|
+
while (low < high) {
|
|
123
|
+
const middle = Math.floor((low + high) / 2);
|
|
124
|
+
const block = await provider.getBlock(middle);
|
|
125
|
+
if (!block)
|
|
126
|
+
break;
|
|
127
|
+
if (block.timestamp < targetTimestamp)
|
|
128
|
+
low = middle + 1;
|
|
129
|
+
else
|
|
130
|
+
high = middle;
|
|
131
|
+
}
|
|
132
|
+
const fromBlock = Math.max(config.deployBlock, low - 5_000);
|
|
133
|
+
const toBlock = Math.min(latestBlock, fromBlock + chunkSize);
|
|
134
|
+
logs = await provider.getLogs({
|
|
135
|
+
...filter,
|
|
136
|
+
fromBlock,
|
|
137
|
+
toBlock,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (logs.length === 0) {
|
|
141
|
+
for (let toBlock = latestBlock; toBlock >= config.deployBlock; toBlock -= chunkSize + 1) {
|
|
142
|
+
const fromBlock = Math.max(config.deployBlock, toBlock - chunkSize);
|
|
143
|
+
const chunk = await provider.getLogs({
|
|
144
|
+
...filter,
|
|
145
|
+
fromBlock,
|
|
146
|
+
toBlock,
|
|
147
|
+
});
|
|
148
|
+
if (chunk.length > 0) {
|
|
149
|
+
logs = chunk;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
81
155
|
const log = logs[0];
|
|
82
156
|
if (!log)
|
|
83
157
|
return null;
|
|
@@ -142,6 +216,7 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
|
|
|
142
216
|
const isMajor = (token) => majors.some((major) => compareAddresses(major, token));
|
|
143
217
|
const queryOrder = isMajor(tokenIn) ? [tokenOut, tokenIn] : [tokenIn, tokenOut];
|
|
144
218
|
let poolIds = [];
|
|
219
|
+
const pairCreatedAtByPoolId = new Map();
|
|
145
220
|
for (const queryToken of queryOrder) {
|
|
146
221
|
const response = await fetch(`${endpoint}/${config.dexscreenerSlug}/${queryToken}`);
|
|
147
222
|
if (!response.ok) {
|
|
@@ -149,15 +224,23 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
|
|
|
149
224
|
}
|
|
150
225
|
const pairs = (await response.json());
|
|
151
226
|
poolIds = selectV4PairPoolIds(pairs, tokenIn, tokenOut);
|
|
152
|
-
if (poolIds.length > 0)
|
|
227
|
+
if (poolIds.length > 0) {
|
|
228
|
+
for (const pair of pairs) {
|
|
229
|
+
if (!pair.pairAddress || !pair.pairCreatedAt)
|
|
230
|
+
continue;
|
|
231
|
+
pairCreatedAtByPoolId.set(pair.pairAddress.toLowerCase(), pair.pairCreatedAt);
|
|
232
|
+
}
|
|
153
233
|
break;
|
|
234
|
+
}
|
|
154
235
|
}
|
|
155
|
-
const keys = await Promise.all(poolIds.map((poolId) => resolvePoolKey(chainId, poolId, provider)));
|
|
236
|
+
const keys = await Promise.all(poolIds.map((poolId) => resolvePoolKey(chainId, poolId, provider, pairCreatedAtByPoolId.get(poolId))));
|
|
156
237
|
const resolvedKeys = keys.filter((key) => key !== null && isSafeDiscoveredV4PoolKey(key));
|
|
157
|
-
const
|
|
238
|
+
const knownKeys = findKnownV4BridgeKeys(chainId, tokenIn, tokenOut);
|
|
239
|
+
const onchainKeys = resolvedKeys.length > 0 || knownKeys.length > 0
|
|
158
240
|
? []
|
|
159
|
-
: await discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider);
|
|
241
|
+
: await discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider).catch(() => []);
|
|
160
242
|
const paths = resolvedKeys
|
|
243
|
+
.concat(knownKeys)
|
|
161
244
|
.concat(onchainKeys)
|
|
162
245
|
.filter((key) => key !== null)
|
|
163
246
|
.map((key) => [
|
|
@@ -172,6 +255,12 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
|
|
|
172
255
|
]);
|
|
173
256
|
return paths;
|
|
174
257
|
}
|
|
258
|
+
export function findKnownV4BridgeKeys(chainId, tokenIn, tokenOut) {
|
|
259
|
+
return (KNOWN_V4_BRIDGE_KEYS[chainId] ?? []).filter((key) => (compareAddresses(key.currency0, tokenIn) &&
|
|
260
|
+
compareAddresses(key.currency1, tokenOut)) ||
|
|
261
|
+
(compareAddresses(key.currency0, tokenOut) &&
|
|
262
|
+
compareAddresses(key.currency1, tokenIn)));
|
|
263
|
+
}
|
|
175
264
|
async function discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider) {
|
|
176
265
|
const config = V4_CHAIN_CONFIG[chainId];
|
|
177
266
|
const [currency0, currency1] = tokenIn.toLowerCase() < tokenOut.toLowerCase()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TransactionRequest } from 'ethers';
|
|
2
|
+
export interface BuildTradesTxParams {
|
|
3
|
+
controller: string;
|
|
4
|
+
ledger: string;
|
|
5
|
+
adapters: string[];
|
|
6
|
+
callData: string[];
|
|
7
|
+
from: string;
|
|
8
|
+
}
|
|
9
|
+
export default function buildTradesTx(params: BuildTradesTxParams): TransactionRequest;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FundController__factory } from '../typechain/index.js';
|
|
2
|
+
export default function buildTradesTx(params) {
|
|
3
|
+
const data = FundController__factory.createInterface().encodeFunctionData('executeTrades', [params.ledger, params.adapters, params.callData]);
|
|
4
|
+
return {
|
|
5
|
+
to: params.controller,
|
|
6
|
+
from: params.from,
|
|
7
|
+
data,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -7,4 +7,6 @@ export type { BuildWithdrawTxParams } from './buildWithdrawTx';
|
|
|
7
7
|
export { default as buildHarvestTx } from './buildHarvestTx';
|
|
8
8
|
export type { BuildHarvestTxParams } from './buildHarvestTx';
|
|
9
9
|
export { default as buildTradeTx } from './buildTradeTx';
|
|
10
|
+
export { default as buildTradesTx } from './buildTradesTx';
|
|
10
11
|
export type { BuildTradeTxParams } from './buildTradeTx';
|
|
12
|
+
export type { BuildTradesTxParams } from './buildTradesTx';
|
package/dist/txBuilders/index.js
CHANGED
|
@@ -3,3 +3,4 @@ export { default as buildCloseTx } from './buildCloseTx.js';
|
|
|
3
3
|
export { default as buildWithdrawTx } from './buildWithdrawTx.js';
|
|
4
4
|
export { default as buildHarvestTx } from './buildHarvestTx.js';
|
|
5
5
|
export { default as buildTradeTx } from './buildTradeTx.js';
|
|
6
|
+
export { default as buildTradesTx } from './buildTradesTx.js';
|