VirtualsBondingAdapter

0xdeef773d61719a3181e35e9281600db8ba063f71

Verification
Verified
0.8.17+commit.8df45f5f
Type
Contract
2,150 bytes
ABI entries
7
2 read · 2 write
License
none

Contract information

Address
0xdeef773d61719a3181e35e9281600db8ba063f71
Chain
Robinhood Chain (4663)
Compiler
0.8.17+commit.8df45f5f
Optimization
Enabled
Creation tx
0xe3a084c871…3d486fa5ef

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (2)

ASSET_TOKEN()address
ROUTER()address

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_router",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "ForceApproveFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SafeTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ASSET_TOKEN",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ROUTER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "bytes",
        "name": "moreInfo",
        "type": "bytes"
      }
    ],
    "name": "sellBase",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "internalType": "bytes",
        "name": "moreInfo",
        "type": "bytes"
      }
    ],
    "name": "sellQuote",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

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

import {IAdapter} from "@interfaces/IAdapter.sol";
import {IERC20} from "@interfaces/IERC20.sol";
import {RefundLib} from "@libraries/RefundLib.sol";
import {SafeERC20} from "@libraries/SafeERC20.sol";

/// @notice Virtuals Protocol BondingV2/V4 swap interface
interface IVirtualsBonding {
    function buy(uint256 amountIn, address tokenAddress, uint256 amountOutMin, uint256 deadline)
        external
        payable
        returns (bool);

    function sell(uint256 amountIn, address tokenAddress, uint256 amountOutMin, uint256 deadline)
        external
        returns (bool);
}

/// @notice Virtuals Protocol FRouter interface (for reading assetToken)
interface IFRouterV2 {
    function assetToken() external view returns (address);
}

/// @title VirtualsBondingAdapter
/// @notice Adapter for Virtuals Protocol BondingV2/V4 on Base
/// @dev moreInfo = abi.encode(fromToken, toToken)
///      pool = BondingV2 or BondingV4 contract address.
///      Swaps are routed through the FRouter which pulls tokens via safeTransferFrom.
contract VirtualsBondingAdapter is IAdapter {
    using SafeERC20 for IERC20;

    address public immutable ROUTER;
    address public immutable ASSET_TOKEN;

    constructor(address _router) {
        ROUTER = _router;
        ASSET_TOKEN = IFRouterV2(_router).assetToken();
    }

    function sellBase(address to, address pool, bytes memory moreInfo) external override {
        _bondingSwap(to, pool, moreInfo);
    }

    function sellQuote(address to, address pool, bytes memory moreInfo) external override {
        _bondingSwap(to, pool, moreInfo);
    }

    function _bondingSwap(address to, address pool, bytes memory moreInfo) internal {
        (address fromToken, address toToken) = abi.decode(moreInfo, (address, address));

        uint256 fromAmount = IERC20(fromToken).balanceOf(address(this));
        require(fromAmount > 0, "VirtualsBondingAdapter: zero balance");

        IERC20(fromToken).safeApprove(ROUTER, 0);
        IERC20(fromToken).safeApprove(ROUTER, fromAmount);

        if (fromToken == ASSET_TOKEN) {
            // Buy: spend ASSET_TOKEN to buy agent token
            IVirtualsBonding(pool).buy(fromAmount, toToken, 0, block.timestamp);
            IERC20(toToken).safeTransfer(to, IERC20(toToken).balanceOf(address(this)));
        } else {
            // Sell: sell agent token for ASSET_TOKEN
            IVirtualsBonding(pool).sell(fromAmount, fromToken, 0, block.timestamp);
            IERC20(ASSET_TOKEN).safeTransfer(to, IERC20(ASSET_TOKEN).balanceOf(address(this)));
        }

        RefundLib.refund(fromToken);
    }
}
Chain explorer6545msChain node84ms