RainSwapper
0xd5adc179b5f9a1e2ab2e398b84881c71918c0529
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
4,378 bytes
ABI entries
23
4 read · 4 write
License
none
Contract information
- Address
- 0xd5adc179b5f9a1e2ab2e398b84881c71918c0529
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x2ABcA1C7aD…6d70230e60
- Creation tx
- 0x52c687e7c5…b788b16796
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (4)
dexFactory() → address
feeTier() → uint24
poolOf(address) → address
weth() → address
Events (2)
SwapBuySwapSell
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "uint24",
"name": "feeTier_",
"type": "uint24"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EthTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "Expired",
"type": "error"
},
{
"inputs": [],
"name": "NoPool",
"type": "error"
},
{
"inputs": [],
"name": "NotPool",
"type": "error"
},
{
"inputs": [],
"name": "NotSelf",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "SlippageExceeded",
"type": "error"
},
{
"inputs": [],
"name": "SnipeCapReached",
"type": "error"
},
{
"inputs": [],
"name": "SwapFailed",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAmount",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "buyer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensOut",
"type": "uint256"
}
],
"name": "SwapBuy",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "seller",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethOut",
"type": "uint256"
}
],
"name": "SwapSell",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "minTokensOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "buy",
"outputs": [
{
"internalType": "uint256",
"name": "tokensOut",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "dexFactory",
"outputs": [
{
"internalType": "contract IUniswapV3Factory",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeTier",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "poolOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokensIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minEthOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "sell",
"outputs": [
{
"internalType": "uint256",
"name": "ethOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IUniswapV3Pool",
"name": "pool",
"type": "address"
},
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "swapExactIn",
"outputs": [
{
"internalType": "uint256",
"name": "out",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int256",
"name": "amount0Delta",
"type": "int256"
},
{
"internalType": "int256",
"name": "amount1Delta",
"type": "int256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "uniswapV3SwapCallback",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IWETH9",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {
INonfungiblePositionManager,
IUniswapV3Factory,
IUniswapV3Pool,
IWETH9
} from "./interfaces/IExternal.sol";
import {IRainLaunchTokenViews} from "./interfaces/IRainLaunch.sol";
contract RainSwapper is ReentrancyGuard {
using SafeERC20 for IERC20;
IUniswapV3Factory public immutable dexFactory;
IWETH9 public immutable weth;
uint24 public immutable feeTier;
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
address private _expectedPool;
event SwapBuy(address indexed token, address indexed buyer, uint256 ethIn, uint256 tokensOut);
event SwapSell(address indexed token, address indexed seller, uint256 tokensIn, uint256 ethOut);
error NoPool();
error ZeroAmount();
error Expired();
error SlippageExceeded();
error NotPool();
error NotSelf();
error EthTransferFailed();
error SnipeCapReached();
error SwapFailed();
constructor(address positionManager_, uint24 feeTier_) {
INonfungiblePositionManager npm = INonfungiblePositionManager(positionManager_);
dexFactory = IUniswapV3Factory(npm.factory());
weth = IWETH9(npm.WETH9());
feeTier = feeTier_;
}
function buy(address token, uint256 minTokensOut, uint256 deadline)
external
payable
nonReentrant
returns (uint256 tokensOut)
{
if (block.timestamp > deadline) revert Expired();
if (msg.value == 0) revert ZeroAmount();
IUniswapV3Pool pool = _poolOf(token);
bool tokenIsToken0 = token < address(weth);
weth.deposit{value: msg.value}();
_expectedPool = address(pool);
uint256 wethUsed = msg.value;
try RainSwapper(payable(address(this))).swapExactIn(pool, !tokenIsToken0, msg.value, msg.sender)
returns (uint256 out) {
tokensOut = out;
} catch {
(uint256 allowed, bool capContext) = _snipeAllowance(token, msg.sender);
if (!capContext) {
_expectedPool = address(0);
revert SwapFailed();
}
if (allowed == 0) {
_expectedPool = address(0);
revert SnipeCapReached();
}
(int256 a0, int256 a1) = pool.swap(
msg.sender,
!tokenIsToken0,
-int256(allowed),
tokenIsToken0 ? MAX_SQRT_RATIO - 1 : MIN_SQRT_RATIO + 1,
""
);
tokensOut = uint256(-(tokenIsToken0 ? a0 : a1));
wethUsed = uint256(tokenIsToken0 ? a1 : a0);
}
_expectedPool = address(0);
if (wethUsed == msg.value) {
if (tokensOut < minTokensOut) revert SlippageExceeded();
} else if (tokensOut * msg.value < minTokensOut * wethUsed) {
revert SlippageExceeded();
}
uint256 wethLeft = weth.balanceOf(address(this));
if (wethLeft > 0) {
weth.withdraw(wethLeft);
(bool ok,) = msg.sender.call{value: wethLeft}("");
if (!ok) revert EthTransferFailed();
}
emit SwapBuy(token, msg.sender, msg.value - wethLeft, tokensOut);
}
function swapExactIn(IUniswapV3Pool pool, bool zeroForOne, uint256 amountIn, address recipient)
external
returns (uint256 out)
{
if (msg.sender != address(this)) revert NotSelf();
(int256 a0, int256 a1) = pool.swap(
recipient,
zeroForOne,
int256(amountIn),
zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,
""
);
out = uint256(-(zeroForOne ? a1 : a0));
}
function _snipeAllowance(address token, address to)
internal
view
returns (uint256 allowed, bool capContext)
{
try IRainLaunchTokenViews(token).restrictionEndBlock() returns (uint256 endBlock) {
if (block.number > endBlock) return (0, false);
IRainLaunchTokenViews t = IRainLaunchTokenViews(token);
address key = t.restrictionKeyOf(to);
// Holdings-based room: how much more this wallet may hold before hitting maxWallet.
// No cumulative counter — a wallet that sold back down gets its full room again.
uint256 wallet = t.maxWalletLimit();
uint256 bal = t.balanceOf(key);
return (wallet > bal ? wallet - bal : 0, true);
} catch {
return (0, false);
}
}
function sell(address token, uint256 tokensIn, uint256 minEthOut, uint256 deadline)
external
nonReentrant
returns (uint256 ethOut)
{
if (block.timestamp > deadline) revert Expired();
if (tokensIn == 0 || tokensIn > uint256(type(int256).max)) revert ZeroAmount();
IUniswapV3Pool pool = _poolOf(token);
bool tokenIsToken0 = token < address(weth);
IERC20(token).safeTransferFrom(msg.sender, address(this), tokensIn);
_expectedPool = address(pool);
(int256 a0, int256 a1) = pool.swap(
address(this),
tokenIsToken0,
int256(tokensIn),
tokenIsToken0 ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,
""
);
_expectedPool = address(0);
ethOut = uint256(-(tokenIsToken0 ? a1 : a0));
if (ethOut < minEthOut) revert SlippageExceeded();
uint256 tokenLeft = IERC20(token).balanceOf(address(this));
if (tokenLeft > 0) IERC20(token).safeTransfer(msg.sender, tokenLeft);
weth.withdraw(ethOut);
(bool ok,) = msg.sender.call{value: ethOut}("");
if (!ok) revert EthTransferFailed();
emit SwapSell(token, msg.sender, tokensIn - tokenLeft, ethOut);
}
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {
address pool = _expectedPool;
if (pool == address(0) || msg.sender != pool) revert NotPool();
if (amount0Delta > 0) {
IERC20(IUniswapV3Pool(pool).token0()).safeTransfer(pool, uint256(amount0Delta));
}
if (amount1Delta > 0) {
IERC20(IUniswapV3Pool(pool).token1()).safeTransfer(pool, uint256(amount1Delta));
}
}
function poolOf(address token) external view returns (address) {
return dexFactory.getPool(token, address(weth), feeTier);
}
function _poolOf(address token) internal view returns (IUniswapV3Pool pool) {
address p = dexFactory.getPool(token, address(weth), feeTier);
if (p == address(0)) revert NoPool();
pool = IUniswapV3Pool(p);
}
receive() external payable {
if (msg.sender != address(weth)) revert EthTransferFailed();
}
}
Chain explorer1742msChain node98ms