SlvrLiquidityZap

0x85b10820f5b7ef2bbf9f5b59da64860dd6bfb9f0

Verification
Verified
v0.8.33+commit.64118f21
Type
Contract
5,445 bytes
ABI entries
19
4 read · 2 write
License
none

Contract information

Address
0x85b10820f5b7ef2bbf9f5b59da64860dd6bfb9f0
Chain
Robinhood Chain (4663)
Compiler
v0.8.33+commit.64118f21
Optimization
Enabled
Creation tx
0xbd171d576d…f9d898abda

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (4)

LIQUIDITY_STAKING()address
PAIR()address
SLVR_TOKEN()address
WETH()address

Events (2)

LiquidityAddedLiquidityRemoved

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "slvrToken_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pair_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "liquidityStaking_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "BadSwapAmount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Expired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientOut",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoLiquidity",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "currentAllowance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "requestedDecrease",
        "type": "uint256"
      }
    ],
    "name": "SafeERC20FailedDecreaseAllowance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Slippage",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "user",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "slvrAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "liquidity",
        "type": "uint256"
      }
    ],
    "name": "LiquidityAdded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "user",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "liquidity",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "slvrAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "ethAmount",
        "type": "uint256"
      }
    ],
    "name": "LiquidityRemoved",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "LIQUIDITY_STAKING",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PAIR",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "SLVR_TOKEN",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WETH",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "minLiquidity",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "addLiquidityWithEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "liquidity",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amountToken",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amountETH",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "liquidity",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amountTokenMin",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amountETHMin",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      }
    ],
    "name": "removeLiquidityAndZapOut",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amountToken",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amountETH",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IUniswapV2Pair} from "@uniswap/v2-core/interfaces/IUniswapV2Pair.sol";

/// @notice Minimal SlvrLiquidityStaking interface
interface ISlvrLiquidityStaking {
    function depositFor(address user, uint256 amount) external;
    function withdraw(uint256 amount) external;
}

/// @notice WETH interface
interface IWETH {
    function deposit() external payable;
    function withdraw(uint256 amount) external;
    function transfer(address to, uint256 value) external returns (bool);
}

/// @notice SlvrToken's permissionless tax-swapback entrypoint
interface ISlvrTokenSwapback {
    function executeSwapback(uint256 amountOutMin, uint256 deadline) external;
    function swapbackMinOut() external view returns (uint256);
}

