levr-sdk 0.0.1

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.
Files changed (179) hide show
  1. package/LICENSE.md +201 -0
  2. package/README.md +711 -0
  3. package/dist/esm/abis/IClankerAirdrop.js +443 -0
  4. package/dist/esm/abis/IClankerHookDynamicFee.js +198 -0
  5. package/dist/esm/abis/IClankerHookStaticFee.js +75 -0
  6. package/dist/esm/abis/IClankerLPLocker.js +402 -0
  7. package/dist/esm/abis/IClankerLpLockerMultiple.js +609 -0
  8. package/dist/esm/abis/IClankerToken.js +421 -0
  9. package/dist/esm/abis/LevrFactory_v1.js +608 -0
  10. package/dist/esm/abis/LevrForwarder_v1.js +622 -0
  11. package/dist/esm/abis/LevrGovernor_v1.js +726 -0
  12. package/dist/esm/abis/LevrStakedToken_v1.js +441 -0
  13. package/dist/esm/abis/LevrStaking_v1.js +607 -0
  14. package/dist/esm/abis/LevrTreasury_v1.js +219 -0
  15. package/dist/esm/abis/Permit2.js +29 -0
  16. package/dist/esm/abis/V4Quoter.js +466 -0
  17. package/dist/esm/abis/WETH9.js +153 -0
  18. package/dist/esm/abis/index.js +23 -0
  19. package/dist/esm/balance.js +72 -0
  20. package/dist/esm/balance.js.map +1 -0
  21. package/dist/esm/build-calldatas-v4.js +98 -0
  22. package/dist/esm/build-calldatas-v4.js.map +1 -0
  23. package/dist/esm/build-clanker-v4.js +128 -0
  24. package/dist/esm/build-clanker-v4.js.map +1 -0
  25. package/dist/esm/client/hook/index.js +36 -0
  26. package/dist/esm/client/hook/index.js.map +1 -0
  27. package/dist/esm/client/hook/use-balance.js +56 -0
  28. package/dist/esm/client/hook/use-balance.js.map +1 -0
  29. package/dist/esm/client/hook/use-clanker.js +57 -0
  30. package/dist/esm/client/hook/use-clanker.js.map +1 -0
  31. package/dist/esm/client/hook/use-deploy.js +16 -0
  32. package/dist/esm/client/hook/use-deploy.js.map +1 -0
  33. package/dist/esm/client/hook/use-fee-receivers.js +54 -0
  34. package/dist/esm/client/hook/use-fee-receivers.js.map +1 -0
  35. package/dist/esm/client/hook/use-governance.js +389 -0
  36. package/dist/esm/client/hook/use-governance.js.map +1 -0
  37. package/dist/esm/client/hook/use-prepare.js +47 -0
  38. package/dist/esm/client/hook/use-prepare.js.map +1 -0
  39. package/dist/esm/client/hook/use-project.js +29 -0
  40. package/dist/esm/client/hook/use-project.js.map +1 -0
  41. package/dist/esm/client/hook/use-projects.js +22 -0
  42. package/dist/esm/client/hook/use-projects.js.map +1 -0
  43. package/dist/esm/client/hook/use-proposals.js +31 -0
  44. package/dist/esm/client/hook/use-proposals.js.map +1 -0
  45. package/dist/esm/client/hook/use-register.js +101 -0
  46. package/dist/esm/client/hook/use-register.js.map +1 -0
  47. package/dist/esm/client/hook/use-stake.js +327 -0
  48. package/dist/esm/client/hook/use-stake.js.map +1 -0
  49. package/dist/esm/client/hook/use-swap.js +110 -0
  50. package/dist/esm/client/hook/use-swap.js.map +1 -0
  51. package/dist/esm/client/index.js +4 -0
  52. package/dist/esm/client/index.js.map +1 -0
  53. package/dist/esm/client/levr-provider.js +201 -0
  54. package/dist/esm/client/levr-provider.js.map +1 -0
  55. package/dist/esm/client/query-keys.js +61 -0
  56. package/dist/esm/client/query-keys.js.map +1 -0
  57. package/dist/esm/constants.js +182 -0
  58. package/dist/esm/constants.js.map +1 -0
  59. package/dist/esm/deploy-v4.js +46 -0
  60. package/dist/esm/deploy-v4.js.map +1 -0
  61. package/dist/esm/fee-receivers.js +52 -0
  62. package/dist/esm/fee-receivers.js.map +1 -0
  63. package/dist/esm/governance.js +555 -0
  64. package/dist/esm/governance.js.map +1 -0
  65. package/dist/esm/index.js +18 -0
  66. package/dist/esm/index.js.map +1 -0
  67. package/dist/esm/project.js +146 -0
  68. package/dist/esm/project.js.map +1 -0
  69. package/dist/esm/projects.js +212 -0
  70. package/dist/esm/projects.js.map +1 -0
  71. package/dist/esm/proposals.js +98 -0
  72. package/dist/esm/proposals.js.map +1 -0
  73. package/dist/esm/quote-v4.js +169 -0
  74. package/dist/esm/quote-v4.js.map +1 -0
  75. package/dist/esm/schema/base.schema.js +11 -0
  76. package/dist/esm/schema/base.schema.js.map +1 -0
  77. package/dist/esm/schema/clanker.schema.js +191 -0
  78. package/dist/esm/schema/clanker.schema.js.map +1 -0
  79. package/dist/esm/schema/index.js +4 -0
  80. package/dist/esm/schema/index.js.map +1 -0
  81. package/dist/esm/schema/levr.schema.js +114 -0
  82. package/dist/esm/schema/levr.schema.js.map +1 -0
  83. package/dist/esm/stake.js +384 -0
  84. package/dist/esm/stake.js.map +1 -0
  85. package/dist/esm/swap-v4.js +281 -0
  86. package/dist/esm/swap-v4.js.map +1 -0
  87. package/dist/esm/types.js +2 -0
  88. package/dist/esm/types.js.map +1 -0
  89. package/dist/esm/util.js +19 -0
  90. package/dist/esm/util.js.map +1 -0
  91. package/dist/types/abis/IClankerAirdrop.d.ts +348 -0
  92. package/dist/types/abis/IClankerHookDynamicFee.d.ts +156 -0
  93. package/dist/types/abis/IClankerHookStaticFee.d.ts +58 -0
  94. package/dist/types/abis/IClankerLPLocker.d.ts +316 -0
  95. package/dist/types/abis/IClankerLpLockerMultiple.d.ts +481 -0
  96. package/dist/types/abis/IClankerToken.d.ts +326 -0
  97. package/dist/types/abis/LevrFactory_v1.d.ts +471 -0
  98. package/dist/types/abis/LevrForwarder_v1.d.ts +478 -0
  99. package/dist/types/abis/LevrGovernor_v1.d.ts +562 -0
  100. package/dist/types/abis/LevrStakedToken_v1.d.ts +338 -0
  101. package/dist/types/abis/LevrStaking_v1.d.ts +468 -0
  102. package/dist/types/abis/LevrTreasury_v1.d.ts +169 -0
  103. package/dist/types/abis/Permit2.d.ts +54 -0
  104. package/dist/types/abis/V4Quoter.d.ts +496 -0
  105. package/dist/types/abis/WETH9.d.ts +244 -0
  106. package/dist/types/abis/index.d.ts +15 -0
  107. package/dist/types/balance.d.ts +24 -0
  108. package/dist/types/balance.d.ts.map +1 -0
  109. package/dist/types/build-calldatas-v4.d.ts +20 -0
  110. package/dist/types/build-calldatas-v4.d.ts.map +1 -0
  111. package/dist/types/build-clanker-v4.d.ts +12 -0
  112. package/dist/types/build-clanker-v4.d.ts.map +1 -0
  113. package/dist/types/client/hook/index.d.ts +32 -0
  114. package/dist/types/client/hook/index.d.ts.map +1 -0
  115. package/dist/types/client/hook/use-balance.d.ts +18 -0
  116. package/dist/types/client/hook/use-balance.d.ts.map +1 -0
  117. package/dist/types/client/hook/use-clanker.d.ts +23 -0
  118. package/dist/types/client/hook/use-clanker.d.ts.map +1 -0
  119. package/dist/types/client/hook/use-deploy.d.ts +45 -0
  120. package/dist/types/client/hook/use-deploy.d.ts.map +1 -0
  121. package/dist/types/client/hook/use-fee-receivers.d.ts +26 -0
  122. package/dist/types/client/hook/use-fee-receivers.d.ts.map +1 -0
  123. package/dist/types/client/hook/use-governance.d.ts +145 -0
  124. package/dist/types/client/hook/use-governance.d.ts.map +1 -0
  125. package/dist/types/client/hook/use-prepare.d.ts +21 -0
  126. package/dist/types/client/hook/use-prepare.d.ts.map +1 -0
  127. package/dist/types/client/hook/use-project.d.ts +11 -0
  128. package/dist/types/client/hook/use-project.d.ts.map +1 -0
  129. package/dist/types/client/hook/use-projects.d.ts +6 -0
  130. package/dist/types/client/hook/use-projects.d.ts.map +1 -0
  131. package/dist/types/client/hook/use-proposals.d.ts +16 -0
  132. package/dist/types/client/hook/use-proposals.d.ts.map +1 -0
  133. package/dist/types/client/hook/use-register.d.ts +31 -0
  134. package/dist/types/client/hook/use-register.d.ts.map +1 -0
  135. package/dist/types/client/hook/use-stake.d.ts +195 -0
  136. package/dist/types/client/hook/use-stake.d.ts.map +1 -0
  137. package/dist/types/client/hook/use-swap.d.ts +59 -0
  138. package/dist/types/client/hook/use-swap.d.ts.map +1 -0
  139. package/dist/types/client/index.d.ts +4 -0
  140. package/dist/types/client/index.d.ts.map +1 -0
  141. package/dist/types/client/levr-provider.d.ts +127 -0
  142. package/dist/types/client/levr-provider.d.ts.map +1 -0
  143. package/dist/types/client/query-keys.d.ts +61 -0
  144. package/dist/types/client/query-keys.d.ts.map +1 -0
  145. package/dist/types/constants.d.ts +101 -0
  146. package/dist/types/constants.d.ts.map +1 -0
  147. package/dist/types/deploy-v4.d.ts +13 -0
  148. package/dist/types/deploy-v4.d.ts.map +1 -0
  149. package/dist/types/fee-receivers.d.ts +29 -0
  150. package/dist/types/fee-receivers.d.ts.map +1 -0
  151. package/dist/types/governance.d.ts +205 -0
  152. package/dist/types/governance.d.ts.map +1 -0
  153. package/dist/types/index.d.ts +18 -0
  154. package/dist/types/index.d.ts.map +1 -0
  155. package/dist/types/project.d.ts +51 -0
  156. package/dist/types/project.d.ts.map +1 -0
  157. package/dist/types/projects.d.ts +20 -0
  158. package/dist/types/projects.d.ts.map +1 -0
  159. package/dist/types/proposals.d.ts +20 -0
  160. package/dist/types/proposals.d.ts.map +1 -0
  161. package/dist/types/quote-v4.d.ts +54 -0
  162. package/dist/types/quote-v4.d.ts.map +1 -0
  163. package/dist/types/schema/base.schema.d.ts +5 -0
  164. package/dist/types/schema/base.schema.d.ts.map +1 -0
  165. package/dist/types/schema/clanker.schema.d.ts +104 -0
  166. package/dist/types/schema/clanker.schema.d.ts.map +1 -0
  167. package/dist/types/schema/index.d.ts +4 -0
  168. package/dist/types/schema/index.d.ts.map +1 -0
  169. package/dist/types/schema/levr.schema.d.ts +34 -0
  170. package/dist/types/schema/levr.schema.d.ts.map +1 -0
  171. package/dist/types/stake.d.ts +137 -0
  172. package/dist/types/stake.d.ts.map +1 -0
  173. package/dist/types/swap-v4.d.ts +97 -0
  174. package/dist/types/swap-v4.d.ts.map +1 -0
  175. package/dist/types/types.d.ts +20 -0
  176. package/dist/types/types.d.ts.map +1 -0
  177. package/dist/types/util.d.ts +5 -0
  178. package/dist/types/util.d.ts.map +1 -0
  179. package/package.json +100 -0
