SeedSwapper

0x338e55edb9e250e4cbbe02729bdf3435e767e134

Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
9,746 bytes
ABI entries
25
11 read · 7 write
License
none

Contract information

Address
0x338e55edb9e250e4cbbe02729bdf3435e767e134
Chain
Robinhood Chain (4663)
Compiler
v0.8.30+commit.73712a01
Optimization
Enabled
Creation tx
0xe90d246e28…db73eba56a

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (11)

aapl()address
feeB()uint24
hooksB()address
manager()address
poolKeyB()tuple
seed()address
tickSpacingB()int24
usdg()address
usdgIsZeroB()bool
v2Router()address
weth()address

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "manager_",
        "type": "address"
      },
      {
        "internalType": "contract IERC20",
        "name": "seed_",
        "type": "address"
      },
      {
        "internalType": "contract IERC20",
        "name": "usdg_",
        "type": "address"
      },
      {
        "internalType": "contract IERC20",
        "name": "aapl_",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "keyB",
        "type": "tuple"
      },
      {
        "internalType": "contract IUniswapV2Router",
        "name": "v2Router_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "EthPathDisabled",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientOutput",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidPoolKey",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotPoolManager",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "aapl",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeB",
    "outputs": [
      {
        "internalType": "uint24",
        "name": "",
        "type": "uint24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hooksB",
    "outputs": [
      {
        "internalType": "contract IHooks",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "manager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolKeyB",
    "outputs": [
      {
        "components": [
          {
            "internalType": "Currency",
            "name": "currency0",
            "type": "address"
          },
          {
            "internalType": "Currency",
            "name": "currency1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "contract IHooks",
            "name": "hooks",
            "type": "address"
          }
        ],
        "internalType": "struct PoolKey",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "seed",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "aaplIn",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "swapAaplForEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "aaplIn",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "swapAaplForSeed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "bought",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "swapEthForAapl",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "bought",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "swapEthForSeed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "bought",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "seedIn",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "swapSeedForAapl",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "bought",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "seedIn",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "swapSeedForEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "ethOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tickSpacingB",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "unlockCallback",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "usdg",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "usdgIsZeroB",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "v2Router",
    "outputs": [
      {
        "internalType": "contract IUniswapV2Router",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

//
//                         ,(
//                        ((
//                         \\
//                     .---\\---.
//                    /    ``    \
//                   |            |
//                   |            |
//                    \          /
//                     '.______.'
//
//              O R C H A R D  ·  $SEED
//             Plant ETH. Harvest $AAPL.
//
//                https://orchard.gold
//            https://x.com/orcharddotgold
//

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {ISwapper} from "./interfaces/ISwapper.sol";
import {IUniswapV2Router} from "./interfaces/IUniswapV2Router.sol";

contract SeedSwapper is ISwapper, IUnlockCallback {
    using SafeERC20 for IERC20;

    error NotPoolManager();
    error InsufficientOutput();
    error InvalidPoolKey();
    error EthPathDisabled();
    error ZeroAddress();

    enum Route {
        UsdgToAapl,
        AaplToUsdg
    }

    IPoolManager public immutable manager;
    IERC20 public immutable seed;
    IERC20 public immutable usdg;
    IERC20 public immutable aapl;
    IUniswapV2Router public immutable v2Router;
    address public immutable weth;

    bool public immutable usdgIsZeroB;
    uint24 public immutable feeB;
    int24 public immutable tickSpacingB;
    IHooks public immutable hooksB;

    constructor(
        IPoolManager manager_,
        IERC20 seed_,
        IERC20 usdg_,
        IERC20 aapl_,
        PoolKey memory keyB,
        IUniswapV2Router v2Router_,
        address weth_
    ) {
        if (
            address(manager_) == address(0) || address(seed_) == address(0) || address(usdg_) == address(0)
                || address(aapl_) == address(0)
        ) revert ZeroAddress();
        if (address(v2Router_) != address(0) && weth_ == address(0)) revert ZeroAddress();
        manager = manager_;
        seed = seed_;
        usdg = usdg_;
        aapl = aapl_;
        v2Router = v2Router_;
        weth = weth_;

        (address b0, address b1) = (Currency.unwrap(keyB.currency0), Currency.unwrap(keyB.currency1));
        if (b0 == address(usdg_) && b1 == address(aapl_)) usdgIsZeroB = true;
        else if (b0 == address(aapl_) && b1 == address(usdg_)) usdgIsZeroB = false;
        else revert InvalidPoolKey();
        feeB = keyB.fee;
        tickSpacingB = keyB.tickSpacing;
        hooksB = keyB.hooks;
    }

    function poolKeyB() public view returns (PoolKey memory) {
        (Currency c0, Currency c1) = usdgIsZeroB
            ? (Currency.wrap(address(usdg)), Currency.wrap(address(aapl)))
            : (Currency.wrap(address(aapl)), Currency.wrap(address(usdg)));
        return PoolKey({currency0: c0, currency1: c1, fee: feeB, tickSpacing: tickSpacingB, hooks: hooksB});
    }

    function swapAaplForSeed(uint256 aaplIn, uint256 minOut) external returns (uint256 bought) {
        if (address(v2Router) == address(0)) revert EthPathDisabled();
        aapl.safeTransferFrom(msg.sender, address(this), aaplIn);
        uint256 usdgOut = _unlock(Route.AaplToUsdg, aaplIn, 0, address(this));

        address[] memory path = new address[](3);
        path[0] = address(usdg);
        path[1] = weth;
        path[2] = address(seed);
        usdg.forceApprove(address(v2Router), usdgOut);
        uint256 balanceBefore = seed.balanceOf(msg.sender);
        v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(usdgOut, 0, path, msg.sender, block.timestamp);
        bought = seed.balanceOf(msg.sender) - balanceBefore;
        if (bought < minOut) revert InsufficientOutput();
    }

    function swapAaplForEth(uint256 aaplIn, uint256 minOut) external returns (uint256 ethOut) {
        if (address(v2Router) == address(0)) revert EthPathDisabled();
        aapl.safeTransferFrom(msg.sender, address(this), aaplIn);
        uint256 usdgOut = _unlock(Route.AaplToUsdg, aaplIn, 0, address(this));

        address[] memory path = new address[](2);
        path[0] = address(usdg);
        path[1] = weth;
        usdg.forceApprove(address(v2Router), usdgOut);
        uint256 balanceBefore = msg.sender.balance;
        v2Router.swapExactTokensForETH(usdgOut, minOut, path, msg.sender, block.timestamp);
        ethOut = msg.sender.balance - balanceBefore;
        if (ethOut < minOut) revert InsufficientOutput();
    }

    function swapSeedForEth(uint256 seedIn, uint256 minOut) external returns (uint256 ethOut) {
        if (address(v2Router) == address(0)) revert EthPathDisabled();
        uint256 seedBefore = seed.balanceOf(address(this));
        seed.safeTransferFrom(msg.sender, address(this), seedIn);
        uint256 pulled = seed.balanceOf(address(this)) - seedBefore;

        address[] memory path = new address[](2);
        path[0] = address(seed);
        path[1] = weth;
        seed.forceApprove(address(v2Router), pulled);
        uint256 balanceBefore = msg.sender.balance;
        v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(pulled, 0, path, msg.sender, block.timestamp);
        ethOut = msg.sender.balance - balanceBefore;
        if (ethOut < minOut) revert InsufficientOutput();
    }

    function swapSeedForAapl(uint256 seedIn, uint256 minOut) external returns (uint256 bought) {
        if (address(v2Router) == address(0)) revert EthPathDisabled();
        uint256 seedBefore = seed.balanceOf(address(this));
        seed.safeTransferFrom(msg.sender, address(this), seedIn);
        uint256 pulled = seed.balanceOf(address(this)) - seedBefore;

        address[] memory path = new address[](3);
        path[0] = address(seed);
        path[1] = weth;
        path[2] = address(usdg);
        seed.forceApprove(address(v2Router), pulled);
        uint256 usdgBefore = usdg.balanceOf(address(this));
        v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(pulled, 0, path, address(this), block.timestamp);
        uint256 usdgOut = usdg.balanceOf(address(this)) - usdgBefore;

        bought = _unlock(Route.UsdgToAapl, usdgOut, minOut, msg.sender);
    }

    function swapEthForAapl(uint256 minOut) external payable returns (uint256 bought) {
        bought = _unlock(Route.UsdgToAapl, _ethToUsdg(), minOut, msg.sender);
    }

    function swapEthForSeed(uint256 minOut) external payable returns (uint256 bought) {
        if (address(v2Router) == address(0)) revert EthPathDisabled();
        address[] memory path = new address[](2);
        path[0] = weth;
        path[1] = address(seed);
        uint256 balanceBefore = seed.balanceOf(msg.sender);
        v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{
            value: msg.value
        }(0, path, msg.sender, block.timestamp);
        bought = seed.balanceOf(msg.sender) - balanceBefore;
        if (bought < minOut) revert InsufficientOutput();
    }

    function _ethToUsdg() private returns (uint256 usdgIn) {
        if (address(v2Router) == address(0)) revert EthPathDisabled();
        address[] memory path = new address[](2);
        path[0] = weth;
        path[1] = address(usdg);
        uint256 usdgBefore = usdg.balanceOf(address(this));
        v2Router.swapExactETHForTokens{value: msg.value}(0, path, address(this), block.timestamp);
        usdgIn = usdg.balanceOf(address(this)) - usdgBefore;
    }

    function _unlock(Route route, uint256 amountIn, uint256 minOut, address recipient)
        private
        returns (uint256 bought)
    {
        bought = abi.decode(manager.unlock(abi.encode(route, amountIn, minOut, recipient)), (uint256));
    }

    function unlockCallback(bytes calldata data) external returns (bytes memory) {
        if (msg.sender != address(manager)) revert NotPoolManager();
        (Route route, uint256 amountIn, uint256 minOut, address recipient) =
            abi.decode(data, (Route, uint256, uint256, address));

        IERC20 tokenIn;
        IERC20 tokenOut;
        bool zeroForOne;
        if (route == Route.UsdgToAapl) {
            (tokenIn, tokenOut, zeroForOne) = (usdg, aapl, usdgIsZeroB);
        } else {
            (tokenIn, tokenOut, zeroForOne) = (aapl, usdg, !usdgIsZeroB);
        }
        uint256 bought = _exactIn(poolKeyB(), zeroForOne, amountIn);
        if (bought < minOut) revert InsufficientOutput();

        manager.sync(Currency.wrap(address(tokenIn)));
        tokenIn.safeTransfer(address(manager), amountIn);
        manager.settle();
        manager.take(Currency.wrap(address(tokenOut)), recipient, bought);
        return abi.encode(bought);
    }

    function _exactIn(PoolKey memory key, bool zeroForOne, uint256 amountIn) private returns (uint256 amountOut) {
        BalanceDelta delta = manager.swap(
            key,
            IPoolManager.SwapParams({
                zeroForOne: zeroForOne,
                // forge-lint: disable-next-line(unsafe-typecast)
                amountSpecified: -int256(amountIn),
                sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1
            }),
            ""
        );
        amountOut = uint256(uint128(zeroForOne ? delta.amount1() : delta.amount0()));
    }
}
Chain explorer3469msChain node86ms