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.
- package/LICENSE.md +201 -0
- package/README.md +711 -0
- package/dist/esm/abis/IClankerAirdrop.js +443 -0
- package/dist/esm/abis/IClankerHookDynamicFee.js +198 -0
- package/dist/esm/abis/IClankerHookStaticFee.js +75 -0
- package/dist/esm/abis/IClankerLPLocker.js +402 -0
- package/dist/esm/abis/IClankerLpLockerMultiple.js +609 -0
- package/dist/esm/abis/IClankerToken.js +421 -0
- package/dist/esm/abis/LevrFactory_v1.js +608 -0
- package/dist/esm/abis/LevrForwarder_v1.js +622 -0
- package/dist/esm/abis/LevrGovernor_v1.js +726 -0
- package/dist/esm/abis/LevrStakedToken_v1.js +441 -0
- package/dist/esm/abis/LevrStaking_v1.js +607 -0
- package/dist/esm/abis/LevrTreasury_v1.js +219 -0
- package/dist/esm/abis/Permit2.js +29 -0
- package/dist/esm/abis/V4Quoter.js +466 -0
- package/dist/esm/abis/WETH9.js +153 -0
- package/dist/esm/abis/index.js +23 -0
- package/dist/esm/balance.js +72 -0
- package/dist/esm/balance.js.map +1 -0
- package/dist/esm/build-calldatas-v4.js +98 -0
- package/dist/esm/build-calldatas-v4.js.map +1 -0
- package/dist/esm/build-clanker-v4.js +128 -0
- package/dist/esm/build-clanker-v4.js.map +1 -0
- package/dist/esm/client/hook/index.js +36 -0
- package/dist/esm/client/hook/index.js.map +1 -0
- package/dist/esm/client/hook/use-balance.js +56 -0
- package/dist/esm/client/hook/use-balance.js.map +1 -0
- package/dist/esm/client/hook/use-clanker.js +57 -0
- package/dist/esm/client/hook/use-clanker.js.map +1 -0
- package/dist/esm/client/hook/use-deploy.js +16 -0
- package/dist/esm/client/hook/use-deploy.js.map +1 -0
- package/dist/esm/client/hook/use-fee-receivers.js +54 -0
- package/dist/esm/client/hook/use-fee-receivers.js.map +1 -0
- package/dist/esm/client/hook/use-governance.js +389 -0
- package/dist/esm/client/hook/use-governance.js.map +1 -0
- package/dist/esm/client/hook/use-prepare.js +47 -0
- package/dist/esm/client/hook/use-prepare.js.map +1 -0
- package/dist/esm/client/hook/use-project.js +29 -0
- package/dist/esm/client/hook/use-project.js.map +1 -0
- package/dist/esm/client/hook/use-projects.js +22 -0
- package/dist/esm/client/hook/use-projects.js.map +1 -0
- package/dist/esm/client/hook/use-proposals.js +31 -0
- package/dist/esm/client/hook/use-proposals.js.map +1 -0
- package/dist/esm/client/hook/use-register.js +101 -0
- package/dist/esm/client/hook/use-register.js.map +1 -0
- package/dist/esm/client/hook/use-stake.js +327 -0
- package/dist/esm/client/hook/use-stake.js.map +1 -0
- package/dist/esm/client/hook/use-swap.js +110 -0
- package/dist/esm/client/hook/use-swap.js.map +1 -0
- package/dist/esm/client/index.js +4 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/levr-provider.js +201 -0
- package/dist/esm/client/levr-provider.js.map +1 -0
- package/dist/esm/client/query-keys.js +61 -0
- package/dist/esm/client/query-keys.js.map +1 -0
- package/dist/esm/constants.js +182 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/deploy-v4.js +46 -0
- package/dist/esm/deploy-v4.js.map +1 -0
- package/dist/esm/fee-receivers.js +52 -0
- package/dist/esm/fee-receivers.js.map +1 -0
- package/dist/esm/governance.js +555 -0
- package/dist/esm/governance.js.map +1 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/project.js +146 -0
- package/dist/esm/project.js.map +1 -0
- package/dist/esm/projects.js +212 -0
- package/dist/esm/projects.js.map +1 -0
- package/dist/esm/proposals.js +98 -0
- package/dist/esm/proposals.js.map +1 -0
- package/dist/esm/quote-v4.js +169 -0
- package/dist/esm/quote-v4.js.map +1 -0
- package/dist/esm/schema/base.schema.js +11 -0
- package/dist/esm/schema/base.schema.js.map +1 -0
- package/dist/esm/schema/clanker.schema.js +191 -0
- package/dist/esm/schema/clanker.schema.js.map +1 -0
- package/dist/esm/schema/index.js +4 -0
- package/dist/esm/schema/index.js.map +1 -0
- package/dist/esm/schema/levr.schema.js +114 -0
- package/dist/esm/schema/levr.schema.js.map +1 -0
- package/dist/esm/stake.js +384 -0
- package/dist/esm/stake.js.map +1 -0
- package/dist/esm/swap-v4.js +281 -0
- package/dist/esm/swap-v4.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/util.js +19 -0
- package/dist/esm/util.js.map +1 -0
- package/dist/types/abis/IClankerAirdrop.d.ts +348 -0
- package/dist/types/abis/IClankerHookDynamicFee.d.ts +156 -0
- package/dist/types/abis/IClankerHookStaticFee.d.ts +58 -0
- package/dist/types/abis/IClankerLPLocker.d.ts +316 -0
- package/dist/types/abis/IClankerLpLockerMultiple.d.ts +481 -0
- package/dist/types/abis/IClankerToken.d.ts +326 -0
- package/dist/types/abis/LevrFactory_v1.d.ts +471 -0
- package/dist/types/abis/LevrForwarder_v1.d.ts +478 -0
- package/dist/types/abis/LevrGovernor_v1.d.ts +562 -0
- package/dist/types/abis/LevrStakedToken_v1.d.ts +338 -0
- package/dist/types/abis/LevrStaking_v1.d.ts +468 -0
- package/dist/types/abis/LevrTreasury_v1.d.ts +169 -0
- package/dist/types/abis/Permit2.d.ts +54 -0
- package/dist/types/abis/V4Quoter.d.ts +496 -0
- package/dist/types/abis/WETH9.d.ts +244 -0
- package/dist/types/abis/index.d.ts +15 -0
- package/dist/types/balance.d.ts +24 -0
- package/dist/types/balance.d.ts.map +1 -0
- package/dist/types/build-calldatas-v4.d.ts +20 -0
- package/dist/types/build-calldatas-v4.d.ts.map +1 -0
- package/dist/types/build-clanker-v4.d.ts +12 -0
- package/dist/types/build-clanker-v4.d.ts.map +1 -0
- package/dist/types/client/hook/index.d.ts +32 -0
- package/dist/types/client/hook/index.d.ts.map +1 -0
- package/dist/types/client/hook/use-balance.d.ts +18 -0
- package/dist/types/client/hook/use-balance.d.ts.map +1 -0
- package/dist/types/client/hook/use-clanker.d.ts +23 -0
- package/dist/types/client/hook/use-clanker.d.ts.map +1 -0
- package/dist/types/client/hook/use-deploy.d.ts +45 -0
- package/dist/types/client/hook/use-deploy.d.ts.map +1 -0
- package/dist/types/client/hook/use-fee-receivers.d.ts +26 -0
- package/dist/types/client/hook/use-fee-receivers.d.ts.map +1 -0
- package/dist/types/client/hook/use-governance.d.ts +145 -0
- package/dist/types/client/hook/use-governance.d.ts.map +1 -0
- package/dist/types/client/hook/use-prepare.d.ts +21 -0
- package/dist/types/client/hook/use-prepare.d.ts.map +1 -0
- package/dist/types/client/hook/use-project.d.ts +11 -0
- package/dist/types/client/hook/use-project.d.ts.map +1 -0
- package/dist/types/client/hook/use-projects.d.ts +6 -0
- package/dist/types/client/hook/use-projects.d.ts.map +1 -0
- package/dist/types/client/hook/use-proposals.d.ts +16 -0
- package/dist/types/client/hook/use-proposals.d.ts.map +1 -0
- package/dist/types/client/hook/use-register.d.ts +31 -0
- package/dist/types/client/hook/use-register.d.ts.map +1 -0
- package/dist/types/client/hook/use-stake.d.ts +195 -0
- package/dist/types/client/hook/use-stake.d.ts.map +1 -0
- package/dist/types/client/hook/use-swap.d.ts +59 -0
- package/dist/types/client/hook/use-swap.d.ts.map +1 -0
- package/dist/types/client/index.d.ts +4 -0
- package/dist/types/client/index.d.ts.map +1 -0
- package/dist/types/client/levr-provider.d.ts +127 -0
- package/dist/types/client/levr-provider.d.ts.map +1 -0
- package/dist/types/client/query-keys.d.ts +61 -0
- package/dist/types/client/query-keys.d.ts.map +1 -0
- package/dist/types/constants.d.ts +101 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/deploy-v4.d.ts +13 -0
- package/dist/types/deploy-v4.d.ts.map +1 -0
- package/dist/types/fee-receivers.d.ts +29 -0
- package/dist/types/fee-receivers.d.ts.map +1 -0
- package/dist/types/governance.d.ts +205 -0
- package/dist/types/governance.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/project.d.ts +51 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/projects.d.ts +20 -0
- package/dist/types/projects.d.ts.map +1 -0
- package/dist/types/proposals.d.ts +20 -0
- package/dist/types/proposals.d.ts.map +1 -0
- package/dist/types/quote-v4.d.ts +54 -0
- package/dist/types/quote-v4.d.ts.map +1 -0
- package/dist/types/schema/base.schema.d.ts +5 -0
- package/dist/types/schema/base.schema.d.ts.map +1 -0
- package/dist/types/schema/clanker.schema.d.ts +104 -0
- package/dist/types/schema/clanker.schema.d.ts.map +1 -0
- package/dist/types/schema/index.d.ts +4 -0
- package/dist/types/schema/index.d.ts.map +1 -0
- package/dist/types/schema/levr.schema.d.ts +34 -0
- package/dist/types/schema/levr.schema.d.ts.map +1 -0
- package/dist/types/stake.d.ts +137 -0
- package/dist/types/stake.d.ts.map +1 -0
- package/dist/types/swap-v4.d.ts +97 -0
- package/dist/types/swap-v4.d.ts.map +1 -0
- package/dist/types/types.d.ts +20 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/util.d.ts +5 -0
- package/dist/types/util.d.ts.map +1 -0
- 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
|