@guru-fund/sdk 0.2.6 → 0.2.8
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 +3 -3
- package/dist/GuruProtocol.d.ts +4 -0
- package/dist/GuruProtocol.js +6 -0
- package/dist/addresses.d.ts +2 -1
- package/dist/addresses.js +36 -2
- package/dist/index.d.ts +2 -1
- package/dist/quotes/quoteClose.d.ts +22 -0
- package/dist/quotes/quoteClose.js +57 -0
- package/dist/quotes/quoteHarvest.js +2 -2
- package/dist/router/constants.d.ts +1 -0
- package/dist/router/constants.js +1 -0
- package/dist/router/helpers.d.ts +1 -0
- package/dist/router/helpers.js +10 -5
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.js +39 -6
- package/dist/router/simulation.js +2 -8
- package/dist/router/v4PoolDiscovery.d.ts +1 -0
- package/dist/router/v4PoolDiscovery.js +57 -5
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/quoteClose.d.ts +14 -0
- package/dist/schemas/quoteClose.js +7 -0
- package/dist/txBuilders/buildCloseTx.d.ts +14 -0
- package/dist/txBuilders/buildCloseTx.js +15 -0
- package/dist/txBuilders/index.d.ts +2 -0
- package/dist/txBuilders/index.js +1 -0
- package/package.json +1 -1
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)
|
|
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
|
package/dist/GuruProtocol.d.ts
CHANGED
|
@@ -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;
|
package/dist/GuruProtocol.js
CHANGED
|
@@ -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;
|
package/dist/addresses.d.ts
CHANGED
|
@@ -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);
|
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,
|
|
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
|
}
|
|
@@ -16,3 +16,4 @@ export declare const V4_ZERO_ADDRESS = "0x00000000000000000000000000000000000000
|
|
|
16
16
|
export declare const DEXSCREENER_ENDPOINT = "https://api.dexscreener.com/token-pairs/v1";
|
|
17
17
|
export declare const V4_DISCOVERY_MIN_LIQUIDITY_USD = 10000;
|
|
18
18
|
export declare const V4_DISCOVERY_MAX_POOLS = 3;
|
|
19
|
+
export declare const V4_DISCOVERY_MAX_LP_FEE = 100000;
|
package/dist/router/constants.js
CHANGED
|
@@ -25,3 +25,4 @@ export const V4_ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
|
25
25
|
export const DEXSCREENER_ENDPOINT = 'https://api.dexscreener.com/token-pairs/v1';
|
|
26
26
|
export const V4_DISCOVERY_MIN_LIQUIDITY_USD = 10_000;
|
|
27
27
|
export const V4_DISCOVERY_MAX_POOLS = 3;
|
|
28
|
+
export const V4_DISCOVERY_MAX_LP_FEE = 100_000;
|
package/dist/router/helpers.d.ts
CHANGED
|
@@ -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;
|
package/dist/router/helpers.js
CHANGED
|
@@ -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.
|
|
11
|
-
|
|
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));
|
package/dist/router/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface RouterContext {
|
|
|
10
10
|
getPath: PathFetcher;
|
|
11
11
|
}
|
|
12
12
|
export declare function veloraThenV4Discovery(velora: () => Promise<Route>, v4Discovery: () => Promise<Route>): Promise<Route>;
|
|
13
|
+
export declare function bestOfV4AndFallback(v4Discovery: () => Promise<Route>, fallback: () => Promise<Route>): Promise<Route>;
|
|
13
14
|
export declare function tryWithVeloraFallback<T>(velora: () => Promise<T>, fallback: () => Promise<T>): Promise<T>;
|
|
14
15
|
export declare function getRouteIn(params: RouteSearchParams, ctx: RouterContext): Promise<Route>;
|
|
15
16
|
export declare function getRouteOut(params: RouteSearchParams, ctx: RouterContext): Promise<Route>;
|
package/dist/router/index.js
CHANGED
|
@@ -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 = [
|
|
@@ -42,6 +43,29 @@ export async function veloraThenV4Discovery(velora, v4Discovery) {
|
|
|
42
43
|
return await v4Discovery();
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
export async function bestOfV4AndFallback(v4Discovery, fallback) {
|
|
47
|
+
const [v4Result, fallbackResult] = await Promise.allSettled([
|
|
48
|
+
v4Discovery(),
|
|
49
|
+
fallback(),
|
|
50
|
+
]);
|
|
51
|
+
if (v4Result.status === 'rejected' &&
|
|
52
|
+
v4Result.reason instanceof Error &&
|
|
53
|
+
(v4Result.reason.message.includes('TOKEN_LOCKED_BY_V4_HOOK') ||
|
|
54
|
+
v4Result.reason.message.includes('UNISWAP_V4_HOOK_ROUTE_REVERTED'))) {
|
|
55
|
+
throw v4Result.reason;
|
|
56
|
+
}
|
|
57
|
+
if (v4Result.status === 'fulfilled' && fallbackResult.status === 'fulfilled') {
|
|
58
|
+
return BigInt(v4Result.value.data.amountToReceive) >=
|
|
59
|
+
BigInt(fallbackResult.value.data.amountToReceive)
|
|
60
|
+
? v4Result.value
|
|
61
|
+
: fallbackResult.value;
|
|
62
|
+
}
|
|
63
|
+
if (v4Result.status === 'fulfilled')
|
|
64
|
+
return v4Result.value;
|
|
65
|
+
if (fallbackResult.status === 'fulfilled')
|
|
66
|
+
return fallbackResult.value;
|
|
67
|
+
throw fallbackResult.reason;
|
|
68
|
+
}
|
|
45
69
|
export async function tryWithVeloraFallback(velora, fallback) {
|
|
46
70
|
try {
|
|
47
71
|
return await velora();
|
|
@@ -71,7 +95,12 @@ export async function getRouteIn(params, ctx) {
|
|
|
71
95
|
simulator: ctx.simulator,
|
|
72
96
|
getSwapFeePercentage: ctx.getSwapFeePercentage,
|
|
73
97
|
};
|
|
74
|
-
|
|
98
|
+
try {
|
|
99
|
+
return await getVeloraRoute(params, veloraCtx);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return bestOfV4AndFallback(() => getUniswapV4Route(params, v4Ctx), () => getFallbackRouteIn(params, fallbackCtx));
|
|
103
|
+
}
|
|
75
104
|
}
|
|
76
105
|
export async function getRouteOut(params, ctx) {
|
|
77
106
|
const veloraCtx = {
|
|
@@ -88,12 +117,16 @@ export async function getRouteOut(params, ctx) {
|
|
|
88
117
|
simulator: ctx.simulator,
|
|
89
118
|
getSwapFeePercentage: ctx.getSwapFeePercentage,
|
|
90
119
|
};
|
|
91
|
-
|
|
120
|
+
try {
|
|
121
|
+
return await getVeloraRoute(params, veloraCtx);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return bestOfV4AndFallback(() => getUniswapV4Route(params, v4Ctx), () => getFallbackRouteOut(params, fallbackCtx));
|
|
125
|
+
}
|
|
92
126
|
}
|
|
93
127
|
export async function getPriceUsd1e18(token, ctx) {
|
|
94
128
|
const addresses = getGuruProtocolAddresses(ctx.chainId);
|
|
95
|
-
if (compareAddresses(token,
|
|
96
|
-
compareAddresses(token, addresses.tokens.USDT)) {
|
|
129
|
+
if (stablecoinAddresses(addresses).some((stable) => compareAddresses(token, stable))) {
|
|
97
130
|
return 10n ** 18n;
|
|
98
131
|
}
|
|
99
132
|
const poolHelper = new PoolHelper({
|
|
@@ -101,7 +134,7 @@ export async function getPriceUsd1e18(token, ctx) {
|
|
|
101
134
|
provider: ctx.provider,
|
|
102
135
|
getSwapFeePercentage: ctx.getSwapFeePercentage,
|
|
103
136
|
});
|
|
104
|
-
const stable = addresses.tokens.USDC;
|
|
137
|
+
const stable = addresses.tokens.USDG ?? addresses.tokens.USDC;
|
|
105
138
|
const { decimals: stableDecimals } = await new Token(stable, ctx.provider)
|
|
106
139
|
.metadata()
|
|
107
140
|
.catch(() => ({ decimals: 6 }));
|
|
@@ -309,7 +342,7 @@ async function _priceViaV4Pools(token, oneToken, getWethUsd, ctx) {
|
|
|
309
342
|
}
|
|
310
343
|
return best > 0n ? best : null;
|
|
311
344
|
};
|
|
312
|
-
const inUsdc = await quoteVia(addresses.tokens.USDC);
|
|
345
|
+
const inUsdc = await quoteVia(addresses.tokens.USDG ?? addresses.tokens.USDC);
|
|
313
346
|
if (inUsdc !== null) {
|
|
314
347
|
return inUsdc * 10n ** 12n;
|
|
315
348
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
59
|
+
throw new Error('NO_EXECUTABLE_ROUTE_FOUND');
|
|
66
60
|
}
|
|
67
61
|
const [bestIndex, secondBestIndex] = successIndices;
|
|
68
62
|
const targetIndex = secondBestIndex ?? bestIndex;
|
|
@@ -8,6 +8,7 @@ export interface V4PoolKey {
|
|
|
8
8
|
tickSpacing: number;
|
|
9
9
|
hooks: string;
|
|
10
10
|
}
|
|
11
|
+
export declare function isSafeDiscoveredV4PoolKey(key: V4PoolKey): boolean;
|
|
11
12
|
export declare function computeV4PoolId(key: V4PoolKey): string;
|
|
12
13
|
interface DexscreenerPair {
|
|
13
14
|
dexId?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
-
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';
|
|
4
|
+
import { DEXSCREENER_ENDPOINT, V4_ZERO_ADDRESS, V4_DISCOVERY_MAX_POOLS, V4_DISCOVERY_MAX_LP_FEE, V4_DISCOVERY_MIN_LIQUIDITY_USD, VELORA_NEGATIVE_CACHE_TTL, VELORA_PATH_CACHE_TTL, } from './constants.js';
|
|
5
5
|
const V4_CHAIN_CONFIG = {
|
|
6
6
|
1: {
|
|
7
7
|
dexscreenerSlug: 'ethereum',
|
|
@@ -13,8 +13,16 @@ 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';
|
|
23
|
+
export function isSafeDiscoveredV4PoolKey(key) {
|
|
24
|
+
return key.fee >= 0 && key.fee <= V4_DISCOVERY_MAX_LP_FEE;
|
|
25
|
+
}
|
|
18
26
|
export function computeV4PoolId(key) {
|
|
19
27
|
return keccak256(AbiCoder.defaultAbiCoder().encode(['address', 'address', 'uint24', 'int24', 'address'], [key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks]));
|
|
20
28
|
}
|
|
@@ -73,6 +81,19 @@ async function resolvePoolKey(chainId, poolId, provider) {
|
|
|
73
81
|
const log = logs[0];
|
|
74
82
|
if (!log)
|
|
75
83
|
return null;
|
|
84
|
+
const key = poolKeyFromInitializeLog(log);
|
|
85
|
+
if (!key)
|
|
86
|
+
return null;
|
|
87
|
+
if (computeV4PoolId(key).toLowerCase() !== poolId.toLowerCase()) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
poolKeyCache.set(cacheKey, key);
|
|
91
|
+
return key;
|
|
92
|
+
}
|
|
93
|
+
function poolKeyFromInitializeLog(log) {
|
|
94
|
+
const poolId = log.topics[1];
|
|
95
|
+
if (!poolId || !log.topics[2] || !log.topics[3])
|
|
96
|
+
return null;
|
|
76
97
|
const [fee, tickSpacing, hooks] = AbiCoder.defaultAbiCoder().decode(['uint24', 'int24', 'address', 'uint160', 'int24'], log.data);
|
|
77
98
|
const key = {
|
|
78
99
|
currency0: '0x' + log.topics[2].slice(26),
|
|
@@ -84,7 +105,6 @@ async function resolvePoolKey(chainId, poolId, provider) {
|
|
|
84
105
|
if (computeV4PoolId(key).toLowerCase() !== poolId.toLowerCase()) {
|
|
85
106
|
return null;
|
|
86
107
|
}
|
|
87
|
-
poolKeyCache.set(cacheKey, key);
|
|
88
108
|
return key;
|
|
89
109
|
}
|
|
90
110
|
const discoveryCache = new Map();
|
|
@@ -112,7 +132,13 @@ export async function discoverV4Paths(chainId, tokenIn, tokenOut, provider, endp
|
|
|
112
132
|
async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpoint, cachedPaths) {
|
|
113
133
|
const config = V4_CHAIN_CONFIG[chainId];
|
|
114
134
|
const { tokens } = getGuruProtocolAddresses(chainId);
|
|
115
|
-
const majors = [
|
|
135
|
+
const majors = [
|
|
136
|
+
tokens.WETH,
|
|
137
|
+
tokens.USDC,
|
|
138
|
+
tokens.USDT,
|
|
139
|
+
tokens.USDG,
|
|
140
|
+
V4_ZERO_ADDRESS,
|
|
141
|
+
].filter((token) => Boolean(token));
|
|
116
142
|
const isMajor = (token) => majors.some((major) => compareAddresses(major, token));
|
|
117
143
|
const queryOrder = isMajor(tokenIn) ? [tokenOut, tokenIn] : [tokenIn, tokenOut];
|
|
118
144
|
let poolIds = [];
|
|
@@ -127,7 +153,12 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
|
|
|
127
153
|
break;
|
|
128
154
|
}
|
|
129
155
|
const keys = await Promise.all(poolIds.map((poolId) => resolvePoolKey(chainId, poolId, provider)));
|
|
130
|
-
const
|
|
156
|
+
const resolvedKeys = keys.filter((key) => key !== null && isSafeDiscoveredV4PoolKey(key));
|
|
157
|
+
const onchainKeys = resolvedKeys.length > 0
|
|
158
|
+
? []
|
|
159
|
+
: await discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider);
|
|
160
|
+
const paths = resolvedKeys
|
|
161
|
+
.concat(onchainKeys)
|
|
131
162
|
.filter((key) => key !== null)
|
|
132
163
|
.map((key) => [
|
|
133
164
|
{
|
|
@@ -141,6 +172,27 @@ async function discoverDirectV4Paths(chainId, tokenIn, tokenOut, provider, endpo
|
|
|
141
172
|
]);
|
|
142
173
|
return paths;
|
|
143
174
|
}
|
|
175
|
+
async function discoverOnchainDirectV4PoolKeys(chainId, tokenIn, tokenOut, provider) {
|
|
176
|
+
const config = V4_CHAIN_CONFIG[chainId];
|
|
177
|
+
const [currency0, currency1] = tokenIn.toLowerCase() < tokenOut.toLowerCase()
|
|
178
|
+
? [tokenIn, tokenOut]
|
|
179
|
+
: [tokenOut, tokenIn];
|
|
180
|
+
const logs = await provider.getLogs({
|
|
181
|
+
address: config.poolManager,
|
|
182
|
+
topics: [
|
|
183
|
+
POOL_INITIALIZE_TOPIC,
|
|
184
|
+
null,
|
|
185
|
+
zeroPadValue(currency0, 32),
|
|
186
|
+
zeroPadValue(currency1, 32),
|
|
187
|
+
],
|
|
188
|
+
fromBlock: config.deployBlock,
|
|
189
|
+
toBlock: 'latest',
|
|
190
|
+
});
|
|
191
|
+
return logs
|
|
192
|
+
.map(poolKeyFromInitializeLog)
|
|
193
|
+
.filter((key) => key !== null && isSafeDiscoveredV4PoolKey(key))
|
|
194
|
+
.sort((a, b) => a.fee - b.fee);
|
|
195
|
+
}
|
|
144
196
|
export function _clearV4DiscoveryCache() {
|
|
145
197
|
discoveryCache.clear();
|
|
146
198
|
poolKeyCache.clear();
|
package/dist/schemas/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/schemas/index.js
CHANGED
|
@@ -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,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';
|
package/dist/txBuilders/index.js
CHANGED
|
@@ -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';
|