From d2ae776f37be1663777c3ef89ff01a7748f3e2f0 Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Sun, 15 Feb 2026 22:18:27 +0330 Subject: [PATCH 1/7] feat: BETHToETH implementation --- src/BETHToETH.sol | 47 ++++++++++++++++++++++++++++++++ src/interfaces/ISwapRouter.sol | 17 ++++++++++++ src/interfaces/IWNativeToken.sol | 13 +++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/BETHToETH.sol create mode 100644 src/interfaces/ISwapRouter.sol create mode 100644 src/interfaces/IWNativeToken.sol diff --git a/src/BETHToETH.sol b/src/BETHToETH.sol new file mode 100644 index 0000000..983f7e4 --- /dev/null +++ b/src/BETHToETH.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IWNativeToken } from "src/interfaces/IWNativeToken.sol"; +import { ISwapRouter } from "src/interfaces/ISwapRouter.sol"; + +contract BETHToETH{ + + IERC20 public immutable bethContract; + IWNativeToken public immutable wethContract; + + constructor(IERC20 _bethContract, IWNativeToken _wethContract) { + require(address(_bethContract) != address(0), "Invalid BETH address"); + require(address(_wethContract) != address(0), "Invalid WETH address"); + bethContract = _bethContract; + wethContract = _wethContract; + } + + function swapBethWithEth(uint256 _swapAmount, address _recipient, ISwapRouter _swapRouter) public returns (uint256 amountOut){ + require(_swapAmount > 0, "Amount must be greater than 0"); + require(_recipient != address(0), "Invalid recipient"); + + require( + bethContract.transferFrom(msg.sender, address(this), _swapAmount), + "error while transferFrom beth to this" + ); + + bethContract.approve(address(_swapRouter), _swapAmount); + + amountOut = _swapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({ + tokenIn: address(bethContract), + tokenOut: address(wethContract), + deployer: address(0), + recipient: address(this), + deadline: block.timestamp + 15 minutes, + amountIn: _swapAmount, + amountOutMinimum: 0, + limitSqrtPrice: 0 + })); + + bethContract.approve(address(_swapRouter), 0); // extra safety + wethContract.withdraw(amountOut); + (bool success, ) = _recipient.call{value: amountOut}(""); + require(success, "ETH transfer failed"); + } +} diff --git a/src/interfaces/ISwapRouter.sol b/src/interfaces/ISwapRouter.sol new file mode 100644 index 0000000..5cd92e1 --- /dev/null +++ b/src/interfaces/ISwapRouter.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +interface ISwapRouter{ + struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + address deployer; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 limitSqrtPrice; + } + + function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); +} \ No newline at end of file diff --git a/src/interfaces/IWNativeToken.sol b/src/interfaces/IWNativeToken.sol new file mode 100644 index 0000000..f14c931 --- /dev/null +++ b/src/interfaces/IWNativeToken.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title Interface for WNativeToken +interface IWNativeToken is IERC20 { + /// @notice Deposit ether to get wrapped ether + function deposit() external payable; + + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external; +} \ No newline at end of file From 705e75557ab51fc0b5303374dfffdb2bb507d44e Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Mon, 16 Feb 2026 17:05:08 +0330 Subject: [PATCH 2/7] fix(BETHToETH): fallback added to fix withdraw error --- src/BETHToETH.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BETHToETH.sol b/src/BETHToETH.sol index 983f7e4..44d3977 100644 --- a/src/BETHToETH.sol +++ b/src/BETHToETH.sol @@ -44,4 +44,6 @@ contract BETHToETH{ (bool success, ) = _recipient.call{value: amountOut}(""); require(success, "ETH transfer failed"); } + + fallback() external payable {} } From 274b8f68d40d73e4e3606f3a781f6a01e0d3b580 Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Mon, 16 Feb 2026 17:05:51 +0330 Subject: [PATCH 3/7] feat: deploy BETHToETH script added --- script/DeployBETHToETH.s.sol | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 script/DeployBETHToETH.s.sol diff --git a/script/DeployBETHToETH.s.sol b/script/DeployBETHToETH.s.sol new file mode 100644 index 0000000..b5b306c --- /dev/null +++ b/script/DeployBETHToETH.s.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "forge-std/Script.sol"; +import "../src/BETHToETH.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IWNativeToken } from "src/interfaces/IWNativeToken.sol"; + +contract DeployBETHToETH is Script { + // mainnet addresses + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant BETH = 0x5624344235607940d4d4EE76Bf8817d403EB9Cf8; + + function run() external { + vm.startBroadcast(); + + BETHToETH bethToEth = new BETHToETH(IERC20(BETH),IWNativeToken(WETH)); + + console.log("BETHToETH deployed to:", address(bethToEth)); + console.log("BETH address:", BETH); + console.log("WETH address:", WETH); + + vm.stopBroadcast(); + } +} \ No newline at end of file From 7356c741c0dfd2ea3974e95878983fc88015cda0 Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Mon, 16 Feb 2026 17:33:25 +0330 Subject: [PATCH 4/7] ref: CypherEth hook contract moved to /hooks/cypher-eth --- script/DeployBETHToETH.s.sol | 4 ++-- src/{ => hooks/cypher-eth}/BETHToETH.sol | 4 ++-- src/{interfaces => hooks/cypher-eth}/ISwapRouter.sol | 0 src/{interfaces => hooks/cypher-eth}/IWNativeToken.sol | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename src/{ => hooks/cypher-eth}/BETHToETH.sol (92%) rename src/{interfaces => hooks/cypher-eth}/ISwapRouter.sol (100%) rename src/{interfaces => hooks/cypher-eth}/IWNativeToken.sol (100%) diff --git a/script/DeployBETHToETH.s.sol b/script/DeployBETHToETH.s.sol index b5b306c..ebf50f2 100644 --- a/script/DeployBETHToETH.s.sol +++ b/script/DeployBETHToETH.s.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; import "forge-std/Script.sol"; -import "../src/BETHToETH.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IWNativeToken } from "src/interfaces/IWNativeToken.sol"; +import "src/hooks/cypher-eth/BETHToETH.sol"; +import { IWNativeToken } from "src/hooks/cypher-eth/IWNativeToken.sol"; contract DeployBETHToETH is Script { // mainnet addresses diff --git a/src/BETHToETH.sol b/src/hooks/cypher-eth/BETHToETH.sol similarity index 92% rename from src/BETHToETH.sol rename to src/hooks/cypher-eth/BETHToETH.sol index 44d3977..242564c 100644 --- a/src/BETHToETH.sol +++ b/src/hooks/cypher-eth/BETHToETH.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.13; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IWNativeToken } from "src/interfaces/IWNativeToken.sol"; -import { ISwapRouter } from "src/interfaces/ISwapRouter.sol"; +import { IWNativeToken } from "src/hooks/cypher-eth/IWNativeToken.sol"; +import { ISwapRouter } from "src/hooks/cypher-eth/ISwapRouter.sol"; contract BETHToETH{ diff --git a/src/interfaces/ISwapRouter.sol b/src/hooks/cypher-eth/ISwapRouter.sol similarity index 100% rename from src/interfaces/ISwapRouter.sol rename to src/hooks/cypher-eth/ISwapRouter.sol diff --git a/src/interfaces/IWNativeToken.sol b/src/hooks/cypher-eth/IWNativeToken.sol similarity index 100% rename from src/interfaces/IWNativeToken.sol rename to src/hooks/cypher-eth/IWNativeToken.sol From 9f85bbbf469e252257c3336a60690875d4e96231 Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Mon, 16 Feb 2026 17:34:55 +0330 Subject: [PATCH 5/7] ref: forge fmt --- src/hooks/cypher-eth/BETHToETH.sol | 41 ++++++++++++++------------ src/hooks/cypher-eth/ISwapRouter.sol | 6 ++-- src/hooks/cypher-eth/IWNativeToken.sol | 2 +- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/hooks/cypher-eth/BETHToETH.sol b/src/hooks/cypher-eth/BETHToETH.sol index 242564c..4786291 100644 --- a/src/hooks/cypher-eth/BETHToETH.sol +++ b/src/hooks/cypher-eth/BETHToETH.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IWNativeToken } from "src/hooks/cypher-eth/IWNativeToken.sol"; -import { ISwapRouter } from "src/hooks/cypher-eth/ISwapRouter.sol"; - -contract BETHToETH{ +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IWNativeToken} from "src/hooks/cypher-eth/IWNativeToken.sol"; +import {ISwapRouter} from "src/hooks/cypher-eth/ISwapRouter.sol"; +contract BETHToETH { IERC20 public immutable bethContract; IWNativeToken public immutable wethContract; @@ -17,31 +16,35 @@ contract BETHToETH{ wethContract = _wethContract; } - function swapBethWithEth(uint256 _swapAmount, address _recipient, ISwapRouter _swapRouter) public returns (uint256 amountOut){ + function swapBethWithEth(uint256 _swapAmount, address _recipient, ISwapRouter _swapRouter) + public + returns (uint256 amountOut) + { require(_swapAmount > 0, "Amount must be greater than 0"); require(_recipient != address(0), "Invalid recipient"); require( - bethContract.transferFrom(msg.sender, address(this), _swapAmount), - "error while transferFrom beth to this" + bethContract.transferFrom(msg.sender, address(this), _swapAmount), "error while transferFrom beth to this" ); bethContract.approve(address(_swapRouter), _swapAmount); - amountOut = _swapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({ - tokenIn: address(bethContract), - tokenOut: address(wethContract), - deployer: address(0), - recipient: address(this), - deadline: block.timestamp + 15 minutes, - amountIn: _swapAmount, - amountOutMinimum: 0, - limitSqrtPrice: 0 - })); + amountOut = _swapRouter.exactInputSingle( + ISwapRouter.ExactInputSingleParams({ + tokenIn: address(bethContract), + tokenOut: address(wethContract), + deployer: address(0), + recipient: address(this), + deadline: block.timestamp + 15 minutes, + amountIn: _swapAmount, + amountOutMinimum: 0, + limitSqrtPrice: 0 + }) + ); bethContract.approve(address(_swapRouter), 0); // extra safety wethContract.withdraw(amountOut); - (bool success, ) = _recipient.call{value: amountOut}(""); + (bool success,) = _recipient.call{value: amountOut}(""); require(success, "ETH transfer failed"); } diff --git a/src/hooks/cypher-eth/ISwapRouter.sol b/src/hooks/cypher-eth/ISwapRouter.sol index 5cd92e1..fa82b97 100644 --- a/src/hooks/cypher-eth/ISwapRouter.sol +++ b/src/hooks/cypher-eth/ISwapRouter.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; - -interface ISwapRouter{ + +interface ISwapRouter { struct ExactInputSingleParams { address tokenIn; address tokenOut; @@ -14,4 +14,4 @@ interface ISwapRouter{ } function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); -} \ No newline at end of file +} diff --git a/src/hooks/cypher-eth/IWNativeToken.sol b/src/hooks/cypher-eth/IWNativeToken.sol index f14c931..7225a73 100644 --- a/src/hooks/cypher-eth/IWNativeToken.sol +++ b/src/hooks/cypher-eth/IWNativeToken.sol @@ -10,4 +10,4 @@ interface IWNativeToken is IERC20 { /// @notice Withdraw wrapped ether to get ether function withdraw(uint256) external; -} \ No newline at end of file +} From 7161cfb362b4fdf8358e090e3a0c6e763051aeef Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Mon, 16 Feb 2026 17:39:43 +0330 Subject: [PATCH 6/7] ref: forge fmt on deploy script --- script/DeployBETHToETH.s.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/script/DeployBETHToETH.s.sol b/script/DeployBETHToETH.s.sol index ebf50f2..444b3ff 100644 --- a/script/DeployBETHToETH.s.sol +++ b/script/DeployBETHToETH.s.sol @@ -2,24 +2,24 @@ pragma solidity ^0.8.19; import "forge-std/Script.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "src/hooks/cypher-eth/BETHToETH.sol"; -import { IWNativeToken } from "src/hooks/cypher-eth/IWNativeToken.sol"; +import {IWNativeToken} from "src/hooks/cypher-eth/IWNativeToken.sol"; contract DeployBETHToETH is Script { // mainnet addresses address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant BETH = 0x5624344235607940d4d4EE76Bf8817d403EB9Cf8; - + function run() external { vm.startBroadcast(); - - BETHToETH bethToEth = new BETHToETH(IERC20(BETH),IWNativeToken(WETH)); - + + BETHToETH bethToEth = new BETHToETH(IERC20(BETH), IWNativeToken(WETH)); + console.log("BETHToETH deployed to:", address(bethToEth)); console.log("BETH address:", BETH); console.log("WETH address:", WETH); - + vm.stopBroadcast(); } -} \ No newline at end of file +} From 56ba84405c456b2562814cb5c22e3d63f3f7f6c7 Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Mon, 16 Feb 2026 18:52:03 +0330 Subject: [PATCH 7/7] ref(BETHToETH): SwapRouter set move to constructor --- script/DeployBETHToETH.s.sol | 3 ++- src/hooks/cypher-eth/BETHToETH.sol | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/script/DeployBETHToETH.s.sol b/script/DeployBETHToETH.s.sol index 444b3ff..8377910 100644 --- a/script/DeployBETHToETH.s.sol +++ b/script/DeployBETHToETH.s.sol @@ -10,11 +10,12 @@ contract DeployBETHToETH is Script { // mainnet addresses address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant BETH = 0x5624344235607940d4d4EE76Bf8817d403EB9Cf8; + address constant swapRouter = 0x20C5893f69F635f55b0367C519F3f95e59c0b0Ab; function run() external { vm.startBroadcast(); - BETHToETH bethToEth = new BETHToETH(IERC20(BETH), IWNativeToken(WETH)); + BETHToETH bethToEth = new BETHToETH(IERC20(BETH), IWNativeToken(WETH), ISwapRouter(swapRouter)); console.log("BETHToETH deployed to:", address(bethToEth)); console.log("BETH address:", BETH); diff --git a/src/hooks/cypher-eth/BETHToETH.sol b/src/hooks/cypher-eth/BETHToETH.sol index 4786291..0951a0a 100644 --- a/src/hooks/cypher-eth/BETHToETH.sol +++ b/src/hooks/cypher-eth/BETHToETH.sol @@ -8,18 +8,18 @@ import {ISwapRouter} from "src/hooks/cypher-eth/ISwapRouter.sol"; contract BETHToETH { IERC20 public immutable bethContract; IWNativeToken public immutable wethContract; + ISwapRouter public immutable swapRouterContract; - constructor(IERC20 _bethContract, IWNativeToken _wethContract) { + constructor(IERC20 _bethContract, IWNativeToken _wethContract, ISwapRouter _swapRouterContract) { require(address(_bethContract) != address(0), "Invalid BETH address"); require(address(_wethContract) != address(0), "Invalid WETH address"); + require(address(_swapRouterContract) != address(0), "Invalid WETH address"); bethContract = _bethContract; wethContract = _wethContract; + swapRouterContract = _swapRouterContract; } - function swapBethWithEth(uint256 _swapAmount, address _recipient, ISwapRouter _swapRouter) - public - returns (uint256 amountOut) - { + function swapBethWithEth(uint256 _swapAmount, address _recipient) public returns (uint256 amountOut) { require(_swapAmount > 0, "Amount must be greater than 0"); require(_recipient != address(0), "Invalid recipient"); @@ -27,9 +27,9 @@ contract BETHToETH { bethContract.transferFrom(msg.sender, address(this), _swapAmount), "error while transferFrom beth to this" ); - bethContract.approve(address(_swapRouter), _swapAmount); + bethContract.approve(address(swapRouterContract), _swapAmount); - amountOut = _swapRouter.exactInputSingle( + amountOut = swapRouterContract.exactInputSingle( ISwapRouter.ExactInputSingleParams({ tokenIn: address(bethContract), tokenOut: address(wethContract), @@ -42,7 +42,7 @@ contract BETHToETH { }) ); - bethContract.approve(address(_swapRouter), 0); // extra safety + bethContract.approve(address(swapRouterContract), 0); // extra safety wethContract.withdraw(amountOut); (bool success,) = _recipient.call{value: amountOut}(""); require(success, "ETH transfer failed");