@guru-fund/sdk 0.2.10 → 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.
@@ -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 route = await getRoute({
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: parsed.maxSlippage != null
36
- ? Number(parsed.maxSlippage / 10n)
37
- : undefined,
82
+ slippageE2,
38
83
  account: vaultAddress,
39
84
  vault: vaultAddress,
40
- }, ctx);
41
- return {
42
- ...route,
43
- txData: buildTradeTx({
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
- const pairedToken = compareAddresses(tokenIn, weth) ? tokenOut : tokenIn;
65
- const coinTokenPool = await poolHelper.getUniswapCompatibleTokenPool(pairedToken);
66
- return quoteWethTrade({
67
- feeTier: coinTokenPool.feeTier,
68
- input: { tokenIn, tokenOut, amountIn, slippage },
69
- poolHelper,
70
- finalization,
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
- const pairedToken = compareAddresses(tokenIn, weth) ? tokenOut : tokenIn;
117
- const coinTokenPool = await poolHelper.getUniswapCompatibleTokenPool(pairedToken);
118
- const route = await quoteWethTrade({
119
- feeTier: coinTokenPool.feeTier,
120
- input: { tokenIn, tokenOut, amountIn, slippage },
121
- poolHelper,
122
- finalization,
123
- });
124
- return { ...route, hops: 1 };
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,
@@ -52,13 +52,14 @@ export type QuoteRequest = {
52
52
  swapType?: SwapType;
53
53
  tokenAmount: bigint;
54
54
  slippage?: bigint;
55
- path: [string, string];
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: [string, string];
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 shouldAdjust = compareAddresses(path[Number(swapType)], weth);
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 shouldAdjustQuote = !compareAddresses(path[Number(swapType)], weth);
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
  }
@@ -7,6 +7,8 @@ export interface QuoteWethTradeParams {
7
7
  tokenOut: string;
8
8
  amountIn: bigint;
9
9
  slippage: bigint;
10
+ path?: string[];
11
+ exchangeFactory?: string;
10
12
  };
11
13
  poolHelper: PoolHelper;
12
14
  finalization?: PoolRouteFinalizationContext;
@@ -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: [input.tokenIn, input.tokenOut],
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: [input.tokenIn, input.tokenOut],
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
- ? [input.tokenIn, input.tokenOut]
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 results = await Promise.all(candidates.map((amount) => simulate(encodeVaultExecute(adapter, buildCallDataForAmount(amount)))));
44
- const successIndices = results
45
- .map((result, index) => (result.success ? index : -1))
46
- .filter((index) => index !== -1);
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,
@@ -28,5 +28,6 @@ interface DexscreenerPair {
28
28
  export declare function selectV4PairPoolIds(pairs: DexscreenerPair[], tokenIn: string, tokenOut: string): string[];
29
29
  export declare function stitchNativeBridgeV4Paths(firstLegs: V4Path[], secondLegs: V4Path[]): V4Path[];
30
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[];
31
32
  export declare function _clearV4DiscoveryCache(): void;
32
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) {
@@ -206,10 +235,12 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
206
235
  }
207
236
  const keys = await Promise.all(poolIds.map((poolId) => resolvePoolKey(chainId, poolId, provider, pairCreatedAtByPoolId.get(poolId))));
208
237
  const resolvedKeys = keys.filter((key) => key !== null && isSafeDiscoveredV4PoolKey(key));
209
- const onchainKeys = resolvedKeys.length > 0
238
+ const knownKeys = findKnownV4BridgeKeys(chainId, tokenIn, tokenOut);
239
+ const onchainKeys = resolvedKeys.length > 0 || knownKeys.length > 0
210
240
  ? []
211
- : await discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider);
241
+ : await discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider).catch(() => []);
212
242
  const paths = resolvedKeys
243
+ .concat(knownKeys)
213
244
  .concat(onchainKeys)
214
245
  .filter((key) => key !== null)
215
246
  .map((key) => [
@@ -224,6 +255,12 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
224
255
  ]);
225
256
  return paths;
226
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
+ }
227
264
  async function discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider) {
228
265
  const config = V4_CHAIN_CONFIG[chainId];
229
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';
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guru-fund/sdk",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Guru Protocol SDK — quote + transaction builders for on-chain managed funds.",
5
5
  "license": "MIT",
6
6
  "type": "module",