Blockchain / Global / Address

Contract

0x5A910fA5346b8Cdd0402b38fB159fe0729D2C2Da

Balance
0 SOMI
Transactions sent
1
Type
Contract · 4,757 bytes of code

ForgeHybrid Verified

Compiler
solc 0.8.28+commit.7893614a
Language
Solidity
Optimization
Yes, 200 runs
EVM version
cancun
License
—
Verified
2026-10-12

SOMNIAForgeHybridtestV2.sol

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title ForgeHybrid - Burn Bronze ERC-1155 to get Gold ERC-721
/// @notice Burns 5 Bronze tokens (ERC-1155) and transfers 1 Gold token (ERC-721) from reserve

// Minimal ERC-1155 interface for Bronze
interface IERC1155 {
    function balanceOf(address account, uint256 id) external view returns (uint256);
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
    function setApprovalForAll(address operator, bool approved) external;
    function isApprovedForAll(address account, address operator) external view returns (bool);
}

// Minimal ERC-721 interface for Gold
interface IERC721 {
    function ownerOf(uint256 tokenId) external view returns (address);
    function transferFrom(address from, address to, uint256 tokenId) external;
    function setApprovalForAll(address operator, bool approved) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

contract ForgeHybrid {
    // Errors
    error Unauthorized(address caller);
    error InsufficientBronzeBalance(uint256 required, uint256 available);
    error NoGoldAvailable();
    error ContractPaused();
    error InvalidAddress();
    error Reentrancy();
    error BronzeNotApproved();
    error GoldNotInReserve(uint256 tokenId);
    error InvalidTokenIdRange();

    // Events
    event Forged(address indexed user, uint256 bronzesBurned, uint256 goldTokenId, uint256 feePaid);
    event ParamsUpdated(uint256 bronzePerGold, uint256 feeWei, address feeRecipient);
    event OwnerUpdated(address indexed newOwner);
    event Paused(bool paused);
    event ReserveWalletUpdated(address indexed newReserveWallet);
    event GoldTokenIdRangeUpdated(uint256 startId, uint256 endId);

    // Immutable contract addresses
    IERC1155 public immutable bronzeContract;
    IERC721 public immutable goldContract;
    uint256 public immutable BRONZE_TOKEN_ID;

    // Admin/Config
    address public owner;
    address public reserveWallet;  // Wallet holding the Gold NFTs
    uint256 public bronzePerGold;  // How many Bronze needed for 1 Gold (default: 5)
    uint256 public feeWei;         // Optional fee in native token
    address public feeRecipient;
    bool public paused;

    // Gold Token ID range (which Gold NFTs are available for forging)
    uint256 public goldStartTokenId;  // e.g., 30
    uint256 public goldEndTokenId;    // e.g., 49
    uint256 public nextGoldTokenId;   // Tracks which Gold to give next

    // Reentrancy guard
    uint256 private _status;
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    modifier onlyOwner() {
        if (msg.sender != owner) revert Unauthorized(msg.sender);
        _;
    }

    modifier whenNotPaused() {
        if (paused) revert ContractPaused();
        _;
    }

    modifier nonReentrant() {
        if (_status == _ENTERED) revert Reentrancy();
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }

    constructor(
        address _bronzeContract,
        address _goldContract,
        uint256 _bronzeTokenId,
        uint256 _bronzePerGold,
        address _reserveWallet,
        address _feeRecipient,
        uint256 _goldStartTokenId,
        uint256 _goldEndTokenId
    ) {
        if (_bronzeContract == address(0)) revert InvalidAddress();
        if (_goldContract == address(0)) revert InvalidAddress();
        if (_reserveWallet == address(0)) revert InvalidAddress();
        if (_feeRecipient == address(0)) revert InvalidAddress();
        if (_bronzePerGold == 0) revert InvalidTokenIdRange();
        if (_goldEndTokenId < _goldStartTokenId) revert InvalidTokenIdRange();

        owner = msg.sender;
        bronzeContract = IERC1155(_bronzeContract);
        goldContract = IERC721(_goldContract);
        BRONZE_TOKEN_ID = _bronzeTokenId;
        bronzePerGold = _bronzePerGold;
        reserveWallet = _reserveWallet;
        feeRecipient = _feeRecipient;
        feeWei = 0;
        paused = false;
        _status = _NOT_ENTERED;

        goldStartTokenId = _goldStartTokenId;
        goldEndTokenId = _goldEndTokenId;
        nextGoldTokenId = _goldStartTokenId;
    }

    /// @notice Burn Bronze tokens to receive 1 Gold token
    function forge() external payable nonReentrant whenNotPaused {
        // Check fee if required
        if (feeWei > 0) {
            require(msg.value == feeWei, "Incorrect fee");
        }

        // Check user has enough Bronze
        uint256 userBronze = bronzeContract.balanceOf(msg.sender, BRONZE_TOKEN_ID);
        if (userBronze < bronzePerGold) {
            revert InsufficientBronzeBalance(bronzePerGold, userBronze);
        }

        // Check if user approved this contract to transfer their Bronze
        if (!bronzeContract.isApprovedForAll(msg.sender, address(this))) {
            revert BronzeNotApproved();
        }

        // Check if there are Gold tokens available
        if (nextGoldTokenId > goldEndTokenId) {
            revert NoGoldAvailable();
        }

        // Get the next Gold token ID to transfer
        uint256 goldTokenId = nextGoldTokenId;
        nextGoldTokenId++;

        // Verify the Gold token is in reserve wallet
        address goldOwner = goldContract.ownerOf(goldTokenId);
        if (goldOwner != reserveWallet) {
            revert GoldNotInReserve(goldTokenId);
        }

        // Verify reserve wallet approved this contract
        if (!goldContract.isApprovedForAll(reserveWallet, address(this))) {
            revert GoldNotInReserve(goldTokenId);
        }

        // Burn Bronze from user (transfer to 0x000...dead)
        uint256[] memory ids = new uint256[](1);
        uint256[] memory amounts = new uint256[](1);
        ids[0] = BRONZE_TOKEN_ID;
        amounts[0] = bronzePerGold;
        
        bronzeContract.safeBatchTransferFrom(
            msg.sender,
            0x000000000000000000000000000000000000dEaD,
            ids,
            amounts,
            ""
        );

        // Transfer Gold from reserve to user
        goldContract.transferFrom(reserveWallet, msg.sender, goldTokenId);

        emit Forged(msg.sender, bronzePerGold, goldTokenId, feeWei);
    }

    // Admin functions
    function setParams(
        uint256 _bronzePerGold,
        uint256 _feeWei,
        address _feeRecipient
    ) external onlyOwner {
        if (_bronzePerGold == 0) revert InvalidTokenIdRange();
        if (_feeRecipient == address(0)) revert InvalidAddress();
        
        bronzePerGold = _bronzePerGold;
        feeWei = _feeWei;
        feeRecipient = _feeRecipient;
        
        emit ParamsUpdated(_bronzePerGold, _feeWei, _feeRecipient);
    }

    function setReserveWallet(address _reserveWallet) external onlyOwner {
        if (_reserveWallet == address(0)) revert InvalidAddress();
        reserveWallet = _reserveWallet;
        emit ReserveWalletUpdated(_reserveWallet);
    }

    function setGoldTokenIdRange(uint256 _startId, uint256 _endId) external onlyOwner {
        if (_endId < _startId) revert InvalidTokenIdRange();
        goldStartTokenId = _startId;
        goldEndTokenId = _endId;
        // Reset counter if needed
        if (nextGoldTokenId < _startId) {
            nextGoldTokenId = _startId;
        }
        emit GoldTokenIdRangeUpdated(_startId, _endId);
    }

    function setPaused(bool _paused) external onlyOwner {
        paused = _paused;
        emit Paused(_paused);
    }

    function setOwner(address _newOwner) external onlyOwner {
        if (_newOwner == address(0)) revert InvalidAddress();
        owner = _newOwner;
        emit OwnerUpdated(_newOwner);
    }

    function withdrawFees() external {
        if (msg.sender != owner && msg.sender != feeRecipient) {
            revert Unauthorized(msg.sender);
        }
        uint256 bal = address(this).balance;
        require(bal > 0, "No fees");
        (bool ok, ) = payable(feeRecipient).call{value: bal}("");
        require(ok, "Transfer failed");
    }

    // View functions
    function canForge(address user) external view returns (bool) {
        if (paused) return false;
        if (nextGoldTokenId > goldEndTokenId) return false;
        if (bronzeContract.balanceOf(user, BRONZE_TOKEN_ID) < bronzePerGold) return false;
        if (!bronzeContract.isApprovedForAll(user, address(this))) return false;
        
        return true;
    }

    function getAvailableGoldCount() external view returns (uint256) {
        if (nextGoldTokenId > goldEndTokenId) return 0;
        return goldEndTokenId - nextGoldTokenId + 1;
    }

    function getStats() external view returns (
        uint256 _bronzePerGold,
        uint256 _feeWei,
        uint256 _availableGold,
        uint256 _nextGoldId,
        bool _paused
    ) {
        uint256 available = 0;
        if (nextGoldTokenId <= goldEndTokenId) {
            available = goldEndTokenId - nextGoldTokenId + 1;
        }
        return (bronzePerGold, feeWei, available, nextGoldTokenId, paused);
    }

    receive() external payable {}
}
ABI · 39 entries
json
[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "_bronzeContract",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_goldContract",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_bronzeTokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_bronzePerGold",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_reserveWallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_feeRecipient",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_goldStartTokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_goldEndTokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "BronzeNotApproved",
    "type": "error",
    "inputs": []
  },
  {
    "name": "ContractPaused",
    "type": "error",
    "inputs": []
  },
  {
    "name": "GoldNotInReserve",
    "type": "error",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "InsufficientBronzeBalance",
    "type": "error",
    "inputs": [
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "available",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "InvalidAddress",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidTokenIdRange",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NoGoldAvailable",
    "type": "error",
    "inputs": []
  },
  {
    "name": "Reentrancy",
    "type": "error",
    "inputs": []
  },
  {
    "name": "Unauthorized",
    "type": "error",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "Forged",
    "type": "event",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "bronzesBurned",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "goldTokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "feePaid",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "GoldTokenIdRangeUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "startId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "endId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "OwnerUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "newOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "ParamsUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "bronzePerGold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "feeWei",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "feeRecipient",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Paused",
    "type": "event",
    "inputs": [
      {
        "name": "paused",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      }
    ],
    "anonymous": false
  },
  {
    "name": "ReserveWalletUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "newReserveWallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "BRONZE_TOKEN_ID",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "bronzeContract",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract IERC1155"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "bronzePerGold",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "canForge",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "feeRecipient",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "feeWei",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "forge",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "payable"
  },
  {
    "name": "getAvailableGoldCount",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getStats",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "_bronzePerGold",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_feeWei",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_availableGold",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_nextGoldId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_paused",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "goldContract",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract IERC721"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "goldEndTokenId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "goldStartTokenId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "nextGoldTokenId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "owner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "paused",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "reserveWallet",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "setGoldTokenIdRange",
    "type": "function",
    "inputs": [
      {
        "name": "_startId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_endId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setOwner",
    "type": "function",
    "inputs": [
      {
        "name": "_newOwner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setParams",
    "type": "function",
    "inputs": [
      {
        "name": "_bronzePerGold",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_feeWei",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_feeRecipient",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setPaused",
    "type": "function",
    "inputs": [
      {
        "name": "_paused",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setReserveWallet",
    "type": "function",
    "inputs": [
      {
        "name": "_reserveWallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "withdrawFees",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "receive",
    "stateMutability": "payable"
  }
]