One of the earliest provably-fair dice gambling contracts on Ethereum, deployed August 12, 2015 (day 13 of Frontier). Commit-reveal dice game with 2% house edge
Historical Significance
One of the earliest gambling dApps on Ethereum, deployed during Frontier's first two weeks. Demonstrates early smart contract patterns: commit-reveal schemes, server-side random seeds, and the risks of variable shadowing bugs in Solidity 0.1.x. Contains 122 ETH locked since 2015.
Context
Deployed in August 2015 during the Frontier launch era, when Ethereum had just gone live and developers were experimenting with novel contract designs. HonestDice was among the first wave of gambling contracts, alongside early pyramids and lotteries, that tested what smart contracts could do.
Key Facts
Description
HonestDice was a commit-reveal dice game deployed just 13 days after Ethereum's Frontier launch. Players submitted a bet with a secret hash; the server provided a random seed; the player then revealed their secret to determine the outcome. The game offered configurable odds (1-255) with a 2% house edge and a maximum payout of 5% of the contract's bankroll per roll.
The contract enforced a 10 ETH minimum bet (the published GitHub source shows 1 ETH, but the as-deployed version used 10 ETH). A server-provided seed within 20 blocks prevented front-running.
Notably, the contract contains a shadowing bug: both lockBetsForWithdraw() and unlockBets() declare a local uint betsLocked variable instead of modifying the state variable. Both functions are no-ops on-chain, meaning the owner could never actually lock or unlock bets.
The contract was mislabeled as 'EtherDice' in several academic smart contract analysis datasets. The actual contract name in the source code is HonestDice.
122 ETH remains locked in the contract. The deployer's key appears to be lost.
Source Verified
Exact creation (1,992 bytes) + runtime (1,927 bytes) match. soljson v0.1.1, optimizer enabled.
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Frontier Era
The initial release of Ethereum. A bare-bones implementation for technical users.
Bytecode Overview
Verified Source Available
Source verified through compiler archaeology and exact bytecode matching.
View Verification ProofShow source code (Solidity)
contract HonestDice {
event Bet(address indexed user, uint blocknum, uint256 amount, uint chance);
event Won(address indexed user, uint256 amount, uint chance);
struct Roll {
uint256 value;
uint chance;
uint blocknum;
bytes32 secretHash;
bytes32 serverSeed;
}
uint betsLocked;
address owner;
address feed;
uint256 minimumBet = 10 * 1000000000000000000; // 10 Ether
uint256 constant maxPayout = 5; // 5% of bankroll
uint constant seedCost = 100000000000000000; // This is the cost of supplyin the server seed, deduct it;
mapping (address => Roll) rolls;
uint constant timeout = 20; // 5 Minutes
function HonestDice() {
owner = msg.sender;
feed = msg.sender;
}
function roll(uint chance, bytes32 secretHash) {
if (chance < 1 || chance > 255 || msg.value < minimumBet || calcWinnings(msg.value, chance) > getMaxPayout() || betsLocked != 0) {
msg.sender.send(msg.value); // Refund
return;
}
rolls[msg.sender] = Roll(msg.value, chance, block.number, secretHash, 0);
Bet(msg.sender, block.number, msg.value, chance);
}
function serverSeed(address user, bytes32 seed) {
// The server calls this with a random seed
if (msg.sender != feed) return;
if (rolls[user].serverSeed != 0) return;
rolls[user].serverSeed = seed;
}
function hashTo256(bytes32 hash) constant returns (uint _r) {
// Returns a number between 0 - 255 from a hash
return uint(hash) & 0xff;
}
function hash(bytes32 input) constant returns (uint _r) {
// Simple sha3 hash. Not to be called via the blockchain
return uint(sha3(input));
}
function isReady() constant returns (bool _r) {
return isReadyFor(msg.sender);
}
function isReadyFor(address _user) constant returns (bool _r) {
Roll r = rolls[_user];
if (r.serverSeed == 0) return false;
return true;
}
function getResult(bytes32 secret) constant returns (uint _r) {
// Get the result number of the roll
Roll r = rolls[msg.sender];
if (r.serverSeed == 0) return;
if (sha3(secret) != r.secretHash) return;
return hashTo256(sha3(secret, r.serverSeed));
}
function didWin(bytes32 secret) constant returns (bool _r) {
// Returns if the player won or not
Roll r = rolls[msg.sender];
if (r.serverSeed == 0) return;
if (sha3(secret) != r.secretHash) return;
if (hashTo256(sha3(secret, r.serverSeed)) < r.chance) { // Winner
return true;
}
return false;
}
function calcWinnings(uint256 value, uint chance) constant returns (uint256 _r) {
// 1% house edge
return (value * 98 / 100) * 256 / chance;
}
function getMaxPayout() constant returns (uint256 _r) {
return this.balance * maxPayout / 100;
}
function claim(bytes32 secret) {
Roll r = rolls[msg.sender];
if (r.serverSeed == 0) return;
if (sha3(secret) != r.secretHash) return;
if (hashTo256(sha3(secret, r.serverSeed)) < r.chance) { // Winner
msg.sender.send(calcWinnings(r.value, r.chance));
Won(msg.sender, r.value, r.chance);
}
delete rolls[msg.sender];
}
function canClaimTimeout() constant returns (bool _r) {
Roll r = rolls[msg.sender];
if (r.serverSeed != 0) return false;
if (r.value <= 0) return false;
if (block.number < r.blocknum + timeout) return false;
return true;
}
function claimTimeout() {
// Get your monies back if the server isn't responding with a seed
if (!canClaimTimeout()) return;
Roll r = rolls[msg.sender];
msg.sender.send(r.value);
delete rolls[msg.sender];
}
function getMinimumBet() constant returns (uint _r) {
return minimumBet;
}
function getBankroll() constant returns (uint256 _r) {
return this.balance;
}
function getBetsLocked() constant returns (uint _r) {
return betsLocked;
}
function setMinimumBet(uint256 _minimumBet) {
if (msg.sender != owner) return;
minimumBet = _minimumBet;
}
function setFeed(address newFeed) {
if (msg.sender != owner) return;
feed = newFeed;
}
function lockBetsForWithdraw() {
if (msg.sender != owner) return;
uint betsLocked = block.number;
}
function unlockBets() {
if (msg.sender != owner) return;
uint betsLocked = 0;
}
function withdraw(uint amount) {
if (msg.sender != owner) return;
if (betsLocked == 0 || block.number < betsLocked + 5760) return;
owner.send(amount);
}
}External Links
Related contracts
HonestDice (1 ETH)
Same deployerCommit-reveal dice game deployed day 13 of Ethereum mainnet with 1,000 ETH bankroll. Still active in 2026 with 84 ETH locked. 1% house edge, 1 ETH minimum bet.
0xd79b4c...f80456August 12, 2015ýÞ
Same eraWallet proxy stub generated by the Mist Wallet factory. Forwards all calls via CALLCODE to the master Ethereum Foundation Wallet contract at 0x273930d2.
0xd2f06b...74e89bAugust 21, 2015EthereumAlarmClock
Same eraThe first deployment of Piper Merriam's Ethereum Alarm Clock, a smart contract protocol for scheduling transactions at future block numbers, launched on Septemb
0xb0059e...cc0cc1September 22, 2015Alarm
Same eraThe first scheduled-transaction service on Ethereum, enabling smart contracts to schedule future function calls—deployed by Piper Merriam in October 2015.
0x07307d...9c4588October 23, 2015TimeLockVault
Same eraA 2015 time-lock vault: deposit ETH to lock it ~20 years, withdraw after it expires.
0xed44f3...643735December 20, 2015