@guru-fund/sdk 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ import { GuruProtocol } from '@guru-fund/sdk'
23
23
 
24
24
  const protocol = new GuruProtocol({
25
25
  rpcUrl: 'https://mainnet.infura.io/v3/<key>',
26
- chainId: 1, // mainnet (1) and base (8453) are supported
26
+ chainId: 1, // mainnet (1), base (8453), and Robinhood (4663) are supported
27
27
  })
28
28
  ```
29
29
 
@@ -32,7 +32,7 @@ const protocol = new GuruProtocol({
32
32
  - Builds an internal `JsonRpcProvider(rpcUrl)`. Do not pass a provider in.
33
33
  - Resolves the Guru Protocol contract addresses from the SDK's vendored registry. Do
34
34
  not pass contracts in.
35
- - Throws `UnsupportedChainError` for any chain outside `{ 1, 8453 }`.
35
+ - Throws `UnsupportedChainError` for any chain outside `{ 1, 8453, 4663 }`.
36
36
 
37
37
  Optional override slots (`GuruProtocolOptions`): `simulator`, `getSwapFeePercentage`,
38
38
  `getPriceUsd1e18`, `veloraEndpoint`, `getPath`. Each has a sensible default —
@@ -213,7 +213,7 @@ const decoded = quote.decodeLogs(receipt!.logs) ?? {
213
213
  ## Errors
214
214
 
215
215
  - `UnsupportedChainError` — thrown by the constructor on `chainId` outside
216
- `{ 1, 8453 }`.
216
+ `{ 1, 8453, 4663 }`.
217
217
  - `z.ZodError` — thrown by quote methods on malformed inputs (zod schema at
218
218
  the SDK boundary parses every quote method's input).
219
219
  - `SdkError` — internal errors (e.g., `EVENT_NOT_FOUND` from the receipt
@@ -3,11 +3,13 @@ import { type GuruProtocolAddresses, type GuruProtocolChainId } from './addresse
3
3
  import type { GetPriceUsd1e18 } from './helpers/FundDataFetcher';
4
4
  import { type QuoteDepositParams, type QuoteDepositResult } from './quotes/quoteDeposit';
5
5
  import { type QuoteHarvestParams, type QuoteHarvestResult } from './quotes/quoteHarvest';
6
+ import { type QuoteCloseParams, type QuoteCloseResult } from './quotes/quoteClose';
6
7
  import { type QuoteRebalanceParams, type QuoteRebalanceResult } from './quotes/quoteRebalance';
7
8
  import { type QuoteTradeParams, type QuoteTradeResult } from './quotes/quoteTrade';
8
9
  import { type QuoteWithdrawalParams, type QuoteWithdrawalResult } from './quotes/quoteWithdrawal';
9
10
  import { type PathFetcher } from './router/pathCache';
10
11
  import type { SwapSimulator } from './router/simulation';
12
+ import buildCloseTx from './txBuilders/buildCloseTx';
11
13
  import buildDepositTx from './txBuilders/buildDepositTx';
12
14
  import buildHarvestTx from './txBuilders/buildHarvestTx';
13
15
  import buildTradeTx from './txBuilders/buildTradeTx';
@@ -35,8 +37,10 @@ export declare class GuruProtocol {
35
37
  quoteWithdrawal(params: QuoteWithdrawalParams): Promise<QuoteWithdrawalResult>;
36
38
  quoteTrade(params: QuoteTradeParams): Promise<QuoteTradeResult>;
37
39
  quoteHarvest(params: QuoteHarvestParams): Promise<QuoteHarvestResult>;
40
+ quoteClose(params: QuoteCloseParams): Promise<QuoteCloseResult>;
38
41
  quoteRebalance(params: QuoteRebalanceParams): Promise<QuoteRebalanceResult>;
39
42
  static buildDepositTx: typeof buildDepositTx;
43
+ static buildCloseTx: typeof buildCloseTx;
40
44
  static buildWithdrawTx: typeof buildWithdrawTx;
41
45
  static buildHarvestTx: typeof buildHarvestTx;
42
46
  static buildTradeTx: typeof buildTradeTx;
@@ -2,11 +2,13 @@ import { JsonRpcProvider } from 'ethers';
2
2
  import { getGuruProtocolAddresses, isSupportedChainId, UnsupportedChainError, } from './addresses.js';
3
3
  import quoteDeposit from './quotes/quoteDeposit.js';
4
4
  import quoteHarvest from './quotes/quoteHarvest.js';
5
+ import quoteClose from './quotes/quoteClose.js';
5
6
  import quoteRebalance from './quotes/quoteRebalance.js';
6
7
  import quoteTrade from './quotes/quoteTrade.js';
7
8
  import quoteWithdrawal from './quotes/quoteWithdrawal.js';
8
9
  import { getPriceUsd1e18 as defaultGetPriceUsd1e18 } from './router/index.js';
9
10
  import { getPath as defaultGetPath } from './router/pathCache.js';
11
+ import buildCloseTx from './txBuilders/buildCloseTx.js';
10
12
  import buildDepositTx from './txBuilders/buildDepositTx.js';
11
13
  import buildHarvestTx from './txBuilders/buildHarvestTx.js';
12
14
  import buildTradeTx from './txBuilders/buildTradeTx.js';
@@ -64,10 +66,14 @@ export class GuruProtocol {
64
66
  quoteHarvest(params) {
65
67
  return quoteHarvest(params, this.routerCtx());
66
68
  }
69
+ quoteClose(params) {
70
+ return quoteClose(params, this.routerCtx());
71
+ }
67
72
  quoteRebalance(params) {
68
73
  return quoteRebalance(params, this.routerCtx());
69
74
  }
70
75
  static buildDepositTx = buildDepositTx;
76
+ static buildCloseTx = buildCloseTx;
71
77
  static buildWithdrawTx = buildWithdrawTx;
72
78
  static buildHarvestTx = buildHarvestTx;
73
79
  static buildTradeTx = buildTradeTx;
@@ -1,4 +1,4 @@
1
- export type GuruProtocolChainId = 1 | 8453;
1
+ export type GuruProtocolChainId = 1 | 8453 | 4663;
2
2
  type AdapterKey = 'uniswapV2' | 'uniswapV3' | 'uniswapV4' | 'uniswapV4Hook' | 'pancakeV2' | 'pancakeV3' | 'aerodromeV2' | 'aerodromeV3';
3
3
  type FactoryKey = AdapterKey | 'aerodromeV3Bis';
4
4
  type RouterKey = AdapterKey;
@@ -26,6 +26,7 @@ export type GuruProtocolAddresses = {
26
26
  WETH: string;
27
27
  USDC: string;
28
28
  USDT: string;
29
+ USDG?: string;
29
30
  }>;
30
31
  readonly migrator?: string;
31
32
  };
package/dist/addresses.js CHANGED
@@ -95,14 +95,48 @@ const BASE = {
95
95
  },
96
96
  migrator: '0x209656784c8eb405765288c8f447d11c0aa1a3d3',
97
97
  };
98
+ const ROBINHOOD = {
99
+ chainId: 4663,
100
+ protocol: '0xe0d99cd8bf9f091713c88ff763d669ad3703876c',
101
+ vaultImplementation: '0x70451b00c42a73ea2d9707ddc7413aff8620be85',
102
+ ledgerImplementation: '0xa8644a5eb1cf74f0d8c7771d3bca9cb5f4edd6a5',
103
+ controllers: {
104
+ fund: '0x36e2de2f1b66ecaa5fda2196abb88ce663616533',
105
+ },
106
+ adapters: {
107
+ uniswapV2: '0xdeb704836165043c172ae80467249ff87429605f',
108
+ uniswapV3: '0x34e1efca367b1686af9bc3eb9a593c02c2b295f6',
109
+ uniswapV4: '0x907e28e12a36a41a9cf01e1f455f4778408da198',
110
+ },
111
+ factories: {
112
+ uniswapV2: '0x8bceaa40b9acdfaedf85adf4ff01f5ad6517937f',
113
+ uniswapV3: '0x1f7d7550b1b028f7571e69a784071f0205fd2efa',
114
+ },
115
+ routers: {
116
+ uniswapV2: '0x89e5db8b5aa49aa85ac63f691524311aeb649eba',
117
+ uniswapV3: '0xcaf681a66d020601342297493863e78c959e5cb2',
118
+ uniswapV4: '0x8876789976decbfcbbbe364623c63652db8c0904',
119
+ },
120
+ quoters: {
121
+ uniswapV3: '0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7',
122
+ uniswapV4: '0x8dc178efb8111bb0973dd9d722ebeff267c98f94',
123
+ },
124
+ tokens: {
125
+ WETH: '0x0bd7d308f8e1639fab988df18a8011f41eacad73',
126
+ USDC: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
127
+ USDT: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
128
+ USDG: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
129
+ },
130
+ };
98
131
  export const GURU_PROTOCOL_ADDRESSES = {
99
132
  1: MAINNET,
100
133
  8453: BASE,
134
+ 4663: ROBINHOOD,
101
135
  };
102
136
  export const SUPPORTED_CHAIN_IDS = [
103
- 1, 8453,
137
+ 1, 8453, 4663,
104
138
  ];
105
- export const isSupportedChainId = (chainId) => chainId === 1 || chainId === 8453;
139
+ export const isSupportedChainId = (chainId) => chainId === 1 || chainId === 8453 || chainId === 4663;
106
140
  export function getGuruProtocolAddresses(chainId) {
107
141
  if (!isSupportedChainId(chainId)) {
108
142
  throw new UnsupportedChainError(chainId);
@@ -1,6 +1,6 @@
1
1
  import { ContractTransactionReceipt, Interface } from 'ethers';
2
2
  import { TypedContractEvent } from '../typechain/common';
3
- type TypechainOutputObject<T> = T extends TypedContractEvent<infer _U, infer _W, infer V> ? V : never;
3
+ type TypechainOutputObject<T> = T extends TypedContractEvent<infer _U, infer _W, infer V> ? keyof V extends never ? any : V : any;
4
4
  export default class ReceiptParser {
5
5
  private receipt;
6
6
  constructor(receipt: ContractTransactionReceipt);
package/dist/index.d.ts CHANGED
@@ -3,11 +3,12 @@ export { GURU_PROTOCOL_ADDRESSES, SUPPORTED_CHAIN_IDS, UnsupportedChainError, ge
3
3
  export type { GuruProtocolAddresses, GuruProtocolChainId } from './addresses';
4
4
  export { GuruProtocol } from './GuruProtocol';
5
5
  export type { GuruProtocolOptions } from './GuruProtocol';
6
- export type { BuildDepositTxParams, BuildHarvestTxParams, BuildTradeTxParams, BuildWithdrawTxParams, } from './txBuilders';
6
+ export type { BuildDepositTxParams, BuildCloseTxParams, BuildHarvestTxParams, BuildTradeTxParams, BuildWithdrawTxParams, } from './txBuilders';
7
7
  export type { QuoteDepositParams, QuoteDepositResult, QuoteDepositLogs, } from './quotes/quoteDeposit';
8
8
  export type { QuoteWithdrawalParams, QuoteWithdrawalResult, QuoteWithdrawalLogs, } from './quotes/quoteWithdrawal';
9
9
  export type { QuoteTradeParams, QuoteTradeResult } from './quotes/quoteTrade';
10
10
  export type { QuoteHarvestParams, QuoteHarvestResult, QuoteHarvestLogs, } from './quotes/quoteHarvest';
11
+ export type { QuoteCloseParams, QuoteCloseResult } from './quotes/quoteClose';
11
12
  export type { QuoteRebalanceParams, QuoteRebalanceResult, QuoteRebalanceLogs, QuoteRebalanceTrade, QuoteRebalanceLogTrade, QuoteRebalanceEmptyReason, QuoteRebalanceTargetWeight, } from './quotes/quoteRebalance';
12
13
  export type { Route, RouteSearchParams, V2Path, V3Path, V3PathHop, CachedPath, } from './router/types';
13
14
  export type { PathFetcher, GetPathParams } from './router/pathCache';
@@ -0,0 +1,22 @@
1
+ import { type AddressLike, type BytesLike, type TransactionRequest } from 'ethers';
2
+ import { type GuruProtocolChainId } from '../addresses';
3
+ import { type GetPriceUsd1e18 } from '../helpers/FundDataFetcher';
4
+ import { type RouterContext } from '../router';
5
+ export type ExternalCallStruct = {
6
+ adapter: AddressLike;
7
+ callData: BytesLike;
8
+ };
9
+ export interface QuoteCloseParams {
10
+ ledger: string;
11
+ coin: string;
12
+ slippageSettings?: Record<string, bigint>;
13
+ }
14
+ export interface QuoteCloseContext extends RouterContext {
15
+ chainId: GuruProtocolChainId;
16
+ getPriceUsd1e18: GetPriceUsd1e18;
17
+ }
18
+ export interface QuoteCloseResult {
19
+ extCalls: ExternalCallStruct[];
20
+ txData: TransactionRequest;
21
+ }
22
+ export default function quoteClose(params: QuoteCloseParams, ctx: QuoteCloseContext): Promise<QuoteCloseResult>;
@@ -0,0 +1,57 @@
1
+ import { ZeroAddress, } from 'ethers';
2
+ import { getGuruProtocolAddresses, } from '../addresses.js';
3
+ import compareAddresses from '../helpers/compareAddresses.js';
4
+ import FundDataFetcher from '../helpers/FundDataFetcher.js';
5
+ import { getRouteOut } from '../router/index.js';
6
+ import { stablecoinAddresses } from '../router/helpers.js';
7
+ import { quoteCloseSchema } from '../schemas/quoteClose.js';
8
+ import buildCloseTx from '../txBuilders/buildCloseTx.js';
9
+ import { FundLedger__factory } from '../typechain/index.js';
10
+ const slippageE2For = (settings, token) => {
11
+ const slippage = settings?.[token.toLowerCase()];
12
+ return slippage == null ? undefined : Number(slippage / 10n);
13
+ };
14
+ export default async function quoteClose(params, ctx) {
15
+ const parsed = quoteCloseSchema.parse(params);
16
+ const addresses = getGuruProtocolAddresses(ctx.chainId);
17
+ const isStablecoin = stablecoinAddresses(addresses).some((stable) => compareAddresses(parsed.coin, stable));
18
+ if (!isStablecoin) {
19
+ throw new Error(`[@guru-fund/sdk] quoteClose: coin ${parsed.coin} is not a supported stablecoin on chainId ${ctx.chainId}`);
20
+ }
21
+ const provider = ctx.provider;
22
+ const ledger = FundLedger__factory.connect(parsed.ledger, provider);
23
+ const fetcher = new FundDataFetcher({
24
+ chainId: ctx.chainId,
25
+ provider,
26
+ getPriceUsd1e18: ctx.getPriceUsd1e18,
27
+ });
28
+ const [fundData, vault, manager, controllerAddress] = await Promise.all([
29
+ fetcher.fetchFundData(ledger),
30
+ ledger.vault(),
31
+ ledger.manager(),
32
+ ledger.controller(),
33
+ ]);
34
+ const extCalls = await Promise.all(fundData.assets.map(async (asset) => {
35
+ if (compareAddresses(asset.token.address, parsed.coin)) {
36
+ return { adapter: ZeroAddress, callData: '0x' };
37
+ }
38
+ const route = await getRouteOut({
39
+ chainId: ctx.chainId,
40
+ tokenIn: asset.token.address,
41
+ tokenOut: parsed.coin,
42
+ amountIn: asset.balance,
43
+ slippageE2: slippageE2For(parsed.slippageSettings, asset.token.address),
44
+ account: vault,
45
+ vault,
46
+ }, ctx);
47
+ return { adapter: route.adapter, callData: route.callData };
48
+ }));
49
+ const txData = buildCloseTx({
50
+ controller: controllerAddress,
51
+ ledger: parsed.ledger,
52
+ coin: parsed.coin,
53
+ extCalls,
54
+ from: manager,
55
+ });
56
+ return { extCalls, txData };
57
+ }
@@ -4,6 +4,7 @@ import { GURU_TOKEN_MAINNET, MANAGEMENT_FEE_COOLDOWN_DAYS, MANAGEMENT_FEE_ELIGIB
4
4
  import compareAddresses from '../helpers/compareAddresses.js';
5
5
  import FundDataFetcher, { WEIGHT_DENOMINATOR, } from '../helpers/FundDataFetcher.js';
6
6
  import { getRouteOut } from '../router/index.js';
7
+ import { stablecoinAddresses } from '../router/helpers.js';
7
8
  import { quoteHarvestSchema } from '../schemas/quoteHarvest.js';
8
9
  import buildHarvestTx from '../txBuilders/buildHarvestTx.js';
9
10
  import { FundLedger__factory, HarvestController__factory } from '../typechain/index.js';
@@ -17,8 +18,7 @@ const slippageE2For = (settings, token) => {
17
18
  export default async function quoteHarvest(params, ctx) {
18
19
  const parsed = quoteHarvestSchema.parse(params);
19
20
  const addresses = getGuruProtocolAddresses(ctx.chainId);
20
- const isStablecoin = compareAddresses(parsed.coin, addresses.tokens.USDC) ||
21
- compareAddresses(parsed.coin, addresses.tokens.USDT);
21
+ const isStablecoin = stablecoinAddresses(addresses).some((stable) => compareAddresses(parsed.coin, stable));
22
22
  if (!isStablecoin) {
23
23
  throw new Error(`[@guru-fund/sdk] quoteHarvest: coin ${parsed.coin} is not a supported stablecoin on chainId ${ctx.chainId}`);
24
24
  }
@@ -8,4 +8,5 @@ export interface BaseTollContext {
8
8
  };
9
9
  swapAmountIn: bigint;
10
10
  }
11
+ export declare function stablecoinAddresses(addresses: GuruProtocolAddresses): string[];
11
12
  export declare function resolveBaseToll(addresses: GuruProtocolAddresses, tokenIn: string, tokenOut: string, amountIn: bigint): BaseTollContext;
@@ -5,12 +5,17 @@ export function withSlippageTolerance(amount, slippage) {
5
5
  const tolerance = (amount * slippage) / PERCENTAGE_DENOMINATOR;
6
6
  return amount - tolerance;
7
7
  }
8
+ export function stablecoinAddresses(addresses) {
9
+ return [
10
+ addresses.tokens.USDC,
11
+ addresses.tokens.USDT,
12
+ addresses.tokens.USDG,
13
+ ].filter((token) => Boolean(token));
14
+ }
8
15
  export function resolveBaseToll(addresses, tokenIn, tokenOut, amountIn) {
9
- const tollableTokens = [
10
- addresses.tokens.USDC.toLowerCase(),
11
- addresses.tokens.USDT.toLowerCase(),
12
- addresses.tokens.WETH.toLowerCase(),
13
- ];
16
+ const tollableTokens = stablecoinAddresses(addresses)
17
+ .concat(addresses.tokens.WETH)
18
+ .map((token) => token.toLowerCase());
14
19
  const tollIn = tollableTokens.some((token) => compareAddresses(token, tokenIn));
15
20
  const tollOut = !tollIn &&
16
21
  tollableTokens.some((token) => compareAddresses(token, tokenOut));
@@ -5,6 +5,7 @@ import { Token } from '../helpers/Token.js';
5
5
  import { getFallbackRouteIn, getFallbackRouteOut, } from './getFallbackRoutes.js';
6
6
  import getUniswapV4Route, { connectV4Quoter, toAdapterPathKeys, } from './getUniswapV4Route.js';
7
7
  import getVeloraRoute from './getVeloraRoute.js';
8
+ import { stablecoinAddresses } from './helpers.js';
8
9
  import PoolHelper from './poolHelper.js';
9
10
  import { discoverV4Paths } from './v4PoolDiscovery.js';
10
11
  const AERO_V3_FACTORY_ABI = [
@@ -92,8 +93,7 @@ export async function getRouteOut(params, ctx) {
92
93
  }
93
94
  export async function getPriceUsd1e18(token, ctx) {
94
95
  const addresses = getGuruProtocolAddresses(ctx.chainId);
95
- if (compareAddresses(token, addresses.tokens.USDC) ||
96
- compareAddresses(token, addresses.tokens.USDT)) {
96
+ if (stablecoinAddresses(addresses).some((stable) => compareAddresses(token, stable))) {
97
97
  return 10n ** 18n;
98
98
  }
99
99
  const poolHelper = new PoolHelper({
@@ -101,7 +101,7 @@ export async function getPriceUsd1e18(token, ctx) {
101
101
  provider: ctx.provider,
102
102
  getSwapFeePercentage: ctx.getSwapFeePercentage,
103
103
  });
104
- const stable = addresses.tokens.USDC;
104
+ const stable = addresses.tokens.USDG ?? addresses.tokens.USDC;
105
105
  const { decimals: stableDecimals } = await new Token(stable, ctx.provider)
106
106
  .metadata()
107
107
  .catch(() => ({ decimals: 6 }));
@@ -309,7 +309,7 @@ async function _priceViaV4Pools(token, oneToken, getWethUsd, ctx) {
309
309
  }
310
310
  return best > 0n ? best : null;
311
311
  };
312
- const inUsdc = await quoteVia(addresses.tokens.USDC);
312
+ const inUsdc = await quoteVia(addresses.tokens.USDG ?? addresses.tokens.USDC);
313
313
  if (inUsdc !== null) {
314
314
  return inUsdc * 10n ** 12n;
315
315
  }
@@ -45,13 +45,7 @@ export async function findMaxPassingAmountToReceive({ chainId, blockNumber, cont
45
45
  .map((result, index) => (result.success ? index : -1))
46
46
  .filter((index) => index !== -1);
47
47
  if (successIndices.length === 0) {
48
- if (maxSlippageE3) {
49
- const fallback = withSlippageTolerance(amountQuoted, maxSlippageE3);
50
- console.warn(`[findMax] All ${BUNDLE_SIMULATION_CANDIDATES} simulation candidates failed. ` +
51
- `Falling back to caller-provided max slippage (${Number(maxSlippageE3) / 1000}%).`, { amountQuoted, fallback, maxSlippageE3 });
52
- return fallback;
53
- }
54
- console.warn('Found no successful simulations with correct slippage, returning initial slippage', {
48
+ console.warn('Found no successful simulations for executable route quote', {
55
49
  chainId,
56
50
  blockNumber,
57
51
  controller,
@@ -62,7 +56,7 @@ export async function findMaxPassingAmountToReceive({ chainId, blockNumber, cont
62
56
  callData: encodeVaultExecute(adapter, buildCallDataForAmount(high)),
63
57
  path,
64
58
  });
65
- return amountWithSlippage;
59
+ throw new Error('NO_EXECUTABLE_ROUTE_FOUND');
66
60
  }
67
61
  const [bestIndex, secondBestIndex] = successIndices;
68
62
  const targetIndex = secondBestIndex ?? bestIndex;
@@ -1,4 +1,4 @@
1
- import { AbiCoder, keccak256 } from 'ethers';
1
+ import { AbiCoder, keccak256, zeroPadValue, } from 'ethers';
2
2
  import { getGuruProtocolAddresses, } from '../addresses.js';
3
3
  import compareAddresses from '../helpers/compareAddresses.js';
4
4
  import { DEXSCREENER_ENDPOINT, V4_ZERO_ADDRESS, V4_DISCOVERY_MAX_POOLS, V4_DISCOVERY_MIN_LIQUIDITY_USD, VELORA_NEGATIVE_CACHE_TTL, VELORA_PATH_CACHE_TTL, } from './constants.js';
@@ -13,6 +13,11 @@ const V4_CHAIN_CONFIG = {
13
13
  poolManager: '0x498581ff718922c3f8e6a244956af099b2652b2b',
14
14
  deployBlock: 25_000_000,
15
15
  },
16
+ 4663: {
17
+ dexscreenerSlug: 'robinhood',
18
+ poolManager: '0x8366a39cc670b4001a1121b8f6a443a643e40951',
19
+ deployBlock: 0,
20
+ },
16
21
  };
17
22
  const POOL_INITIALIZE_TOPIC = '0xdd466e674ea557f56295e2d0218a125ea4b4f0f6f3307b95f85e6110838d6438';
18
23
  export function computeV4PoolId(key) {
@@ -73,6 +78,19 @@ async function resolvePoolKey(chainId, poolId, provider) {
73
78
  const log = logs[0];
74
79
  if (!log)
75
80
  return null;
81
+ const key = poolKeyFromInitializeLog(log);
82
+ if (!key)
83
+ return null;
84
+ if (computeV4PoolId(key).toLowerCase() !== poolId.toLowerCase()) {
85
+ return null;
86
+ }
87
+ poolKeyCache.set(cacheKey, key);
88
+ return key;
89
+ }
90
+ function poolKeyFromInitializeLog(log) {
91
+ const poolId = log.topics[1];
92
+ if (!poolId || !log.topics[2] || !log.topics[3])
93
+ return null;
76
94
  const [fee, tickSpacing, hooks] = AbiCoder.defaultAbiCoder().decode(['uint24', 'int24', 'address', 'uint160', 'int24'], log.data);
77
95
  const key = {
78
96
  currency0: '0x' + log.topics[2].slice(26),
@@ -84,7 +102,6 @@ async function resolvePoolKey(chainId, poolId, provider) {
84
102
  if (computeV4PoolId(key).toLowerCase() !== poolId.toLowerCase()) {
85
103
  return null;
86
104
  }
87
- poolKeyCache.set(cacheKey, key);
88
105
  return key;
89
106
  }
90
107
  const discoveryCache = new Map();
@@ -112,7 +129,13 @@ export async function discoverV4Paths(chainId, tokenIn, tokenOut, provider, endp
112
129
  async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpoint, cachedPaths) {
113
130
  const config = V4_CHAIN_CONFIG[chainId];
114
131
  const { tokens } = getGuruProtocolAddresses(chainId);
115
- const majors = [tokens.WETH, tokens.USDC, tokens.USDT, V4_ZERO_ADDRESS];
132
+ const majors = [
133
+ tokens.WETH,
134
+ tokens.USDC,
135
+ tokens.USDT,
136
+ tokens.USDG,
137
+ V4_ZERO_ADDRESS,
138
+ ].filter((token) => Boolean(token));
116
139
  const isMajor = (token) => majors.some((major) => compareAddresses(major, token));
117
140
  const queryOrder = isMajor(tokenIn) ? [tokenOut, tokenIn] : [tokenIn, tokenOut];
118
141
  let poolIds = [];
@@ -127,7 +150,12 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
127
150
  break;
128
151
  }
129
152
  const keys = await Promise.all(poolIds.map((poolId) => resolvePoolKey(chainId, poolId, provider)));
130
- const paths = keys
153
+ const resolvedKeys = keys.filter((key) => key !== null);
154
+ const onchainKeys = resolvedKeys.length > 0
155
+ ? []
156
+ : await discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider);
157
+ const paths = resolvedKeys
158
+ .concat(onchainKeys)
131
159
  .filter((key) => key !== null)
132
160
  .map((key) => [
133
161
  {
@@ -141,6 +169,27 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
141
169
  ]);
142
170
  return paths;
143
171
  }
172
+ async function discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider) {
173
+ const config = V4_CHAIN_CONFIG[chainId];
174
+ const [currency0, currency1] = tokenIn.toLowerCase() < tokenOut.toLowerCase()
175
+ ? [tokenIn, tokenOut]
176
+ : [tokenOut, tokenIn];
177
+ const logs = await provider.getLogs({
178
+ address: config.poolManager,
179
+ topics: [
180
+ POOL_INITIALIZE_TOPIC,
181
+ null,
182
+ zeroPadValue(currency0, 32),
183
+ zeroPadValue(currency1, 32),
184
+ ],
185
+ fromBlock: config.deployBlock,
186
+ toBlock: 'latest',
187
+ });
188
+ return logs
189
+ .map(poolKeyFromInitializeLog)
190
+ .filter((key) => key !== null)
191
+ .sort((a, b) => a.fee - b.fee);
192
+ }
144
193
  export function _clearV4DiscoveryCache() {
145
194
  discoveryCache.clear();
146
195
  poolKeyCache.clear();
@@ -3,4 +3,5 @@ export { quoteDepositSchema } from './quoteDeposit';
3
3
  export { quoteWithdrawalSchema } from './quoteWithdrawal';
4
4
  export { quoteTradeSchema } from './quoteTrade';
5
5
  export { quoteHarvestSchema } from './quoteHarvest';
6
+ export { quoteCloseSchema } from './quoteClose';
6
7
  export { quoteRebalanceSchema } from './quoteRebalance';
@@ -3,4 +3,5 @@ export { quoteDepositSchema } from './quoteDeposit.js';
3
3
  export { quoteWithdrawalSchema } from './quoteWithdrawal.js';
4
4
  export { quoteTradeSchema } from './quoteTrade.js';
5
5
  export { quoteHarvestSchema } from './quoteHarvest.js';
6
+ export { quoteCloseSchema } from './quoteClose.js';
6
7
  export { quoteRebalanceSchema } from './quoteRebalance.js';
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+ export declare const quoteCloseSchema: z.ZodObject<{
3
+ ledger: z.ZodString;
4
+ coin: z.ZodString;
5
+ slippageSettings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodUnion<[z.ZodBigInt, z.ZodEffects<z.ZodNumber, bigint, number>]>, z.ZodEffects<z.ZodString, bigint, string>]>>>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ ledger: string;
8
+ coin: string;
9
+ slippageSettings?: Record<string, bigint> | undefined;
10
+ }, {
11
+ ledger: string;
12
+ coin: string;
13
+ slippageSettings?: Record<string, string | number | bigint> | undefined;
14
+ }>;
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ import { slippageSettingsRecord } from './primitives.js';
3
+ export const quoteCloseSchema = z.object({
4
+ ledger: z.string(),
5
+ coin: z.string(),
6
+ slippageSettings: slippageSettingsRecord(),
7
+ });
@@ -0,0 +1,14 @@
1
+ import type { AddressLike, BytesLike, TransactionRequest } from 'ethers';
2
+ type ExternalCallStruct = {
3
+ adapter: AddressLike;
4
+ callData: BytesLike;
5
+ };
6
+ export interface BuildCloseTxParams {
7
+ controller: string;
8
+ ledger: string;
9
+ coin: string;
10
+ extCalls: ExternalCallStruct[];
11
+ from: string;
12
+ }
13
+ export default function buildCloseTx(params: BuildCloseTxParams): TransactionRequest;
14
+ export {};
@@ -0,0 +1,15 @@
1
+ import { FundController__factory } from '../typechain/index.js';
2
+ export default function buildCloseTx(params) {
3
+ const data = FundController__factory.createInterface().encodeFunctionData('closeFund', [
4
+ {
5
+ ledger: params.ledger,
6
+ coin: params.coin,
7
+ extCalls: params.extCalls,
8
+ },
9
+ ]);
10
+ return {
11
+ to: params.controller,
12
+ from: params.from,
13
+ data,
14
+ };
15
+ }
@@ -1,5 +1,7 @@
1
1
  export { default as buildDepositTx } from './buildDepositTx';
2
2
  export type { BuildDepositTxParams } from './buildDepositTx';
3
+ export { default as buildCloseTx } from './buildCloseTx';
4
+ export type { BuildCloseTxParams } from './buildCloseTx';
3
5
  export { default as buildWithdrawTx } from './buildWithdrawTx';
4
6
  export type { BuildWithdrawTxParams } from './buildWithdrawTx';
5
7
  export { default as buildHarvestTx } from './buildHarvestTx';
@@ -1,4 +1,5 @@
1
1
  export { default as buildDepositTx } from './buildDepositTx.js';
2
+ export { default as buildCloseTx } from './buildCloseTx.js';
2
3
  export { default as buildWithdrawTx } from './buildWithdrawTx.js';
3
4
  export { default as buildHarvestTx } from './buildHarvestTx.js';
4
5
  export { default as buildTradeTx } from './buildTradeTx.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guru-fund/sdk",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Guru Protocol SDK — quote + transaction builders for on-chain managed funds.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -53,7 +53,7 @@
53
53
  "clean": "rm -rf dist .turbo tsconfig.build.tsbuildinfo",
54
54
  "typecheck": "tsc --noEmit",
55
55
  "test": "node --import tsx --test test/*.test.ts",
56
- "prepack": "pnpm run build"
56
+ "prepack": "npm run build"
57
57
  },
58
58
  "dependencies": {
59
59
  "ethers": "=6.15.0",