levr-sdk 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -13
- package/dist/esm/abis/StateView.js +149 -0
- package/dist/esm/abis/V3QuoterV2.js +26 -0
- package/dist/esm/abis/index.js +4 -0
- package/dist/esm/balance.js +30 -4
- package/dist/esm/balance.js.map +1 -1
- package/dist/esm/client/hook/use-balance.js +2 -1
- package/dist/esm/client/hook/use-balance.js.map +1 -1
- package/dist/esm/client/hook/use-fee-receivers.js +0 -1
- package/dist/esm/client/hook/use-fee-receivers.js.map +1 -1
- package/dist/esm/client/hook/use-governance.js +13 -32
- package/dist/esm/client/hook/use-governance.js.map +1 -1
- package/dist/esm/client/hook/use-project.js +7 -10
- package/dist/esm/client/hook/use-project.js.map +1 -1
- package/dist/esm/client/hook/use-proposals.js +2 -4
- package/dist/esm/client/hook/use-proposals.js.map +1 -1
- package/dist/esm/client/hook/use-stake.js +3 -23
- package/dist/esm/client/hook/use-stake.js.map +1 -1
- package/dist/esm/client/hook/use-swap.js +14 -6
- package/dist/esm/client/hook/use-swap.js.map +1 -1
- package/dist/esm/client/levr-provider.js +9 -2
- package/dist/esm/client/levr-provider.js.map +1 -1
- package/dist/esm/client/query-keys.js +1 -1
- package/dist/esm/client/query-keys.js.map +1 -1
- package/dist/esm/constants.js +41 -0
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/fee-receivers.js +5 -2
- package/dist/esm/fee-receivers.js.map +1 -1
- package/dist/esm/governance.js +20 -5
- package/dist/esm/governance.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pool-key.js +152 -0
- package/dist/esm/pool-key.js.map +1 -0
- package/dist/esm/project.js +48 -5
- package/dist/esm/project.js.map +1 -1
- package/dist/esm/projects.js +2 -30
- package/dist/esm/projects.js.map +1 -1
- package/dist/esm/quote/index.js +98 -0
- package/dist/esm/quote/index.js.map +1 -0
- package/dist/esm/quote/v3.js +62 -0
- package/dist/esm/quote/v3.js.map +1 -0
- package/dist/esm/quote/v4.js +228 -0
- package/dist/esm/quote/v4.js.map +1 -0
- package/dist/esm/stake.js +102 -40
- package/dist/esm/stake.js.map +1 -1
- package/dist/esm/usd-price.js +149 -0
- package/dist/esm/usd-price.js.map +1 -0
- package/dist/esm/util.js +45 -1
- package/dist/esm/util.js.map +1 -1
- package/dist/types/abis/StateView.d.ts +278 -0
- package/dist/types/abis/V3QuoterV2.d.ts +39 -0
- package/dist/types/abis/index.d.ts +2 -0
- package/dist/types/balance.d.ts +4 -6
- package/dist/types/balance.d.ts.map +1 -1
- package/dist/types/client/hook/index.d.ts +1 -1
- package/dist/types/client/hook/index.d.ts.map +1 -1
- package/dist/types/client/hook/use-balance.d.ts +3 -1
- package/dist/types/client/hook/use-balance.d.ts.map +1 -1
- package/dist/types/client/hook/use-fee-receivers.d.ts +1 -1
- package/dist/types/client/hook/use-fee-receivers.d.ts.map +1 -1
- package/dist/types/client/hook/use-governance.d.ts +20 -15
- package/dist/types/client/hook/use-governance.d.ts.map +1 -1
- package/dist/types/client/hook/use-project.d.ts +3 -1
- package/dist/types/client/hook/use-project.d.ts.map +1 -1
- package/dist/types/client/hook/use-proposals.d.ts +1 -1
- package/dist/types/client/hook/use-proposals.d.ts.map +1 -1
- package/dist/types/client/hook/use-stake.d.ts +37 -132
- package/dist/types/client/hook/use-stake.d.ts.map +1 -1
- package/dist/types/client/hook/use-swap.d.ts +2 -1
- package/dist/types/client/hook/use-swap.d.ts.map +1 -1
- package/dist/types/client/levr-provider.d.ts +31 -38
- package/dist/types/client/levr-provider.d.ts.map +1 -1
- package/dist/types/client/query-keys.d.ts +1 -1
- package/dist/types/client/query-keys.d.ts.map +1 -1
- package/dist/types/constants.d.ts +18 -0
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/fee-receivers.d.ts +1 -2
- package/dist/types/fee-receivers.d.ts.map +1 -1
- package/dist/types/governance.d.ts +8 -21
- package/dist/types/governance.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/pool-key.d.ts +121 -0
- package/dist/types/pool-key.d.ts.map +1 -0
- package/dist/types/project.d.ts +7 -12
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/projects.d.ts +1 -1
- package/dist/types/projects.d.ts.map +1 -1
- package/dist/types/quote/index.d.ts +97 -0
- package/dist/types/quote/index.d.ts.map +1 -0
- package/dist/types/quote/v3.d.ts +78 -0
- package/dist/types/quote/v3.d.ts.map +1 -0
- package/dist/types/quote/v4.d.ts +95 -0
- package/dist/types/quote/v4.d.ts.map +1 -0
- package/dist/types/stake.d.ts +38 -59
- package/dist/types/stake.d.ts.map +1 -1
- package/dist/types/types.d.ts +15 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/usd-price.d.ts +141 -0
- package/dist/types/usd-price.d.ts.map +1 -0
- package/dist/types/util.d.ts +17 -0
- package/dist/types/util.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/quote-v4.js +0 -169
- package/dist/esm/quote-v4.js.map +0 -1
- package/dist/types/quote-v4.d.ts +0 -54
- package/dist/types/quote-v4.d.ts.map +0 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Unified quote API for Uniswap V3 and V4
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This module provides quote functionality for both Uniswap V3 and V4 pools.
|
|
6
|
+
* Each version offers two methods:
|
|
7
|
+
* - `read`: Performs an async call to get the quote immediately
|
|
8
|
+
* - `bytecode`: Returns encoded call data for use in multicalls or custom execution
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // V3 Read (immediate result)
|
|
13
|
+
* const v3Quote = await quote.v3.read({
|
|
14
|
+
* publicClient,
|
|
15
|
+
* quoterAddress: '0x...',
|
|
16
|
+
* tokenIn: '0x123...',
|
|
17
|
+
* tokenOut: '0x456...',
|
|
18
|
+
* amountIn: parseUnits('1', 18),
|
|
19
|
+
* fee: 3000,
|
|
20
|
+
* })
|
|
21
|
+
* console.log(`Output: ${formatUnits(v3Quote.amountOut, 6)}`)
|
|
22
|
+
*
|
|
23
|
+
* // V3 Bytecode (for multicall)
|
|
24
|
+
* const v3Bytecode = quote.v3.bytecode({
|
|
25
|
+
* quoterAddress: '0x...',
|
|
26
|
+
* tokenIn: '0x123...',
|
|
27
|
+
* tokenOut: '0x456...',
|
|
28
|
+
* amountIn: parseUnits('1', 18),
|
|
29
|
+
* fee: 3000,
|
|
30
|
+
* })
|
|
31
|
+
* // Use v3Bytecode.address and v3Bytecode.data in multicall
|
|
32
|
+
*
|
|
33
|
+
* // V4 Read (immediate result with hook fees and price impact)
|
|
34
|
+
* const v4Quote = await quote.v4.read({
|
|
35
|
+
* publicClient,
|
|
36
|
+
* poolKey,
|
|
37
|
+
* zeroForOne: true,
|
|
38
|
+
* amountIn: parseEther('1'),
|
|
39
|
+
* pricing,
|
|
40
|
+
* tokenAddress: '0x...',
|
|
41
|
+
* })
|
|
42
|
+
* console.log(`Output: ${formatEther(v4Quote.amountOut)}`)
|
|
43
|
+
* console.log(`Price Impact: ${v4Quote.priceImpactBps}%`)
|
|
44
|
+
* console.log(`Hook Fees:`, v4Quote.hookFees)
|
|
45
|
+
*
|
|
46
|
+
* // V4 Bytecode (for multicall)
|
|
47
|
+
* const v4Bytecode = quote.v4.bytecode({
|
|
48
|
+
* publicClient, // Needed for chain ID
|
|
49
|
+
* poolKey,
|
|
50
|
+
* zeroForOne: true,
|
|
51
|
+
* amountIn: parseEther('1'),
|
|
52
|
+
* })
|
|
53
|
+
* // Use v4Bytecode.address and v4Bytecode.data in multicall
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
// Import implementations
|
|
57
|
+
import { quoteV3Read, quoteV3Bytecode } from "./v3.js";
|
|
58
|
+
import { quoteV4Read, quoteV4Bytecode } from "./v4.js";
|
|
59
|
+
/**
|
|
60
|
+
* @description Unified quote API for Uniswap V3 and V4
|
|
61
|
+
*/
|
|
62
|
+
export const quote = {
|
|
63
|
+
/**
|
|
64
|
+
* Uniswap V3 quote methods
|
|
65
|
+
*/
|
|
66
|
+
v3: {
|
|
67
|
+
/**
|
|
68
|
+
* Get a V3 quote by reading from the quoter contract
|
|
69
|
+
* @param params Quote parameters
|
|
70
|
+
* @returns Quote result with output amount
|
|
71
|
+
*/
|
|
72
|
+
read: quoteV3Read,
|
|
73
|
+
/**
|
|
74
|
+
* Get encoded bytecode for a V3 quote (for multicalls)
|
|
75
|
+
* @param params Quote parameters
|
|
76
|
+
* @returns Contract address and encoded call data
|
|
77
|
+
*/
|
|
78
|
+
bytecode: quoteV3Bytecode,
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Uniswap V4 quote methods
|
|
82
|
+
*/
|
|
83
|
+
v4: {
|
|
84
|
+
/**
|
|
85
|
+
* Get a V4 quote by reading from the quoter contract
|
|
86
|
+
* @param params Quote parameters
|
|
87
|
+
* @returns Quote result with output amount, gas estimate, price impact, and hook fees
|
|
88
|
+
*/
|
|
89
|
+
read: quoteV4Read,
|
|
90
|
+
/**
|
|
91
|
+
* Get encoded bytecode for a V4 quote (for multicalls)
|
|
92
|
+
* @param params Quote parameters
|
|
93
|
+
* @returns Contract address and encoded call data
|
|
94
|
+
*/
|
|
95
|
+
bytecode: quoteV4Bytecode,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/quote/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAeH,yBAAyB;AACzB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAEnD;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB;;OAEG;IACH,EAAE,EAAE;QACF;;;;WAIG;QACH,IAAI,EAAE,WAAW;QACjB;;;;WAIG;QACH,QAAQ,EAAE,eAAe;KAC1B;IACD;;OAEG;IACH,EAAE,EAAE;QACF;;;;WAIG;QACH,IAAI,EAAE,WAAW;QACjB;;;;WAIG;QACH,QAAQ,EAAE,eAAe;KAC1B;CACF,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { encodeFunctionData } from "viem";
|
|
2
|
+
import { V3QuoterV2 } from "../abis/index.js";
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// V3 Implementation
|
|
5
|
+
// ============================================================================
|
|
6
|
+
/**
|
|
7
|
+
* @description Quote a swap on Uniswap V3 by reading from the quoter contract
|
|
8
|
+
* @param params Parameters for V3 quote
|
|
9
|
+
* @returns Output amount and fee tier
|
|
10
|
+
*/
|
|
11
|
+
export const quoteV3Read = async (params) => {
|
|
12
|
+
if (!params.publicClient) {
|
|
13
|
+
throw new Error("publicClient is required for read method");
|
|
14
|
+
}
|
|
15
|
+
const { publicClient, quoterAddress, tokenIn, tokenOut, amountIn, fee, sqrtPriceLimitX96 = 0n, } = params;
|
|
16
|
+
const result = await publicClient.simulateContract({
|
|
17
|
+
address: quoterAddress,
|
|
18
|
+
abi: V3QuoterV2,
|
|
19
|
+
functionName: "quoteExactInputSingle",
|
|
20
|
+
args: [
|
|
21
|
+
{
|
|
22
|
+
tokenIn,
|
|
23
|
+
tokenOut,
|
|
24
|
+
amountIn,
|
|
25
|
+
fee,
|
|
26
|
+
sqrtPriceLimitX96,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
const [amountOut] = result.result;
|
|
31
|
+
return {
|
|
32
|
+
amountOut,
|
|
33
|
+
fee,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* @description Get bytecode for a V3 quote that can be used in multicalls
|
|
38
|
+
* @param params Parameters for V3 quote
|
|
39
|
+
* @returns Contract address, encoded call data, and ABI
|
|
40
|
+
*/
|
|
41
|
+
export const quoteV3Bytecode = (params) => {
|
|
42
|
+
const { quoterAddress, tokenIn, tokenOut, amountIn, fee, sqrtPriceLimitX96 = 0n } = params;
|
|
43
|
+
const data = encodeFunctionData({
|
|
44
|
+
abi: V3QuoterV2,
|
|
45
|
+
functionName: "quoteExactInputSingle",
|
|
46
|
+
args: [
|
|
47
|
+
{
|
|
48
|
+
tokenIn,
|
|
49
|
+
tokenOut,
|
|
50
|
+
amountIn,
|
|
51
|
+
fee,
|
|
52
|
+
sqrtPriceLimitX96,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
address: quoterAddress,
|
|
58
|
+
data,
|
|
59
|
+
abi: V3QuoterV2,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=v3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3.js","sourceRoot":"","sources":["../../../src/quote/v3.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,MAAM,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAwEpC,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,MAAqB,EAAkC,EAAE;IACzF,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;IAC7D,CAAC;IAED,MAAM,EACJ,YAAY,EACZ,aAAa,EACb,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,iBAAiB,GAAG,EAAE,GACvB,GAAG,MAAM,CAAA;IAEV,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,gBAAgB,CAAC;QACjD,OAAO,EAAE,aAAa;QACtB,GAAG,EAAE,UAAU;QACf,YAAY,EAAE,uBAAuB;QACrC,IAAI,EAAE;YACJ;gBACE,OAAO;gBACP,QAAQ;gBACR,QAAQ;gBACR,GAAG;gBACH,iBAAiB;aAClB;SACF;KACF,CAAC,CAAA;IAEF,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;IAEjC,OAAO;QACL,SAAS;QACT,GAAG;KACJ,CAAA;AACH,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAqB,EAA6B,EAAE;IAClF,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,GAAG,EAAE,EAAE,GAAG,MAAM,CAAA;IAE1F,MAAM,IAAI,GAAG,kBAAkB,CAAC;QAC9B,GAAG,EAAE,UAAU;QACf,YAAY,EAAE,uBAAuB;QACrC,IAAI,EAAE;YACJ;gBACE,OAAO;gBACP,QAAQ;gBACR,QAAQ;gBACR,GAAG;gBACH,iBAAiB;aAClB;SACF;KACF,CAAC,CAAA;IAEF,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,IAAI;QACJ,GAAG,EAAE,UAAU;KAChB,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { encodeAbiParameters, encodeFunctionData, formatUnits, keccak256 } from "viem";
|
|
2
|
+
import { IClankerHookDynamicFee, IClankerHookStaticFee, V4Quoter } from "../abis/index.js";
|
|
3
|
+
import { UNISWAP_V4_QUOTER } from "../constants.js";
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// V4 Helper Functions
|
|
6
|
+
// ============================================================================
|
|
7
|
+
/**
|
|
8
|
+
* @description Try to get static fees from a Clanker hook using multicall
|
|
9
|
+
*/
|
|
10
|
+
const tryGetStaticFees = async (publicClient, hookAddress, poolId) => {
|
|
11
|
+
try {
|
|
12
|
+
const results = await publicClient.multicall({
|
|
13
|
+
contracts: [
|
|
14
|
+
{
|
|
15
|
+
address: hookAddress,
|
|
16
|
+
abi: IClankerHookStaticFee,
|
|
17
|
+
functionName: "clankerFee",
|
|
18
|
+
args: [poolId],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
address: hookAddress,
|
|
22
|
+
abi: IClankerHookStaticFee,
|
|
23
|
+
functionName: "pairedFee",
|
|
24
|
+
args: [poolId],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
if (results[0].status === "success" && results[1].status === "success") {
|
|
29
|
+
return {
|
|
30
|
+
clankerFee: Number(results[0].result),
|
|
31
|
+
pairedFee: Number(results[1].result),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* @description Try to get dynamic fee configuration from a Clanker hook
|
|
42
|
+
*/
|
|
43
|
+
const tryGetDynamicFees = async (publicClient, hookAddress, poolId) => {
|
|
44
|
+
try {
|
|
45
|
+
const config = await publicClient.readContract({
|
|
46
|
+
address: hookAddress,
|
|
47
|
+
abi: IClankerHookDynamicFee,
|
|
48
|
+
functionName: "poolConfigVars",
|
|
49
|
+
args: [poolId],
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
baseFee: Number(config.baseFee),
|
|
53
|
+
maxLpFee: Number(config.maxLpFee),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* @description Get hook fee information from Clanker hooks
|
|
62
|
+
*/
|
|
63
|
+
const getHookFees = async (publicClient, poolKey) => {
|
|
64
|
+
// Generate pool ID for hook queries (keccak256 of abi.encode(PoolKey))
|
|
65
|
+
const poolId = keccak256(encodeAbiParameters([
|
|
66
|
+
{
|
|
67
|
+
type: "tuple",
|
|
68
|
+
components: [
|
|
69
|
+
{ name: "currency0", type: "address" },
|
|
70
|
+
{ name: "currency1", type: "address" },
|
|
71
|
+
{ name: "fee", type: "uint24" },
|
|
72
|
+
{ name: "tickSpacing", type: "int24" },
|
|
73
|
+
{ name: "hooks", type: "address" },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
], [poolKey]));
|
|
77
|
+
// Try static fees first
|
|
78
|
+
const staticFees = await tryGetStaticFees(publicClient, poolKey.hooks, poolId);
|
|
79
|
+
if (staticFees) {
|
|
80
|
+
return {
|
|
81
|
+
type: "static",
|
|
82
|
+
clankerFee: staticFees.clankerFee,
|
|
83
|
+
pairedFee: staticFees.pairedFee,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Try dynamic fees
|
|
87
|
+
const dynamicFees = await tryGetDynamicFees(publicClient, poolKey.hooks, poolId);
|
|
88
|
+
if (dynamicFees) {
|
|
89
|
+
return {
|
|
90
|
+
type: "dynamic",
|
|
91
|
+
baseFee: dynamicFees.baseFee,
|
|
92
|
+
maxLpFee: dynamicFees.maxLpFee,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* @description Calculate price impact using USD pricing
|
|
99
|
+
* Compares the execution price to the market spot price
|
|
100
|
+
*/
|
|
101
|
+
const calculatePriceImpact = (amountIn, amountOut, currency0, currency1, currency0Decimals, currency1Decimals, tokenAddress, pricing, zeroForOne) => {
|
|
102
|
+
try {
|
|
103
|
+
// Determine which currency is the token and which is WETH
|
|
104
|
+
const currency0IsToken = currency0.toLowerCase() === tokenAddress.toLowerCase();
|
|
105
|
+
// Format amounts to decimal strings
|
|
106
|
+
const amountInFormatted = parseFloat(formatUnits(amountIn, zeroForOne ? currency0Decimals : currency1Decimals));
|
|
107
|
+
const amountOutFormatted = parseFloat(formatUnits(amountOut, zeroForOne ? currency1Decimals : currency0Decimals));
|
|
108
|
+
if (amountInFormatted === 0 || amountOutFormatted === 0)
|
|
109
|
+
return undefined;
|
|
110
|
+
// Get market spot prices
|
|
111
|
+
const wethPrice = parseFloat(pricing.wethUsd);
|
|
112
|
+
const tokenPrice = parseFloat(pricing.tokenUsd);
|
|
113
|
+
if (tokenPrice === 0 || wethPrice === 0)
|
|
114
|
+
return undefined;
|
|
115
|
+
// Calculate execution rate (how many output per input)
|
|
116
|
+
const executionRate = amountOutFormatted / amountInFormatted;
|
|
117
|
+
// Determine swap direction and calculate market rate
|
|
118
|
+
const inputIsToken = zeroForOne ? currency0IsToken : !currency0IsToken;
|
|
119
|
+
// Calculate market spot rate (output per input at market prices)
|
|
120
|
+
const marketRate = inputIsToken
|
|
121
|
+
? tokenPrice / wethPrice // Token → WETH
|
|
122
|
+
: wethPrice / tokenPrice; // WETH → Token
|
|
123
|
+
// Getting better or equal rate than market - minimal impact
|
|
124
|
+
if (executionRate >= marketRate) {
|
|
125
|
+
return 0.1;
|
|
126
|
+
}
|
|
127
|
+
// Getting worse rate than market - calculate actual slippage
|
|
128
|
+
const impact = (1 - executionRate / marketRate) * 100;
|
|
129
|
+
return impact;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.warn("Price impact calculation failed:", error);
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// V4 Implementation
|
|
138
|
+
// ============================================================================
|
|
139
|
+
/**
|
|
140
|
+
* @description Quote a swap on Uniswap V4 by reading from the quoter contract
|
|
141
|
+
* @param params Quote parameters including pool key and amount
|
|
142
|
+
* @returns Quote result with output amount, gas estimate, price impact, and hook fees
|
|
143
|
+
*/
|
|
144
|
+
export const quoteV4Read = async (params) => {
|
|
145
|
+
if (!params.publicClient) {
|
|
146
|
+
throw new Error("publicClient is required for read method");
|
|
147
|
+
}
|
|
148
|
+
const { publicClient, poolKey, zeroForOne, amountIn, hookData = "0x", pricing, currency0Decimals = 18, currency1Decimals = 18, tokenAddress, } = params;
|
|
149
|
+
const chainId = publicClient.chain?.id;
|
|
150
|
+
if (!chainId)
|
|
151
|
+
throw new Error("Chain ID not found on public client");
|
|
152
|
+
const quoterAddress = UNISWAP_V4_QUOTER(chainId);
|
|
153
|
+
if (!quoterAddress)
|
|
154
|
+
throw new Error(`V4 Quoter address not found for chain ID ${chainId}`);
|
|
155
|
+
// Fetch hook fees and quote in parallel
|
|
156
|
+
const [{ result }, hookFees] = await Promise.all([
|
|
157
|
+
publicClient.simulateContract({
|
|
158
|
+
address: quoterAddress,
|
|
159
|
+
abi: V4Quoter,
|
|
160
|
+
functionName: "quoteExactInputSingle",
|
|
161
|
+
args: [
|
|
162
|
+
{
|
|
163
|
+
poolKey: {
|
|
164
|
+
currency0: poolKey.currency0,
|
|
165
|
+
currency1: poolKey.currency1,
|
|
166
|
+
fee: poolKey.fee,
|
|
167
|
+
tickSpacing: poolKey.tickSpacing,
|
|
168
|
+
hooks: poolKey.hooks,
|
|
169
|
+
},
|
|
170
|
+
zeroForOne,
|
|
171
|
+
exactAmount: amountIn,
|
|
172
|
+
hookData,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
}),
|
|
176
|
+
getHookFees(publicClient, poolKey),
|
|
177
|
+
]);
|
|
178
|
+
// The quoter returns [amountOut: bigint, gasEstimate: bigint]
|
|
179
|
+
const [amountOut, gasEstimate] = result;
|
|
180
|
+
// Calculate price impact if pricing and token address are available
|
|
181
|
+
const priceImpactBps = pricing && tokenAddress
|
|
182
|
+
? calculatePriceImpact(amountIn, amountOut, poolKey.currency0, poolKey.currency1, currency0Decimals, currency1Decimals, tokenAddress, pricing, zeroForOne)
|
|
183
|
+
: undefined;
|
|
184
|
+
return {
|
|
185
|
+
amountOut,
|
|
186
|
+
gasEstimate,
|
|
187
|
+
priceImpactBps,
|
|
188
|
+
hookFees,
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* @description Get bytecode for a V4 quote that can be used in multicalls
|
|
193
|
+
* @param params Quote parameters including pool key and amount
|
|
194
|
+
* @returns Contract address, encoded call data, and ABI
|
|
195
|
+
*/
|
|
196
|
+
export const quoteV4Bytecode = (params) => {
|
|
197
|
+
const { poolKey, zeroForOne, amountIn, hookData = "0x", publicClient } = params;
|
|
198
|
+
const chainId = publicClient?.chain?.id;
|
|
199
|
+
if (!chainId)
|
|
200
|
+
throw new Error("Chain ID required for bytecode generation");
|
|
201
|
+
const quoterAddress = UNISWAP_V4_QUOTER(chainId);
|
|
202
|
+
if (!quoterAddress)
|
|
203
|
+
throw new Error(`V4 Quoter address not found for chain ID ${chainId}`);
|
|
204
|
+
const data = encodeFunctionData({
|
|
205
|
+
abi: V4Quoter,
|
|
206
|
+
functionName: "quoteExactInputSingle",
|
|
207
|
+
args: [
|
|
208
|
+
{
|
|
209
|
+
poolKey: {
|
|
210
|
+
currency0: poolKey.currency0,
|
|
211
|
+
currency1: poolKey.currency1,
|
|
212
|
+
fee: poolKey.fee,
|
|
213
|
+
tickSpacing: poolKey.tickSpacing,
|
|
214
|
+
hooks: poolKey.hooks,
|
|
215
|
+
},
|
|
216
|
+
zeroForOne,
|
|
217
|
+
exactAmount: amountIn,
|
|
218
|
+
hookData,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
address: quoterAddress,
|
|
224
|
+
data,
|
|
225
|
+
abi: V4Quoter,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
//# sourceMappingURL=v4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v4.js","sourceRoot":"","sources":["../../../src/quote/v4.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/stake.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem";
|
|
2
2
|
import { LevrForwarder_v1, LevrStaking_v1 } from "./abis/index.js";
|
|
3
3
|
import { WETH } from "./constants.js";
|
|
4
|
-
import { quoteV4 } from "./quote-v4.js";
|
|
5
4
|
export class Stake {
|
|
6
5
|
wallet;
|
|
7
6
|
publicClient;
|
|
@@ -11,6 +10,7 @@ export class Stake {
|
|
|
11
10
|
chainId;
|
|
12
11
|
userAddress;
|
|
13
12
|
trustedForwarder;
|
|
13
|
+
pricing;
|
|
14
14
|
constructor(config) {
|
|
15
15
|
if (Object.values(config).some((value) => !value))
|
|
16
16
|
throw new Error("Invalid config");
|
|
@@ -22,6 +22,7 @@ export class Stake {
|
|
|
22
22
|
this.chainId = config.publicClient.chain?.id ?? 1; // Get chainId from publicClient
|
|
23
23
|
this.userAddress = config.wallet.account.address;
|
|
24
24
|
this.trustedForwarder = config.trustedForwarder;
|
|
25
|
+
this.pricing = config.pricing;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Approve ERC20 tokens for spending by the staking contract
|
|
@@ -159,14 +160,21 @@ export class Stake {
|
|
|
159
160
|
],
|
|
160
161
|
});
|
|
161
162
|
const [totalStaked, escrowBalance, windowSeconds, streamStart, streamEnd, rewardRate] = results.map((r) => r.result);
|
|
163
|
+
const totalStakedFormatted = formatUnits(totalStaked, this.tokenDecimals);
|
|
164
|
+
const escrowBalanceFormatted = formatUnits(escrowBalance, this.tokenDecimals);
|
|
165
|
+
const rewardRateFormatted = formatUnits(rewardRate, this.tokenDecimals);
|
|
166
|
+
// Calculate USD values if pricing is available
|
|
167
|
+
const tokenPrice = this.pricing ? parseFloat(this.pricing.tokenUsd) : null;
|
|
162
168
|
return {
|
|
163
169
|
totalStaked: {
|
|
164
170
|
raw: totalStaked,
|
|
165
|
-
formatted:
|
|
171
|
+
formatted: totalStakedFormatted,
|
|
172
|
+
usd: tokenPrice ? (parseFloat(totalStakedFormatted) * tokenPrice).toString() : undefined,
|
|
166
173
|
},
|
|
167
174
|
escrowBalance: {
|
|
168
175
|
raw: escrowBalance,
|
|
169
|
-
formatted:
|
|
176
|
+
formatted: escrowBalanceFormatted,
|
|
177
|
+
usd: tokenPrice ? (parseFloat(escrowBalanceFormatted) * tokenPrice).toString() : undefined,
|
|
170
178
|
},
|
|
171
179
|
streamParams: {
|
|
172
180
|
windowSeconds: windowSeconds,
|
|
@@ -176,7 +184,8 @@ export class Stake {
|
|
|
176
184
|
},
|
|
177
185
|
rewardRatePerSecond: {
|
|
178
186
|
raw: rewardRate,
|
|
179
|
-
formatted:
|
|
187
|
+
formatted: rewardRateFormatted,
|
|
188
|
+
usd: tokenPrice ? (parseFloat(rewardRateFormatted) * tokenPrice).toString() : undefined,
|
|
180
189
|
},
|
|
181
190
|
};
|
|
182
191
|
}
|
|
@@ -202,10 +211,13 @@ export class Stake {
|
|
|
202
211
|
});
|
|
203
212
|
const stakedBalance = results[0].result;
|
|
204
213
|
const aprBps = results[1].result;
|
|
214
|
+
const stakedBalanceFormatted = formatUnits(stakedBalance, this.tokenDecimals);
|
|
215
|
+
const tokenPrice = this.pricing ? parseFloat(this.pricing.tokenUsd) : null;
|
|
205
216
|
return {
|
|
206
217
|
stakedBalance: {
|
|
207
218
|
raw: stakedBalance,
|
|
208
|
-
formatted:
|
|
219
|
+
formatted: stakedBalanceFormatted,
|
|
220
|
+
usd: tokenPrice ? (parseFloat(stakedBalanceFormatted) * tokenPrice).toString() : undefined,
|
|
209
221
|
},
|
|
210
222
|
aprBps: {
|
|
211
223
|
raw: aprBps,
|
|
@@ -225,14 +237,36 @@ export class Stake {
|
|
|
225
237
|
functionName: "outstandingRewards",
|
|
226
238
|
args: [token],
|
|
227
239
|
});
|
|
240
|
+
const availableFormatted = formatUnits(result[0], decimals);
|
|
241
|
+
const pendingFormatted = formatUnits(result[1], decimals);
|
|
242
|
+
// Calculate USD values if pricing is available
|
|
243
|
+
let availableUsd;
|
|
244
|
+
let pendingUsd;
|
|
245
|
+
if (this.pricing) {
|
|
246
|
+
const isTokenReward = token === this.tokenAddress;
|
|
247
|
+
const wethAddress = WETH(this.chainId)?.address;
|
|
248
|
+
const isWethReward = wethAddress && token.toLowerCase() === wethAddress.toLowerCase();
|
|
249
|
+
if (isTokenReward) {
|
|
250
|
+
const price = parseFloat(this.pricing.tokenUsd);
|
|
251
|
+
availableUsd = (parseFloat(availableFormatted) * price).toString();
|
|
252
|
+
pendingUsd = (parseFloat(pendingFormatted) * price).toString();
|
|
253
|
+
}
|
|
254
|
+
else if (isWethReward) {
|
|
255
|
+
const price = parseFloat(this.pricing.wethUsd);
|
|
256
|
+
availableUsd = (parseFloat(availableFormatted) * price).toString();
|
|
257
|
+
pendingUsd = (parseFloat(pendingFormatted) * price).toString();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
228
260
|
return {
|
|
229
261
|
available: {
|
|
230
262
|
raw: result[0],
|
|
231
|
-
formatted:
|
|
263
|
+
formatted: availableFormatted,
|
|
264
|
+
usd: availableUsd,
|
|
232
265
|
},
|
|
233
266
|
pending: {
|
|
234
267
|
raw: result[1],
|
|
235
|
-
formatted:
|
|
268
|
+
formatted: pendingFormatted,
|
|
269
|
+
usd: pendingUsd,
|
|
236
270
|
},
|
|
237
271
|
};
|
|
238
272
|
}
|
|
@@ -248,10 +282,27 @@ export class Stake {
|
|
|
248
282
|
functionName: "claimableRewards",
|
|
249
283
|
args: [this.userAddress, token],
|
|
250
284
|
});
|
|
285
|
+
const formatted = formatUnits(result, decimals);
|
|
286
|
+
// Calculate USD value if pricing is available
|
|
287
|
+
let usd;
|
|
288
|
+
if (this.pricing) {
|
|
289
|
+
const isTokenReward = token === this.tokenAddress;
|
|
290
|
+
const wethAddress = WETH(this.chainId)?.address;
|
|
291
|
+
const isWethReward = wethAddress && token.toLowerCase() === wethAddress.toLowerCase();
|
|
292
|
+
if (isTokenReward) {
|
|
293
|
+
const price = parseFloat(this.pricing.tokenUsd);
|
|
294
|
+
usd = (parseFloat(formatted) * price).toString();
|
|
295
|
+
}
|
|
296
|
+
else if (isWethReward) {
|
|
297
|
+
const price = parseFloat(this.pricing.wethUsd);
|
|
298
|
+
usd = (parseFloat(formatted) * price).toString();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
251
301
|
return {
|
|
252
302
|
claimable: {
|
|
253
303
|
raw: result,
|
|
254
|
-
formatted
|
|
304
|
+
formatted,
|
|
305
|
+
usd,
|
|
255
306
|
},
|
|
256
307
|
};
|
|
257
308
|
}
|
|
@@ -267,9 +318,25 @@ export class Stake {
|
|
|
267
318
|
functionName: "rewardRatePerSecond",
|
|
268
319
|
args: [token],
|
|
269
320
|
});
|
|
321
|
+
const formatted = formatUnits(result, decimals);
|
|
322
|
+
// Calculate USD value if pricing is available
|
|
323
|
+
let usd;
|
|
324
|
+
if (this.pricing) {
|
|
325
|
+
const isTokenReward = token.toLowerCase() === this.tokenAddress.toLowerCase();
|
|
326
|
+
const isWethReward = !isTokenReward;
|
|
327
|
+
if (isTokenReward) {
|
|
328
|
+
const price = parseFloat(this.pricing.tokenUsd);
|
|
329
|
+
usd = (parseFloat(formatted) * price).toString();
|
|
330
|
+
}
|
|
331
|
+
else if (isWethReward) {
|
|
332
|
+
const price = parseFloat(this.pricing.wethUsd);
|
|
333
|
+
usd = (parseFloat(formatted) * price).toString();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
270
336
|
return {
|
|
271
337
|
raw: result,
|
|
272
|
-
formatted
|
|
338
|
+
formatted,
|
|
339
|
+
usd,
|
|
273
340
|
};
|
|
274
341
|
}
|
|
275
342
|
/**
|
|
@@ -321,17 +388,21 @@ export class Stake {
|
|
|
321
388
|
return receipt;
|
|
322
389
|
}
|
|
323
390
|
/**
|
|
324
|
-
* Calculate WETH APR using
|
|
325
|
-
*
|
|
391
|
+
* Calculate WETH APR using USD pricing data
|
|
392
|
+
* No on-chain quotes needed - uses existing pricing data
|
|
393
|
+
* Formula: (wethRewardRatePerSecond * secondsPerYear * wethPriceInTokens / totalStaked) * 10000
|
|
326
394
|
*
|
|
327
|
-
* @param poolKey - The Uniswap V4 pool key for price discovery
|
|
328
395
|
* @returns WETH APR in basis points and percentage
|
|
329
396
|
*/
|
|
330
|
-
async calculateWethApr(
|
|
397
|
+
async calculateWethApr() {
|
|
331
398
|
const wethAddress = WETH(this.chainId)?.address;
|
|
332
399
|
if (!wethAddress) {
|
|
333
400
|
throw new Error("WETH address not found for this chain");
|
|
334
401
|
}
|
|
402
|
+
// Require pricing data to calculate APR
|
|
403
|
+
if (!this.pricing) {
|
|
404
|
+
return { raw: 0n, percentage: 0 };
|
|
405
|
+
}
|
|
335
406
|
// Get pool data and WETH reward rate
|
|
336
407
|
const [poolData, wethRewardRate] = await Promise.all([
|
|
337
408
|
this.getPoolData(),
|
|
@@ -342,39 +413,30 @@ export class Stake {
|
|
|
342
413
|
if (totalStaked === 0n || wethRewardRate.raw === 0n) {
|
|
343
414
|
return { raw: 0n, percentage: 0 };
|
|
344
415
|
}
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
const
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
const oneWeth = parseUnits("1", 18);
|
|
351
|
-
let wethPriceInUnderlying;
|
|
352
|
-
try {
|
|
353
|
-
const quote = await quoteV4({
|
|
354
|
-
publicClient: this.publicClient,
|
|
355
|
-
chainId: this.chainId,
|
|
356
|
-
poolKey,
|
|
357
|
-
zeroForOne,
|
|
358
|
-
amountIn: oneWeth,
|
|
359
|
-
});
|
|
360
|
-
// amountOut is how many underlying tokens we get for 1 WETH
|
|
361
|
-
wethPriceInUnderlying = quote.amountOut;
|
|
362
|
-
}
|
|
363
|
-
catch (error) {
|
|
364
|
-
// If quote fails, return 0 APR
|
|
365
|
-
console.error("Failed to quote WETH price:", error);
|
|
416
|
+
// Calculate WETH price in underlying tokens using USD pricing
|
|
417
|
+
// wethPriceInTokens = wethUsd / tokenUsd
|
|
418
|
+
const wethUsd = parseFloat(this.pricing.wethUsd);
|
|
419
|
+
const tokenUsd = parseFloat(this.pricing.tokenUsd);
|
|
420
|
+
if (tokenUsd === 0) {
|
|
366
421
|
return { raw: 0n, percentage: 0 };
|
|
367
422
|
}
|
|
423
|
+
const wethPriceInTokens = wethUsd / tokenUsd;
|
|
368
424
|
// Calculate annual WETH rewards (rewards per second * seconds per year)
|
|
369
425
|
const secondsPerYear = BigInt(365 * 24 * 60 * 60);
|
|
370
426
|
const annualWethRewards = wethRewardRate.raw * secondsPerYear;
|
|
371
|
-
// Convert WETH rewards to underlying token equivalent
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
const
|
|
427
|
+
// Convert WETH rewards to underlying token equivalent using bigint math
|
|
428
|
+
// annualWethRewards is in WETH wei (18 decimals)
|
|
429
|
+
// wethPriceInTokens is tokens per WETH (e.g., 3.75 billion for $3751 WETH / $0.000001 token)
|
|
430
|
+
// Scale the price ratio to bigint with high precision (use 1e18 for precision)
|
|
431
|
+
const priceScaleFactor = BigInt(1000000000000000000);
|
|
432
|
+
const wethPriceScaled = BigInt(Math.floor(wethPriceInTokens * Number(priceScaleFactor)));
|
|
433
|
+
// Formula: (annualWethRewards_wei * wethPrice_tokens/WETH) / 1e18
|
|
434
|
+
// Since both are already in wei and we have token decimals:
|
|
435
|
+
// Result = (annualWethRewards * wethPriceScaled) / priceScaleFactor
|
|
436
|
+
// This gives us token wei (same decimals as token)
|
|
437
|
+
const annualRewardsInUnderlying = (annualWethRewards * wethPriceScaled) / priceScaleFactor;
|
|
376
438
|
// Calculate APR: (annualRewardsInUnderlying / totalStaked) * 10000
|
|
377
|
-
const aprBps = (annualRewardsInUnderlying * 10000n) / totalStaked;
|
|
439
|
+
const aprBps = totalStaked > 0n ? (annualRewardsInUnderlying * 10000n) / totalStaked : 0n;
|
|
378
440
|
return {
|
|
379
441
|
raw: aprBps,
|
|
380
442
|
percentage: Number(aprBps) / 100, // Convert bps to percentage
|