/// @title SlvrLiquidityZap
/// @notice Direct pair liquidity zap - swaps and mints directly on the pair
/// @dev Works directly with UniswapV2 pair, no router needed
/// @dev Automatically stakes LP tokens to liquidity staking on behalf of users
contract SlvrLiquidityZap is ReentrancyGuard {
    using SafeERC20 for IERC20;

    IERC20 public immutable SLVR_TOKEN;
    address public immutable WETH;
    address public immutable PAIR;
    address public immutable LIQUIDITY_STAKING;

    error Expired();
    error ZeroAmount();
    error NoLiquidity();
    error BadSwapAmount();
    error InsufficientOut();
    error Slippage();

    event LiquidityAdded(
        address indexed user,
        uint256 slvrAmount,
        uint256 ethAmount,
        uint256 liquidity
    );

    event LiquidityRemoved(
        address indexed user,
        uint256 liquidity,
        uint256 slvrAmount,
        uint256 ethAmount
    );

    constructor(address slvrToken_, address weth_, address pair_, address liquidityStaking_) {
        require(slvrToken_ != address(0), "slvrToken=0");
        require(weth_ != address(0), "weth=0");
        require(pair_ != address(0), "pair=0");
        require(liquidityStaking_ != address(0), "liquidityStaking=0");
        
        SLVR_TOKEN = IERC20(slvrToken_);
        WETH = weth_;
        PAIR = pair_;
        LIQUIDITY_STAKING = liquidityStaking_;
    }

    /// @notice Zap ETH into SLVR/ETH LP tokens by swapping directly against the pair
    /// @dev 1. Wraps ETH to WETH, 2. Optimal swap WETH->SLVR, 3. Direct mint on pair, 4. Stake LP
    /// @param minLiquidity Minimum LP tokens to receive (slippage protection)
    /// @param deadline Transaction deadline
    /// @return liquidity LP tokens received and staked
    /// @return amountToken Actual SLVR tokens used
    /// @return amountETH Actual WETH used
    function addLiquidityWithEth(
        uint256 minLiquidity,
        uint256 deadline
    ) external payable nonReentrant returns (uint256 liquidity, uint256 amountToken, uint256 amountETH) {
        if (block.timestamp > deadline) revert Expired();
        if (msg.value == 0) revert ZeroAmount();

        // Flush any pending tax swapback BEFORE reading reserves. SLVR fires an automatic
        // swapback inside any transfer TO the pair; if it fired during our SLVR deposit
        // below, its router swap would sync our already-deposited WETH into reserves and
        // pair.mint() would see a zero WETH delta (INSUFFICIENT_LIQUIDITY_MINTED). Flushing
        // here updates lastSwapbackTime so the interval gate blocks a re-fire in this tx;
        // if the flush reverts (below threshold / too soon) the mid-transfer trigger is
        // equally gated. Reserves are read after, so the math sees the post-flush state.
        // Passing swapbackMinOut keeps the flush exactly as strict as the automatic path.
        ISlvrTokenSwapback slvr = ISlvrTokenSwapback(address(SLVR_TOKEN));
        try slvr.executeSwapback(slvr.swapbackMinOut(), block.timestamp) {} catch {}

        IUniswapV2Pair pair = IUniswapV2Pair(PAIR);
        
        // Get pair token addresses
        address token0 = pair.token0();
        address token1 = pair.token1();
        
        // Determine which token is which
        bool slvrIsToken0 = (token0 == address(SLVR_TOKEN));
        
        // Get reserves
        (uint112 r0, uint112 r1,) = pair.getReserves();
        if (r0 == 0 || r1 == 0) revert NoLiquidity();
        
        uint256 reserveIn = slvrIsToken0 ? uint256(r1) : uint256(r0);  // WETH reserve
        uint256 reserveOut = slvrIsToken0 ? uint256(r0) : uint256(r1); // SLVR reserve
        
        // Wrap ETH to WETH
        IWETH weth = IWETH(WETH);
        weth.deposit{value: msg.value}();
        
        // Pull WETH to this contract (already here from deposit, but for clarity)
        uint256 amountIn = msg.value;
        
        // Calculate optimal swap amount
        uint256 swapAmountIn = _calculateOptimalSwapIn(reserveIn, amountIn);
        if (swapAmountIn == 0 || swapAmountIn >= amountIn) revert BadSwapAmount();
        
        // Transfer swap amount to pair
        IERC20(WETH).safeTransfer(PAIR, swapAmountIn);
        
        // Measure actual input (FoT-tolerant)
        uint256 balanceInAfter = IERC20(WETH).balanceOf(PAIR);
        uint256 actualIn = balanceInAfter - reserveIn;
        if (actualIn == 0) revert BadSwapAmount();
        
        // Compute output
        uint256 amountOut = _getAmountOut(actualIn, reserveIn, reserveOut);
        if (amountOut == 0) revert InsufficientOut();
        
        // Perform swap: WETH -> SLVR, output to this contract
        if (slvrIsToken0) {
            // WETH is token1, want SLVR (token0) out
            pair.swap(amountOut, 0, address(this), new bytes(0));
        } else {
            // WETH is token0, want SLVR (token1) out
            pair.swap(0, amountOut, address(this), new bytes(0));
        }
        
        // Read reserves again (post-swap)
        (r0, r1,) = pair.getReserves();
        
        // Get balances of both tokens in this contract
        uint256 bal0 = IERC20(token0).balanceOf(address(this));
        uint256 bal1 = IERC20(token1).balanceOf(address(this));
        
        // Calculate optimal deposit amounts
        (uint256 amount0Used, uint256 amount1Used) = _calcOptimalDepositAmounts(
            bal0,
            bal1,
            uint256(r0),
            uint256(r1)
        );
        
        // Transfer SLVR to the pair BEFORE WETH: the SLVR transfer can trigger the token's
        // automatic swapback (a swap + sync on this pair), which would absorb any balance
        // already parked in the pair but not yet minted. With SLVR first, a mid-transfer
        // swapback runs against a clean pair — our SLVR is credited after the trigger and
        // our WETH is not deposited yet — so mint() still sees both deltas.
        (uint256 slvrUsed, uint256 wethUsed) =
            slvrIsToken0 ? (amount0Used, amount1Used) : (amount1Used, amount0Used);
        if (slvrUsed > 0) SLVR_TOKEN.safeTransfer(PAIR, slvrUsed);
        if (wethUsed > 0) IERC20(WETH).safeTransfer(PAIR, wethUsed);
        
        // Mint LP tokens directly from pair
        liquidity = pair.mint(address(this));
        if (liquidity < minLiquidity) revert Slippage();
        
        // Map amounts to SLVR/WETH for return values
        if (slvrIsToken0) {
            amountToken = amount0Used;
            amountETH = amount1Used;
        } else {
            amountToken = amount1Used;
            amountETH = amount0Used;
        }
        
        // Refund any dust
        uint256 dust0 = IERC20(token0).balanceOf(address(this));
        uint256 dust1 = IERC20(token1).balanceOf(address(this));
        if (dust0 > 0) IERC20(token0).safeTransfer(msg.sender, dust0);
        if (dust1 > 0) IERC20(token1).safeTransfer(msg.sender, dust1);
        
        // Approve and stake LP tokens
        IERC20 lpToken = IERC20(PAIR);
        uint256 currentAllowance = lpToken.allowance(address(this), LIQUIDITY_STAKING);
        if (currentAllowance < liquidity) {
            if (currentAllowance != 0) {
                lpToken.safeDecreaseAllowance(LIQUIDITY_STAKING, currentAllowance);
            }
            lpToken.safeIncreaseAllowance(LIQUIDITY_STAKING, liquidity);
        }
        
        // Deposit LP tokens to liquidity staking on behalf of the user
        ISlvrLiquidityStaking(LIQUIDITY_STAKING).depositFor(msg.sender, liquidity);
        
        emit LiquidityAdded(msg.sender, amountToken, amountETH, liquidity);
    }
    
    /// @dev Optimal swap amount for UniswapV2 0.3% fee (997/1000)
    /// swapIn = (sqrt(r^2 * 3988009 + a * r * 3988000) - r * 1997) / 1994
    function _calculateOptimalSwapIn(
        uint256 reserveIn,
        uint256 amountIn
    ) internal pure returns (uint256) {
        if (reserveIn == 0 || amountIn == 0) return 0;
        
        uint256 r = reserveIn;
        uint256 a = amountIn;
        
        // r^2 * 3988009
        uint256 part1 = r * r * 3988009;
        // a * r * 3988000
        uint256 part2 = a * r * 3988000;
        
        uint256 numerator = _sqrt(part1 + part2) - (r * 1997);
        return numerator / 1994;
    }
    
    /// @dev UniswapV2 getAmountOut with 0.3% fee
    function _getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256) {
        require(amountIn > 0, "INSUFFICIENT_INPUT");
        require(reserveIn > 0 && reserveOut > 0, "INSUFFICIENT_LIQUIDITY");
        
        uint256 amountInWithFee = amountIn * 997;
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = reserveIn * 1000 + amountInWithFee;
        return numerator / denominator;
    }
    
    /// @dev Calculate optimal deposit amounts matching reserve ratio
    function _calcOptimalDepositAmounts(
        uint256 bal0,
        uint256 bal1,
        uint256 r0,
        uint256 r1
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        if (bal0 == 0 || bal1 == 0 || r0 == 0 || r1 == 0) {
            return (0, 0);
        }
        
        // Compare bal0/bal1 vs r0/r1
        uint256 ideal1 = (bal0 * r1) / r0;
        
        if (ideal1 <= bal1) {
            // token1 is limiting
            amount0 = bal0;
            amount1 = ideal1;
        } else {
            // token0 is limiting
            uint256 ideal0 = (bal1 * r0) / r1;
            amount0 = ideal0;
            amount1 = bal1;
        }
    }
    
    /// @dev Babylonian sqrt
    function _sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y == 0) return 0;
        uint256 x = y / 2 + 1;
        z = y;
        while (x < z) {
            z = x;
            x = (y / x + x) / 2;
        }
    }

    /// @notice Remove liquidity and convert back to ETH + SLVR (direct pair burn)
    /// @dev User must first withdraw LP tokens from staking, then approve this contract
    /// @dev Burns LP directly on pair and returns SLVR + WETH (unwrapped to ETH)
    /// @param liquidity Amount of LP tokens to remove
    /// @param amountTokenMin Minimum SLVR tokens (slippage protection)
    /// @param amountETHMin Minimum ETH (slippage protection)
    /// @param deadline Transaction deadline
    /// @return amountToken Actual SLVR tokens returned
    /// @return amountETH Actual ETH returned
    function removeLiquidityAndZapOut(
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        uint256 deadline
    ) external nonReentrant returns (uint256 amountToken, uint256 amountETH) {
        if (block.timestamp > deadline) revert Expired();
        if (liquidity == 0) revert ZeroAmount();

        // Transfer LP tokens from user to this contract
        IERC20 lpToken = IERC20(PAIR);
        lpToken.safeTransferFrom(msg.sender, address(this), liquidity);

        // Transfer LP tokens to pair for burning
        lpToken.safeTransfer(PAIR, liquidity);

        IUniswapV2Pair pair = IUniswapV2Pair(PAIR);
        
        // Get token addresses
        address token0 = pair.token0();
        bool slvrIsToken0 = (token0 == address(SLVR_TOKEN));
        
        // Burn LP tokens directly on pair - returns tokens to this contract
        (uint256 amount0, uint256 amount1) = pair.burn(address(this));
        
        // Map amounts to SLVR/WETH
        if (slvrIsToken0) {
            amountToken = amount0;
            amountETH = amount1;
        } else {
            amountToken = amount1;
            amountETH = amount0;
        }
        
        // Slippage checks
        if (amountToken < amountTokenMin) revert Slippage();
        if (amountETH < amountETHMin) revert Slippage();
        
        // Unwrap WETH to ETH
        IWETH weth = IWETH(WETH);
        weth.withdraw(amountETH);
        
        // Transfer SLVR tokens to user
        if (amountToken > 0) {
            SLVR_TOKEN.safeTransfer(msg.sender, amountToken);
        }
        
        // Transfer ETH to user
        if (amountETH > 0) {
            (bool success,) = payable(msg.sender).call{value: amountETH}("");
            require(success, "ETH transfer failed");
        }
        
        emit LiquidityRemoved(msg.sender, liquidity, amountToken, amountETH);
    }

    /// @notice Allow contract to receive ETH
    receive() external payable {}
}
Chain explorer3603msChain node75ms