Blockchain / Global / Address

Contract

0x7D8Bf2dafD04103dc11B62BEbE3AAd7f069CcC30

Balance
0 SOMI
Transactions sent
1
Type
Contract · 3,371 bytes of code

BeatBounceGame Verified

Compiler
solc 0.8.24+commit.e11b9ed9
Language
Solidity
Optimization
Yes, 200 runs
EVM version
paris
License
—
Verified
2026-10-12

contracts/BeatBounceGame.sol

and 3 more files

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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";

contract BeatBounceGame is Ownable, Pausable {
    struct Player {
        bool registered;
        uint64 sessions;
        uint64 lastSaveAt;
        uint64 activeStartAt;
        uint128 totalPlayTime;
    }

    mapping(address => Player) public players;

    event Registered(address indexed player);
    event GameStarted(address indexed player, uint256 at, uint64 sessionNumber);
    event GameSaved(address indexed player, uint256 at, uint256 delta, uint256 total);
    event GameEnded(address indexed player, uint256 at, uint256 delta, uint256 total, bool forced);

    constructor() Ownable() {}

    function register(address player) external onlyOwner whenNotPaused {
        require(player != address(0), "Invalid player");
        Player storage playerData = players[player];
        require(!playerData.registered, "Already registered");

        playerData.registered = true;

        emit Registered(player);
    }

    function startGame(address player) external onlyOwner whenNotPaused {
        Player storage playerData = _requireRegistered(player);
        require(playerData.activeStartAt == 0, "Session active");
        require(playerData.sessions < type(uint64).max, "Sessions overflow");

        playerData.activeStartAt = uint64(block.timestamp);
        unchecked {
            playerData.sessions += 1;
        }

        emit GameStarted(player, block.timestamp, playerData.sessions);
    }

    function saveGame(address player) external onlyOwner whenNotPaused {
        Player storage playerData = _requireRegistered(player);
        uint64 start = playerData.activeStartAt;
        require(start != 0, "No active session");

        uint256 delta = block.timestamp - uint256(start);
        require(delta <= type(uint128).max, "Delta overflow");

        uint128 newTotal = playerData.totalPlayTime + uint128(delta);
        playerData.totalPlayTime = newTotal;

        uint64 current = uint64(block.timestamp);
        playerData.activeStartAt = current;
        playerData.lastSaveAt = current;

        emit GameSaved(player, current, delta, newTotal);
    }

    function endGame(address player) external onlyOwner whenNotPaused {
        _endSession(player, false);
    }

    function emergencyEnd(address player) external onlyOwner {
        _endSession(player, true);
    }

    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    function getPlayer(address player) external view returns (Player memory) {
        return players[player];
    }

    function isInSession(address player) external view returns (bool) {
        return players[player].activeStartAt != 0;
    }

    function _endSession(address player, bool forced) internal {
        Player storage playerData = _requireRegistered(player);
        uint64 start = playerData.activeStartAt;
        require(start != 0, "No active session");

        uint256 delta = block.timestamp - uint256(start);
        require(delta <= type(uint128).max, "Delta overflow");

        uint128 newTotal = playerData.totalPlayTime + uint128(delta);
        playerData.totalPlayTime = newTotal;
        playerData.activeStartAt = 0;

        uint64 current = uint64(block.timestamp);
        playerData.lastSaveAt = current;

        emit GameEnded(player, current, delta, newTotal, forced);
    }

    function _requireRegistered(address player) internal view returns (Player storage) {
        Player storage playerData = players[player];
        require(playerData.registered, "Not registered");
        return playerData;
    }
}

Other files

  • @openzeppelin/contracts/access/Ownable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../utils/Context.sol";
    
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * By default, the owner account will be the one that deploys the contract. This
     * can later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
    
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        constructor() {
            _transferOwnership(_msgSender());
        }
    
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
    
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
    
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
        }
    
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            _transferOwnership(newOwner);
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    
  • @openzeppelin/contracts/security/Pausable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../utils/Context.sol";
    
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    abstract contract Pausable is Context {
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
    
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
    
        bool private _paused;
    
        /**
         * @dev Initializes the contract in unpaused state.
         */
        constructor() {
            _paused = false;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        modifier whenNotPaused() {
            _requireNotPaused();
            _;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        modifier whenPaused() {
            _requirePaused();
            _;
        }
    
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view virtual returns (bool) {
            return _paused;
        }
    
        /**
         * @dev Throws if the contract is paused.
         */
        function _requireNotPaused() internal view virtual {
            require(!paused(), "Pausable: paused");
        }
    
        /**
         * @dev Throws if the contract is not paused.
         */
        function _requirePaused() internal view virtual {
            require(paused(), "Pausable: not paused");
        }
    
        /**
         * @dev Triggers stopped state.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
    
        /**
         * @dev Returns to normal state.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
    }
    
  • @openzeppelin/contracts/utils/Context.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    
ABI · 22 entries
json
[
  {
    "type": "constructor",
    "inputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "GameEnded",
    "type": "event",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "at",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "delta",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "total",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "forced",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      }
    ],
    "anonymous": false
  },
  {
    "name": "GameSaved",
    "type": "event",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "at",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "delta",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "total",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "GameStarted",
    "type": "event",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "at",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "sessionNumber",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "name": "OwnershipTransferred",
    "type": "event",
    "inputs": [
      {
        "name": "previousOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "newOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Paused",
    "type": "event",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Registered",
    "type": "event",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Unpaused",
    "type": "event",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "emergencyEnd",
    "type": "function",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "endGame",
    "type": "function",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "getPlayer",
    "type": "function",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "components": [
          {
            "name": "registered",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "sessions",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "lastSaveAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "activeStartAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "totalPlayTime",
            "type": "uint128",
            "internalType": "uint128"
          }
        ],
        "internalType": "struct BeatBounceGame.Player"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "isInSession",
    "type": "function",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "owner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pause",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "paused",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "players",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "registered",
        "type": "bool",
        "internalType": "bool"
      },
      {
        "name": "sessions",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "lastSaveAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "activeStartAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "totalPlayTime",
        "type": "uint128",
        "internalType": "uint128"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "register",
    "type": "function",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "renounceOwnership",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "saveGame",
    "type": "function",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "startGame",
    "type": "function",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "transferOwnership",
    "type": "function",
    "inputs": [
      {
        "name": "newOwner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "unpause",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  }
]

Send feedback

Agents: POST the same to /api/feedback.