@@ -0,0 +1,384 @@
1
+ import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem";
2
+ import { LevrForwarder_v1, LevrStaking_v1 } from "./abis/index.js";
3
+ import { WETH } from "./constants.js";
4
+ import { quoteV4 } from "./quote-v4.js";
5
+ export class Stake {
6
+ wallet;
7
+ publicClient;
8
+ stakingAddress;
9
+ tokenAddress;
10
+ tokenDecimals;
11
+ chainId;
12
+ userAddress;
13
+ trustedForwarder;
14
+ constructor(config) {
15
+ if (Object.values(config).some((value) => !value))
16
+ throw new Error("Invalid config");
17
+ this.wallet = config.wallet;
18
+ this.publicClient = config.publicClient;
19
+ this.stakingAddress = config.stakingAddress;
20
+ this.tokenAddress = config.tokenAddress;
21
+ this.tokenDecimals = config.tokenDecimals;
22
+ this.chainId = config.publicClient.chain?.id ?? 1; // Get chainId from publicClient
23
+ this.userAddress = config.wallet.account.address;
24
+ this.trustedForwarder = config.trustedForwarder;
25
+ }
26
+ /**
27
+ * Approve ERC20 tokens for spending by the staking contract
28
+ */
29
+ async approve(amount) {
30
+ const parsedAmount = typeof amount === "bigint" ? amount : parseUnits(amount.toString(), this.tokenDecimals);
31
+ const hash = await this.wallet.writeContract({
32
+ address: this.tokenAddress,
33
+ abi: erc20Abi,
34
+ functionName: "approve",
35
+ args: [this.stakingAddress, parsedAmount],
36
+ chain: this.wallet.chain,
37
+ });
38
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
39
+ if (receipt.status === "reverted") {
40
+ throw new Error("Approve transaction reverted");
41
+ }
42
+ return receipt;
43
+ }
44
+ /**
45
+ * Stake tokens in the staking contract
46
+ */
47
+ async stake(amount) {
48
+ const parsedAmount = typeof amount === "bigint" ? amount : parseUnits(amount.toString(), this.tokenDecimals);
49
+ const hash = await this.wallet.writeContract({
50
+ address: this.stakingAddress,
51
+ abi: LevrStaking_v1,
52
+ functionName: "stake",
53
+ args: [parsedAmount],
54
+ chain: this.wallet.chain,
55
+ });
56
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
57
+ if (receipt.status === "reverted") {
58
+ throw new Error("Stake transaction reverted");
59
+ }
60
+ return receipt;
61
+ }
62
+ /**
63
+ * Unstake tokens from the staking contract
64
+ */
65
+ async unstake({ amount, to }) {
66
+ const parsedAmount = typeof amount === "bigint" ? amount : parseUnits(amount.toString(), this.tokenDecimals);
67
+ const hash = await this.wallet.writeContract({
68
+ address: this.stakingAddress,
69
+ abi: LevrStaking_v1,
70
+ functionName: "unstake",
71
+ args: [parsedAmount, to ?? this.userAddress],
72
+ chain: this.wallet.chain,
73
+ });
74
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
75
+ if (receipt.status === "reverted") {
76
+ throw new Error("Unstake transaction reverted");
77
+ }
78
+ return receipt;
79
+ }
80
+ /**
81
+ * Claim rewards from the staking contract
82
+ */
83
+ async claimRewards(params) {
84
+ // Default to claiming both staking token and WETH
85
+ let defaultTokens = [this.tokenAddress];
86
+ const wethAddress = WETH(this.chainId)?.address;
87
+ if (wethAddress) {
88
+ defaultTokens.push(wethAddress);
89
+ }
90
+ const hash = await this.wallet.writeContract({
91
+ address: this.stakingAddress,
92
+ abi: LevrStaking_v1,
93
+ functionName: "claimRewards",
94
+ args: [params?.tokens ?? defaultTokens, params?.to ?? this.userAddress],
95
+ chain: this.wallet.chain,
96
+ });
97
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
98
+ if (receipt.status === "reverted") {
99
+ throw new Error("Claim transaction reverted");
100
+ }
101
+ return receipt;
102
+ }
103
+ /**
104
+ * Get allowance for a token and spender
105
+ */
106
+ async getAllowance() {
107
+ const result = await this.publicClient.readContract({
108
+ address: this.tokenAddress,
109
+ abi: erc20Abi,
110
+ functionName: "allowance",
111
+ args: [this.userAddress, this.stakingAddress],
112
+ });
113
+ return {
114
+ raw: result,
115
+ formatted: formatUnits(result, 18), // Assuming 18 decimals, should be passed as param if needed
116
+ };
117
+ }
118
+ /**
119
+ * Get pool data from staking contract
120
+ */
121
+ async getPoolData() {
122
+ // Get current block to use blockchain time
123
+ const currentBlock = await this.publicClient.getBlock();
124
+ const blockTime = currentBlock.timestamp;
125
+ const results = await this.publicClient.multicall({
126
+ contracts: [
127
+ {
128
+ address: this.stakingAddress,
129
+ abi: LevrStaking_v1,
130
+ functionName: "totalStaked",
131
+ },
132
+ {
133
+ address: this.stakingAddress,
134
+ abi: LevrStaking_v1,
135
+ functionName: "escrowBalance",
136
+ args: [this.tokenAddress],
137
+ },
138
+ {
139
+ address: this.stakingAddress,
140
+ abi: LevrStaking_v1,
141
+ functionName: "streamWindowSeconds",
142
+ },
143
+ {
144
+ address: this.stakingAddress,
145
+ abi: LevrStaking_v1,
146
+ functionName: "streamStart",
147
+ },
148
+ {
149
+ address: this.stakingAddress,
150
+ abi: LevrStaking_v1,
151
+ functionName: "streamEnd",
152
+ },
153
+ {
154
+ address: this.stakingAddress,
155
+ abi: LevrStaking_v1,
156
+ functionName: "rewardRatePerSecond",
157
+ args: [this.tokenAddress],
158
+ },
159
+ ],
160
+ });
161
+ const [totalStaked, escrowBalance, windowSeconds, streamStart, streamEnd, rewardRate] = results.map((r) => r.result);
162
+ return {
163
+ totalStaked: {
164
+ raw: totalStaked,
165
+ formatted: formatUnits(totalStaked, this.tokenDecimals),
166
+ },
167
+ escrowBalance: {
168
+ raw: escrowBalance,
169
+ formatted: formatUnits(escrowBalance, this.tokenDecimals),
170
+ },
171
+ streamParams: {
172
+ windowSeconds: windowSeconds,
173
+ streamStart: streamStart,
174
+ streamEnd: streamEnd,
175
+ isActive: blockTime < streamEnd,
176
+ },
177
+ rewardRatePerSecond: {
178
+ raw: rewardRate,
179
+ formatted: formatUnits(rewardRate, this.tokenDecimals),
180
+ },
181
+ };
182
+ }
183
+ /**
184
+ * Get user data from staking contract
185
+ */
186
+ async getUserData() {
187
+ const results = await this.publicClient.multicall({
188
+ contracts: [
189
+ {
190
+ address: this.stakingAddress,
191
+ abi: LevrStaking_v1,
192
+ functionName: "stakedBalanceOf",
193
+ args: [this.userAddress],
194
+ },
195
+ {
196
+ address: this.stakingAddress,
197
+ abi: LevrStaking_v1,
198
+ functionName: "aprBps",
199
+ args: [],
200
+ },
201
+ ],
202
+ });
203
+ const stakedBalance = results[0].result;
204
+ const aprBps = results[1].result;
205
+ return {
206
+ stakedBalance: {
207
+ raw: stakedBalance,
208
+ formatted: formatUnits(stakedBalance, this.tokenDecimals),
209
+ },
210
+ aprBps: {
211
+ raw: aprBps,
212
+ percentage: Number(aprBps) / 100,
213
+ },
214
+ };
215
+ }
216
+ /**
217
+ * Get outstanding rewards for the token (for accrual purposes)
218
+ */
219
+ async getOutstandingRewards(tokenAddress) {
220
+ const token = tokenAddress ?? this.tokenAddress;
221
+ const decimals = token === this.tokenAddress ? this.tokenDecimals : 18; // Assume 18 for other tokens
222
+ const result = await this.publicClient.readContract({
223
+ address: this.stakingAddress,
224
+ abi: LevrStaking_v1,
225
+ functionName: "outstandingRewards",
226
+ args: [token],
227
+ });
228
+ return {
229
+ available: {
230
+ raw: result[0],
231
+ formatted: formatUnits(result[0], decimals),
232
+ },
233
+ pending: {
234
+ raw: result[1],
235
+ formatted: formatUnits(result[1], decimals),
236
+ },
237
+ };
238
+ }
239
+ /**
240
+ * Get claimable rewards for the current user and token
241
+ */
242
+ async getClaimableRewards(tokenAddress) {
243
+ const token = tokenAddress ?? this.tokenAddress;
244
+ const decimals = token === this.tokenAddress ? this.tokenDecimals : 18; // Assume 18 for other tokens
245
+ const result = await this.publicClient.readContract({
246
+ address: this.stakingAddress,
247
+ abi: LevrStaking_v1,
248
+ functionName: "claimableRewards",
249
+ args: [this.userAddress, token],
250
+ });
251
+ return {
252
+ claimable: {
253
+ raw: result,
254
+ formatted: formatUnits(result, decimals),
255
+ },
256
+ };
257
+ }
258
+ /**
259
+ * Get reward rate per second for a specific token
260
+ */
261
+ async getRewardRatePerSecond(tokenAddress) {
262
+ const token = tokenAddress ?? this.tokenAddress;
263
+ const decimals = token === this.tokenAddress ? this.tokenDecimals : 18; // Assume 18 for other tokens
264
+ const result = await this.publicClient.readContract({
265
+ address: this.stakingAddress,
266
+ abi: LevrStaking_v1,
267
+ functionName: "rewardRatePerSecond",
268
+ args: [token],
269
+ });
270
+ return {
271
+ raw: result,
272
+ formatted: formatUnits(result, decimals),
273
+ };
274
+ }
275
+ /**
276
+ * Accrue rewards by triggering automatic collection from LP locker and claiming from ClankerFeeLocker
277
+ */
278
+ async accrueRewards(tokenAddress) {
279
+ const hash = await this.wallet.writeContract({
280
+ address: this.stakingAddress,
281
+ abi: LevrStaking_v1,
282
+ functionName: "accrueRewards",
283
+ args: [tokenAddress ?? this.tokenAddress],
284
+ chain: this.wallet.chain,
285
+ });
286
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
287
+ if (receipt.status === "reverted") {
288
+ throw new Error("Accrue rewards transaction reverted");
289
+ }
290
+ return receipt;
291
+ }
292
+ /**
293
+ * Accrue rewards for multiple tokens in a single transaction using forwarder multicall
294
+ */
295
+ async accrueAllRewards(tokenAddresses) {
296
+ if (!this.trustedForwarder) {
297
+ throw new Error("Trusted forwarder is required for multicall operations");
298
+ }
299
+ // Use forwarder's executeMulticall for meta-transaction support
300
+ const calls = tokenAddresses.map((tokenAddress) => ({
301
+ target: this.stakingAddress,
302
+ allowFailure: false,
303
+ value: 0n,
304
+ callData: encodeFunctionData({
305
+ abi: LevrStaking_v1,
306
+ functionName: "accrueRewards",
307
+ args: [tokenAddress],
308
+ }),
309
+ }));
310
+ const hash = await this.wallet.writeContract({
311
+ address: this.trustedForwarder,
312
+ abi: LevrForwarder_v1,
313
+ functionName: "executeMulticall",
314
+ args: [calls],
315
+ chain: this.wallet.chain,
316
+ });
317
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
318
+ if (receipt.status === "reverted") {
319
+ throw new Error("Accrue all rewards transaction reverted");
320
+ }
321
+ return receipt;
322
+ }
323
+ /**
324
+ * Calculate WETH APR using pool price and reward rates
325
+ * Formula: (wethRewardRatePerSecond * secondsPerYear * wethPriceInUnderlying / totalStaked) * 10000
326
+ *
327
+ * @param poolKey - The Uniswap V4 pool key for price discovery
328
+ * @returns WETH APR in basis points and percentage
329
+ */
330
+ async calculateWethApr(poolKey) {
331
+ const wethAddress = WETH(this.chainId)?.address;
332
+ if (!wethAddress) {
333
+ throw new Error("WETH address not found for this chain");
334
+ }
335
+ // Get pool data and WETH reward rate
336
+ const [poolData, wethRewardRate] = await Promise.all([
337
+ this.getPoolData(),
338
+ this.getRewardRatePerSecond(wethAddress),
339
+ ]);
340
+ const totalStaked = poolData.totalStaked.raw;
341
+ // If no stakers or no WETH rewards, APR is 0
342
+ if (totalStaked === 0n || wethRewardRate.raw === 0n) {
343
+ return { raw: 0n, percentage: 0 };
344
+ }
345
+ // Quote 1 WETH to determine price in underlying tokens
346
+ // Determine swap direction: WETH -> underlying
347
+ const wethIsCurrency0 = wethAddress.toLowerCase() < this.tokenAddress.toLowerCase();
348
+ const zeroForOne = wethIsCurrency0; // Swap WETH for underlying
349
+ // Quote 1 WETH (18 decimals)
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);
366
+ return { raw: 0n, percentage: 0 };
367
+ }
368
+ // Calculate annual WETH rewards (rewards per second * seconds per year)
369
+ const secondsPerYear = BigInt(365 * 24 * 60 * 60);
370
+ const annualWethRewards = wethRewardRate.raw * secondsPerYear;
371
+ // Convert WETH rewards to underlying token equivalent
372
+ // wethPriceInUnderlying is already in underlying token decimals from the quote
373
+ // annualWethRewards is in WETH (18 decimals)
374
+ // Both need to be normalized to underlying token decimals
375
+ const annualRewardsInUnderlying = (annualWethRewards * wethPriceInUnderlying) / BigInt(1000000000000000000);
376
+ // Calculate APR: (annualRewardsInUnderlying / totalStaked) * 10000
377
+ const aprBps = (annualRewardsInUnderlying * 10000n) / totalStaked;
378
+ return {
379
+ raw: aprBps,
380
+ percentage: Number(aprBps) / 100, // Convert bps to percentage
381
+ };
382
+ }
383
+ }
384
+ //# sourceMappingURL=stake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stake.js","sourceRoot":"","sources":["../../src/stake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,281 @@
1
+ import { CommandType, RoutePlanner } from "@uniswap/universal-router-sdk";
2
+ import { Actions, V4Planner } from "@uniswap/v4-sdk";
3
+ import { erc20Abi } from "viem";
4
+ import Permit2Abi from "./abis/Permit2.js";
5
+ import { ADDRESS_THIS, CONTRACT_BALANCE, MSG_SENDER, UNISWAP_V4_PERMIT2, UNISWAP_V4_UNIVERSAL_ROUTER, WETH, } from "./constants.js";
6
+ /**
7
+ * @description Check if a currency is WETH
8
+ * @param currency Currency address
9
+ * @param chainId Chain ID to get WETH address
10
+ * @returns True if WETH
11
+ */
12
+ const isWETH = (currency, chainId) => {
13
+ const wethInfo = WETH(chainId);
14
+ if (!wethInfo)
15
+ return false;
16
+ return currency.toLowerCase() === wethInfo.address.toLowerCase();
17
+ };
18
+ /**
19
+ * @description Execute a Uniswap V4 swap with automatic native ETH handling
20
+ * @param params Swap parameters including pool key and amounts
21
+ * @returns Transaction receipt
22
+ *
23
+ * @remarks
24
+ * **Architecture:**
25
+ * - Uses Universal Router with V4Planner for encoding swap actions
26
+ * - Automatically handles native ETH ↔ WETH conversions (users never hold WETH)
27
+ * - ERC20 approvals via Permit2 (only for non-WETH tokens)
28
+ * - 20-minute deadline from current block timestamp
29
+ *
30
+ * **Execution Flow:**
31
+ *
32
+ * 1. **Approvals (ERC20 Tokens Only)**:
33
+ * - Checks token balance and existing allowances via multicall
34
+ * - Token → Permit2: Infinite approval if needed
35
+ * - Permit2 → Router: MAX_UINT160 approval with 30-day expiration
36
+ * - Native ETH swaps skip this step entirely
37
+ *
38
+ * 2. **Action Encoding**:
39
+ * - **RoutePlanner Commands**:
40
+ * - `WRAP_ETH` (if input is WETH): Wraps msg.value to router's WETH balance
41
+ * - `V4_SWAP`: Executes the V4Planner actions (see below)
42
+ * - `UNWRAP_WETH` (if output is WETH): Converts router's WETH to native ETH for user
43
+ *
44
+ * - **V4Planner Actions** (nested in V4_SWAP):
45
+ * - `SWAP_EXACT_IN_SINGLE`: Execute the swap with specified pool and amounts
46
+ * - `SETTLE` or `SETTLE_ALL`: Pay for input tokens
47
+ * - `SETTLE(currency, amount, false)`: Router pays from its balance (for WETH input after WRAP_ETH)
48
+ * - `SETTLE_ALL(currency, amount)`: User pays from their token balance (for ERC20 input)
49
+ * - `TAKE` or `TAKE_ALL`: Receive output tokens
50
+ * - `TAKE(currency, routerAddress, 0)`: Router receives WETH (before UNWRAP_WETH)
51
+ * - `TAKE_ALL(currency, 0)`: User directly receives tokens
52
+ *
53
+ * 3. **Transaction Execution**:
54
+ * - Calls `execute(commands, inputs, deadline)` on Universal Router
55
+ * - Sends msg.value = amountIn for WETH input (native ETH)
56
+ * - Sends msg.value = 0 for ERC20 token input
57
+ * - Waits for receipt and validates success
58
+ *
59
+ * **Native ETH Flows:**
60
+ * - **Buy tokens with ETH** (WETH → Token):
61
+ * - User sends ETH as msg.value
62
+ * - WRAP_ETH wraps it to router's WETH balance
63
+ * - SETTLE pays swap from router's WETH (payerIsUser=false)
64
+ * - TAKE_ALL sends tokens directly to user
65
+ *
66
+ * - **Sell tokens for ETH** (Token → WETH):
67
+ * - SETTLE_ALL pays swap from user's token balance (via Permit2)
68
+ * - TAKE sends WETH to router
69
+ * - UNWRAP_WETH converts router's WETH to native ETH
70
+ * - User receives native ETH (sent to MSG_SENDER)
71
+ *
72
+ * - **Token ↔ Token**: Standard ERC20 flow, no WRAP/UNWRAP needed
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // Buy tokens with native ETH
77
+ * const receipt = await swapV4({
78
+ * publicClient,
79
+ * wallet,
80
+ * chainId: base.id,
81
+ * poolKey,
82
+ * zeroForOne: true, // WETH → Token
83
+ * amountIn: parseEther('0.01'),
84
+ * amountOutMinimum: parseUnits('100', tokenDecimals),
85
+ * hookData: '0x', // Optional: pool-specific hook data
86
+ * })
87
+ * ```
88
+ */
89
+ export const swapV4 = async ({ publicClient, wallet, chainId, poolKey, zeroForOne, amountIn, amountOutMinimum, hookData = "0x", onApproveERC20Success, onApproveERC20Error, onApprovePermit2Success, onApprovePermit2Error, }) => {
90
+ if (!wallet.account)
91
+ throw new Error("Wallet account not found");
92
+ const routerAddress = UNISWAP_V4_UNIVERSAL_ROUTER(chainId);
93
+ if (!routerAddress)
94
+ throw new Error("V4 Router address not found for chain");
95
+ const permit2Address = UNISWAP_V4_PERMIT2(chainId);
96
+ if (!permit2Address)
97
+ throw new Error("Permit2 address not found for chain");
98
+ const inputCurrency = zeroForOne ? poolKey.currency0 : poolKey.currency1;
99
+ const outputCurrency = zeroForOne ? poolKey.currency1 : poolKey.currency0;
100
+ const isInputWETH = isWETH(inputCurrency, chainId);
101
+ const isOutputWETH = isWETH(outputCurrency, chainId);
102
+ // Get current block timestamp for deadline and approval checks
103
+ const block = await publicClient.getBlock();
104
+ const currentTime = block.timestamp;
105
+ // Step 1 & 2: Approvals required for ERC20 tokens (but NOT for native ETH)
106
+ // Note: When swapping native ETH, we use WRAP_ETH command - no approvals needed
107
+ // When swapping ERC20 tokens, we need Permit2 approvals for transferFrom
108
+ // Only check balances and approvals for non-WETH tokens
109
+ if (!isInputWETH) {
110
+ // Use multicall to batch balance and allowance checks
111
+ const multicallResults = await publicClient.multicall({
112
+ contracts: [
113
+ // 0: Token balance
114
+ {
115
+ address: inputCurrency,
116
+ abi: erc20Abi,
117
+ functionName: "balanceOf",
118
+ args: [wallet.account.address],
119
+ },
120
+ // 1: Permit2 allowance
121
+ {
122
+ address: inputCurrency,
123
+ abi: erc20Abi,
124
+ functionName: "allowance",
125
+ args: [wallet.account.address, permit2Address],
126
+ },
127
+ // 2: Router allowance via Permit2
128
+ {
129
+ address: permit2Address,
130
+ abi: Permit2Abi,
131
+ functionName: "allowance",
132
+ args: [wallet.account.address, inputCurrency, routerAddress],
133
+ },
134
+ ],
135
+ });
136
+ const balance = multicallResults[0].status === "success" ? multicallResults[0].result : 0n;
137
+ const permit2Allowance = multicallResults[1].status === "success" ? multicallResults[1].result : 0n;
138
+ const routerAllowance = multicallResults[2].status === "success"
139
+ ? multicallResults[2].result
140
+ : [0n, 0n, 0n];
141
+ // Verify sufficient token balance
142
+ if (balance < amountIn) {
143
+ throw new Error(`Insufficient token balance: have ${balance}, need ${amountIn}`);
144
+ }
145
+ // Step 1: Approve Permit2 to spend input token if needed
146
+ const MAX_UINT256 = 2n ** 256n - 1n;
147
+ if (permit2Allowance < amountIn) {
148
+ try {
149
+ const approveTx = await wallet.writeContract({
150
+ address: inputCurrency,
151
+ abi: erc20Abi,
152
+ functionName: "approve",
153
+ args: [permit2Address, MAX_UINT256],
154
+ account: wallet.account,
155
+ chain: wallet.chain,
156
+ });
157
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: approveTx });
158
+ onApproveERC20Success?.(receipt);
159
+ }
160
+ catch (error) {
161
+ onApproveERC20Error?.(error);
162
+ throw error;
163
+ }
164
+ }
165
+ // Step 2: Approve Universal Router via Permit2 if needed
166
+ const MAX_UINT160 = 2n ** 160n - 1n;
167
+ // Check if allowance is sufficient and not expired
168
+ // routerAllowance: [amount: uint160, expiration: uint48, nonce: uint48]
169
+ const needsApproval = routerAllowance[0] < amountIn || routerAllowance[1] <= currentTime;
170
+ if (needsApproval) {
171
+ try {
172
+ // Approve with 30 days expiration (uint48) from current block time
173
+ const expirationBigInt = currentTime + BigInt(30 * 24 * 60 * 60);
174
+ const permit2ApproveTx = await wallet.writeContract({
175
+ address: permit2Address,
176
+ abi: Permit2Abi,
177
+ functionName: "approve",
178
+ args: [inputCurrency, routerAddress, MAX_UINT160, Number(expirationBigInt)],
179
+ account: wallet.account,
180
+ chain: wallet.chain,
181
+ });
182
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: permit2ApproveTx });
183
+ onApprovePermit2Success?.(receipt);
184
+ }
185
+ catch (error) {
186
+ onApprovePermit2Error?.(error);
187
+ throw error;
188
+ }
189
+ }
190
+ }
191
+ // Step 3: Encode swap using V4Planner and RoutePlanner
192
+ const v4Planner = new V4Planner();
193
+ const routePlanner = new RoutePlanner();
194
+ // Step 3a: If input is native ETH, add WRAP_ETH command first
195
+ if (isInputWETH)
196
+ routePlanner.addCommand(CommandType.WRAP_ETH, [ADDRESS_THIS, CONTRACT_BALANCE]);
197
+ // Step 3b: Build V4 actions: SWAP_EXACT_IN_SINGLE -> SETTLE_ALL -> TAKE_ALL/TAKE
198
+ const swapConfig = {
199
+ poolKey: {
200
+ currency0: poolKey.currency0,
201
+ currency1: poolKey.currency1,
202
+ fee: poolKey.fee,
203
+ tickSpacing: poolKey.tickSpacing,
204
+ hooks: poolKey.hooks,
205
+ },
206
+ zeroForOne,
207
+ amountIn,
208
+ amountOutMinimum,
209
+ hookData,
210
+ };
211
+ v4Planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [swapConfig]);
212
+ // SETTLE: Choose between SETTLE_ALL (user pays) vs SETTLE (router pays)
213
+ // - For native ETH input: Use SETTLE with payerIsUser=false (router pays from WRAP_ETH balance)
214
+ // - For ERC20 tokens: Use SETTLE_ALL (user pays from their token balance)
215
+ if (isInputWETH)
216
+ // Router pays from its WETH balance (from WRAP_ETH)
217
+ v4Planner.addAction(Actions.SETTLE, [inputCurrency, amountIn, false]);
218
+ else
219
+ // User pays from their token balance
220
+ v4Planner.addAction(Actions.SETTLE_ALL, [inputCurrency, amountIn]);
221
+ if (isOutputWETH)
222
+ // Take WETH to router first, then UNWRAP_WETH will convert to native ETH for user (MSG_SENDER)
223
+ v4Planner.addAction(Actions.TAKE, [outputCurrency, routerAddress, 0n]);
224
+ // For token output, TAKE_ALL directly to user
225
+ else
226
+ v4Planner.addAction(Actions.TAKE_ALL, [outputCurrency, 0n]);
227
+ // Step 3c: Finalize V4 planner to get encoded actions
228
+ // finalize() returns the full encoded bytes that will be passed to the PoolManager
229
+ const encodedV4Actions = v4Planner.finalize();
230
+ // Add V4_SWAP command - note we only add it to get the command byte,
231
+ // but we'll manually construct the inputs array
232
+ routePlanner.addCommand(CommandType.V4_SWAP, [encodedV4Actions]);
233
+ // Step 3d: If output is WETH, unwrap to native ETH
234
+ if (isOutputWETH)
235
+ routePlanner.addCommand(CommandType.UNWRAP_WETH, [MSG_SENDER, 0n]);
236
+ // Get commands from RoutePlanner
237
+ const commands = routePlanner.commands;
238
+ const inputs = routePlanner.inputs;
239
+ // Universal Router ABI (with deadline)
240
+ const routerAbi = [
241
+ {
242
+ inputs: [
243
+ { internalType: "bytes", name: "commands", type: "bytes" },
244
+ { internalType: "bytes[]", name: "inputs", type: "bytes[]" },
245
+ { internalType: "uint256", name: "deadline", type: "uint256" },
246
+ ],
247
+ name: "execute",
248
+ outputs: [],
249
+ stateMutability: "payable",
250
+ type: "function",
251
+ },
252
+ ];
253
+ // Set deadline (20 minutes from current block time)
254
+ const deadline = currentTime + BigInt(20 * 60);
255
+ try {
256
+ // First simulate to get better error messages
257
+ // Note: Provide msg.value for native ETH or WETH inputs
258
+ // - For native ETH: direct payment
259
+ // - For WETH: router wraps the ETH sent as msg.value
260
+ const txValue = isInputWETH ? amountIn : 0n;
261
+ // If simulation passes, execute the swap
262
+ const txHash = await wallet.writeContract({
263
+ address: routerAddress,
264
+ abi: routerAbi,
265
+ functionName: "execute",
266
+ args: [commands, inputs, deadline],
267
+ value: txValue,
268
+ account: wallet.account,
269
+ chain: wallet.chain,
270
+ });
271
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
272
+ if (receipt.status === "reverted") {
273
+ throw new Error("Swap transaction reverted");
274
+ }
275
+ return receipt;
276
+ }
277
+ catch (error) {
278
+ throw new Error(`Swap failed: ${error instanceof Error ? error.message : String(error)}`);
279
+ }
280
+ };
281
+ //# sourceMappingURL=swap-v4.js.map