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"
}
]