Token & Combat
Got a minute? I’ve been watching how smart contracts could handle fight betting—imagine a platform that pays out instantly, no middleman, and you could even stake on specific move types. Think you can build that? Or maybe you’ve seen a crypto project that can simulate a fight? Let's mash up strategy and code.
yeah i’ve got a framework in mind, you need a state machine for rounds, an oracle to feed real‑time move data, and a payout router that splits based on the outcome. i even built a mini‑sim in solidity that tracks each strike and settles instantly. let’s sketch the contract logic and see where the oracle hooks in
Nice, a state machine for rounds is solid. Make sure each state change—kick, jab, block—has a clear timestamp. The oracle must be trusted, or the whole thing gets rigged. For the payout router, use a tiered split: 60% to the winner, 30% to the loser’s stake, 10% to a community pool for future fighters. In your Solidity sim, add an event for every strike so the front‑end can replay the fight in real time. Also consider a timeout fallback if the oracle lags. We’ll map the hooks next. Let's get those functions written.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
contract FightBet {
enum Action { Kick, Jab, Block, None }
struct Round {
Action lastAction;
uint256 timestamp;
address fighterA;
address fighterB;
uint256 scoreA;
uint256 scoreB;
}
Round public currentRound;
event ActionTaken(address indexed fighter, Action action, uint256 timestamp);
event RoundEnded(address winner, address loser, uint256 payoutWinner, uint256 payoutLoser, uint256 communityPool);
uint256 public constant WINNER_SHARE = 60;
uint256 public constant LOSER_SHARE = 30;
uint256 public constant COMMUNITY_SHARE = 10;
function startRound(address _fighterA, address _fighterB) external {
currentRound = Round({
lastAction: Action.None,
timestamp: block.timestamp,
fighterA: _fighterA,
fighterB: _fighterB,
scoreA: 0,
scoreB: 0
});
}
// Oracle calls this
function recordAction(address _fighter, Action _action) external /* only oracle */ {
require(_fighter == currentRound.fighterA || _fighter == currentRound.fighterB, "Not a participant");
currentRound.lastAction = _action;
currentRound.timestamp = block.timestamp;
if (_action == Action.Kick || _action == Action.Jab) {
if (_fighter == currentRound.fighterA) currentRound.scoreA++;
else currentRound.scoreB++;
}
emit ActionTaken(_fighter, _action, block.timestamp);
}
function endRound() external /* only oracle or timeout */ {
address winner = currentRound.scoreA > currentRound.scoreB ? currentRound.fighterA : currentRound.fighterB;
address loser = currentRound.scoreA > currentRound.scoreB ? currentRound.fighterB : currentRound.fighterA;
uint256 totalStake = address(this).balance;
uint256 payoutWinner = (totalStake * WINNER_SHARE) / 100;
uint256 payoutLoser = (totalStake * LOSER_SHARE) / 100;
uint256 communityPool = totalStake - payoutWinner - payoutLoser;
(bool w1, ) = winner.call{value: payoutWinner}("");
(bool w2, ) = loser.call{value: payoutLoser}("");
// keep communityPool in contract for next rounds
emit RoundEnded(winner, loser, payoutWinner, payoutLoser, communityPool);
}
// fallback for reverts
fallback() external payable {}
receive() external payable {}
}
```
Just hook the oracle to `recordAction` and the timeout to `endRound`. Front‑end can replay with the `ActionTaken` events.
Looks solid, but a few tweaks to lock it down: add an `onlyOracle` modifier to guard `recordAction` and `endRound`, store the oracle address, and check `msg.sender`. For timeout, have a `lastAction` timestamp and if `block.timestamp > lastAction + roundDuration` then anyone can call `endRound`. Also make the payout logic use `ReentrancyGuard` or a safe transfer helper. Finally, emit a `RoundStarted` event in `startRound` so the UI knows the new bout. That’s it—now it’s battle‑ready.
here’s the tightened version—oracle‑only, timeout safe and reentrancy protected
```
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract FightBet is ReentrancyGuard {
enum Action { Kick, Jab, Block, None }
struct Round {
Action lastAction;
uint256 timestamp;
address fighterA;
address fighterB;
uint256 scoreA;
uint256 scoreB;
}
address public oracle;
uint256 public roundDuration = 2 minutes;
event RoundStarted(address indexed fighterA, address indexed fighterB);
event ActionTaken(address indexed fighter, Action action, uint256 timestamp);
event RoundEnded(address winner, address loser, uint256 payoutWinner, uint256 payoutLoser, uint256 communityPool);
modifier onlyOracle() {
require(msg.sender == oracle, "Not oracle");
_;
}
function setOracle(address _oracle) external {
oracle = _oracle;
}
function startRound(address _fighterA, address _fighterB) external {
currentRound = Round({
lastAction: Action.None,
timestamp: block.timestamp,
fighterA: _fighterA,
fighterB: _fighterB,
scoreA: 0,
scoreB: 0
});
emit RoundStarted(_fighterA, _fighterB);
}
function recordAction(address _fighter, Action _action) external onlyOracle {
require(_fighter == currentRound.fighterA || _fighter == currentRound.fighterB, "Not a participant");
currentRound.lastAction = _action;
currentRound.timestamp = block.timestamp;
if (_action == Action.Kick || _action == Action.Jab) {
if (_fighter == currentRound.fighterA) currentRound.scoreA++;
else currentRound.scoreB++;
}
emit ActionTaken(_fighter, _action, block.timestamp);
}
function endRound() external nonReentrant {
require(block.timestamp >= currentRound.timestamp + roundDuration || msg.sender == oracle,
"Timeout not reached");
address winner = currentRound.scoreA > currentRound.scoreB ? currentRound.fighterA : currentRound.fighterB;
address loser = currentRound.scoreA > currentRound.scoreB ? currentRound.fighterB : currentRound.fighterA;
uint256 totalStake = address(this).balance;
uint256 payoutWinner = (totalStake * WINNER_SHARE) / 100;
uint256 payoutLoser = (totalStake * LOSER_SHARE) / 100;
uint256 communityPool = totalStake - payoutWinner - payoutLoser;
payable(winner).transfer(payoutWinner);
payable(loser).transfer(payoutLoser);
emit RoundEnded(winner, loser, payoutWinner, payoutLoser, communityPool);
}
receive() external payable {}
}
```
Feel the instant‑payout power. Let’s fire up the UI and watch those strikes replay in real time.
Nice tighten‑up, but remember to lock the oracle change with onlyOwner or a multi‑sig, otherwise anyone can hijack. Also consider a small fee on the community pool for devs—keeps the chain running. Now throw it into testnet, run a mock fight, and see if the timers hold up. Keep the momentum, champ.