CurveRouterV2

0xa08278b06f6ed56d834df94f5505497bc2a899a1

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
3,867 bytes
ABI entries
9
1 read · 3 write
License
none

Contract information

Address
0xa08278b06f6ed56d834df94f5505497bc2a899a1
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xc5386868c0…c61688a027

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (1)

poolManager()address

ABI

[
  {
    "inputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "_pm",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "OnlyPoolManager",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SlippageExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "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": "key",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "buy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "out",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "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": "key",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "amountIn",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "sell",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "out",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "raw",
        "type": "bytes"
      }
    ],
    "name": "unlockCallback",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {SwapParams} from "v4-core/src/types/PoolOperation.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {CurrencySettler} from "./CurrencySettler.sol";
import {CurveToken} from "./CurveToken.sol";

/// @notice Standard settle-AFTER swap entry for the v2 protected curve. Because
/// v2 uses normal v4 claims accounting, this is an ordinary router — no
/// settle-before hack — and any standard v4 router (incl. the Universal Router)
/// would work identically. Exact-in only: buy with ETH, sell a token amount.
contract CurveRouterV2 is IUnlockCallback {
    using CurrencySettler for Currency;

    IPoolManager public immutable poolManager;

    error OnlyPoolManager();
    error SlippageExceeded();
    error ZeroAmount();

    struct SwapData {
        PoolKey key;
        bool isBuy;
        uint256 amountIn;
        uint256 minOut;
        address user;
    }

    constructor(IPoolManager _pm) {
        poolManager = _pm;
    }

    receive() external payable {}

    function buy(PoolKey calldata key, uint256 minOut) external payable returns (uint256 out) {
        if (msg.value == 0) revert ZeroAmount();
        out = abi.decode(
            poolManager.unlock(abi.encode(SwapData(key, true, msg.value, minOut, msg.sender))), (uint256)
        );
    }

    function sell(PoolKey calldata key, uint256 amountIn, uint256 minOut) external returns (uint256 out) {
        if (amountIn == 0) revert ZeroAmount();
        CurveToken(Currency.unwrap(key.currency1)).transferFrom(msg.sender, address(this), amountIn);
        out = abi.decode(
            poolManager.unlock(abi.encode(SwapData(key, false, amountIn, minOut, msg.sender))), (uint256)
        );
    }

    function unlockCallback(bytes calldata raw) external returns (bytes memory) {
        if (msg.sender != address(poolManager)) revert OnlyPoolManager();
        SwapData memory d = abi.decode(raw, (SwapData));

        // Standard v4 order: swap first, then resolve deltas.
        BalanceDelta delta = poolManager.swap(
            d.key,
            SwapParams({
                zeroForOne: d.isBuy,
                amountSpecified: -int256(d.amountIn),
                sqrtPriceLimitX96: d.isBuy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1
            }),
            abi.encode(d.user)
        );

        uint256 out;
        if (d.isBuy) {
            // pay ETH input, take token output to the user
            d.key.currency0.settle(poolManager, address(this), d.amountIn, false);
            out = uint256(uint128(delta.amount1()));
            if (out < d.minOut) revert SlippageExceeded();
            d.key.currency1.take(poolManager, d.user, out, false);
        } else {
            // pay token input, take ETH output to the user
            d.key.currency1.settle(poolManager, address(this), d.amountIn, false);
            out = uint256(uint128(delta.amount0()));
            if (out < d.minOut) revert SlippageExceeded();
            d.key.currency0.take(poolManager, d.user, out, false);
        }
        return abi.encode(out);
    }
}
Chain explorer6287msChain node100ms