00
The report in 30 seconds
01 / THE MISSING PROOF
Not verifiable
No commitment.
No reproducible bet.
No pre-bet server seed hash, player client seed, or per-bet nonce. A seed shown later cannot prove a prior commitment.
Live game · verification chain absent
Evidence →
02 / THE FAKE VERIFIER
Mismatch found
The checker simulates
a different game.
It targets 95% RTP against the live game’s advertised ~99%, using different multipliers and inputs not tied to real bets.
Checker · cannot reproduce a live bet
Evidence →
Not provably fair. The checker cannot reproduce a live bet, and players cannot independently verify the game’s outcomes.
Moonroll.io markets its Plinko game as provably fair. Following multiple player reports about the verification flow, we conducted an independent technical review of the live game and the on-site "Plinko Fairness Checker." The review covered the user interface, the bet API, network traffic, cookies, and the source code of the public verifier tool.
The conclusion is that none of the components required for a cryptographically verifiable provably fair system are present in the live game. The verifier tool published on Moonroll's site exists, but it does not match the live game's logic and cannot be used to reproduce a real bet.
Moonroll.io markets its Plinko game as provably fair. Following multiple player reports about the verification flow, we conducted an independent technical review of the live game and the on-site "Plinko Fairness Checker." The review covered the user interface, the bet API, network traffic, cookies, and the source code of the public verifier tool.
The conclusion is that none of the components required for a cryptographically verifiable provably fair system are present in the live game. The verifier tool published on Moonroll's site exists, but it does not match the live game's logic and cannot be used to reproduce a real bet.
01
What a provably fair game requires
A provably fair game makes a cryptographic commitment to its outcome before the player bets, then reveals the underlying randomness afterwards so the player can independently reproduce the result. In practice, that means five components must be present and accessible to the player:
THE MISSING VERIFICATION CHAIN
01 / BEFORE THE BET
No published commitment
No server seed hash is shown before betting.
02 / PER BET
No player seed or nonce
The live API exposes neither input for an independent replay.
03 / AFTER THE BET
No verifiable replay
A seed revealed later has no prior hash to compare against.
If all five are present and the reveal matches the original commitment, a third party can recompute the outcome and confirm it was not manipulated. If any of these components are missing, the chain breaks and the player has no way to verify the result.
REPRODUCIBLE BY THE PLAYER
Five connected steps
MOONROLL · 13 NOVEMBER 2025
The chain is missing
01
Commit before betting
Publish SHA256(serverSeed) before any bets.
No pre-bet hash
A raw server seed appears only in history afterwards.
02
Let the player set a seed
Accept a chosen or random clientSeed.
No client seed
No UI input, account seed, or request/response field.
03
Index every bet
Increment nonce: 0, 1, 2, …
No per-bet nonce
No unique bet index bound to seed material.
04
Derive the outcome
HMAC_SHA256(serverSeed, clientSeed || nonce)
RNG remains hidden
Outcomes arrive fully formed; no reproducible formula.
05
Reveal and reproduce
Reveal serverSeed; check the hash and replay the bet.
No proof or replay
No prior hash to check, and no complete inputs to replay.
Why the “provably fair” label is unsupported
This is an instance of what we have described elsewhere as partial provable fairness — a category of implementations where the label is applied to a game whose underlying mechanisms do not deliver the property the term implies. Moonroll's case sits at the extreme end of that category: the components are not partially implemented, they are absent.
02
What the live game actually exposes
Across the live game, the bet API, and the response payloads, none of the required components appear.
The bet request submits only a bet amount, risk level, and number of rows. There is no field for a client seed and no nonce. The bet response returns the final path, the bucket index, and the multiplier — all already computed. The player is never shown a server seed hash before betting; only a raw server seed appears in the bet history afterwards, with no prior commitment to compare it against.
LIVE BET API / WHAT THE PLAYER CAN ACTUALLY VERIFY
BET REQUEST · JSON
{
"betAmount": 0.1,
"riskLevel": "LOW",
"numRows": 8
}BET RESPONSE · OBSERVED FIELDS
pathFinal path already computed
multiplierIndexSelected bucket index
multiplierResulting payout multiplier
The API returns a result, not the proof needed to reproduce it.
No client seed · no nonce · no server seed hash
A Plinko bet record in Moonroll's history view. A server seed is shown after the fact, with no prior commitment hash, no client seed, and no nonce.
03
Why the fairness checker cannot verify a bet
Moonroll publishes a "Plinko Fairness Checker" on its site, presented as a verification tool. Source-code analysis of the checker shows that it does not match the live game in three measurable ways.
TWO SYSTEMS · NO SHARED BET RECORD
A result from the checker is not proof of a live bet.
LIVE GAME
01 INPUT
Bet amount · risk level · rows
02 PROCESS
Hidden server RNG
03 OUTPUT
Path · bucket index · multiplier
FAIRNESS CHECKER
01 INPUT
Client seed + server seed
02 PROCESS
SHA256(seed + row index)
03 OUTPUT
Simulated path + generated multipliers
No published commitment or complete seed record connects these paths.
Different RTP and payout model
LIVE GAME · ADVERTISED
~99%Fixed payout tables, including 110× and 1000× extremes.
CHECKER · HARDCODED TARGET
95%A generated multiplier curve, scaled to an expected value of 0.95.

Moonroll's on-site Plinko verifier tool. It runs a self-contained simulation and cannot reproduce a real bet from the live game.
Because the checker accepts inputs (client seed, server seed) that the live game does not produce, and uses different RTP and multiplier logic, it cannot be used to verify any real bet. It is a self-contained simulation displayed alongside the game, not a verifier of it. It presents the full interface of a verification tool while being structurally incapable of reproducing a single live bet.
Different randomness inputs
The checker calculates outcomes using a different RNG construction:
const combinedSeed = clientSeed + serverSeed;
const random = generateRandomNumber(combinedSeed + i.toString());
The checker concatenates the two seeds and hashes them with the row index appended as a string. The live game’s RNG is not exposed; the bet API never asks for these inputs and never returns them. There is no exposed deterministic link between the two systems.
RTP target and multiplier construction
const targetExpectedValue = 0.95; // 95% RTP
The live game advertises approximately 99% RTP. The checker hardcodes 95%. Multipliers are scaled internally to produce that 95% expected value, which is mathematically incompatible with the multipliers the live game returns. The checker generates a smoothed multiplier curve with smaller extreme payouts; the live game uses fixed extreme multipliers such as 110× and 1000×.
The checker can produce extremes as low as 8.22× in some configurations.
These are examples across configurations, not a comparison of one matched paytable.
04
Requirements against implementation
The comparison covers both the cryptographic verification chain and the checker’s conformance to the live game. Internal seed generation remains opaque; a seed existing internally does not establish a provable commitment to a bet.
VERIFICATION CHAIN / REPLAY / CHECKER CONFORMANCE
Missing proof. Impossible replay. Inconsistent checker.
| REQUIREMENT | PROPER PROVABLY FAIR GAME | MOONROLL PLINKO | VERDICT |
|---|---|---|---|
| Server seed generation | Backend generates a random serverSeed. | Likely generated internally, but not exposed. | Opaque |
| Pre-bet server seed hash | SHA256(serverSeed) published before betting. | No commitment hash shown before bets. | Missing |
| Player client seed | The player selects a clientSeed. | No client seed in the UI or API. | Missing |
| Per-bet nonce | An incrementing index makes each bet unique. | No nonce in API, UI, cookies, or history. | Missing |
| Deterministic RNG | Published formula, e.g. HMAC_SHA256(serverSeed, clientSeed || nonce). | RNG is hidden and cannot be reproduced. | Not verifiable |
| Server seed reveal | A later reveal can be checked against the prior commitment. | A “server seed” string is shown with no prior hash. | Not provable |
| Replay of past bets | Complete inputs let players reproduce every result. | Client seed, nonce, and formula are missing. | Impossible |
| Checker matches live game | Same multipliers, RTP model, and RNG. | Checker uses a 95% target and different multipliers. | Inconsistent |
| Checker reproduces a real bet | Real inputs reproduce the recorded outcome. | Inputs do not match the live bet requirements. | Cannot verify |
| Consistent RTP claims | UI, documentation, and checker agree. | Live game advertises ~99%; checker targets 95%. | Contradiction |
| “Provably fair” claim | Results are cryptographically verifiable. | Required verification mechanisms are absent. | Misleading |
Audit scope: the live implementation and published checker examined on 13 November 2025.
05
What this means for players
The practical consequence is that players betting on Moonroll Plinko have no cryptographic guarantee about the outcomes. The RTP cannot be independently confirmed, the distribution of multipliers cannot be checked, and historical bets cannot be replayed against published seeds. The system relies entirely on trust in the operator — which is the condition that provably fair gaming was originally designed to eliminate.
WHAT THE EVIDENCE ESTABLISHES
A checker result cannot validate your live bet.
A displayed server seed or a successful simulation is not a cryptographic guarantee. The record lacks the commitment and complete inputs needed to independently replay the outcome.
06
What a working fix requires
A working fix would require Moonroll to implement a real commit-reveal flow, accept a client seed, expose a per-bet nonce, and replace the standalone fairness checker with a verifier that reproduces actual bet outcomes from real seed material. Until those changes are in place, the "provably fair" claim on Moonroll Plinko is not supported by what the game actually does.
01Implement a real commit–reveal system
Publish SHA256(serverSeed) before play. Reveal the server seed after rotation so players can check the prior commitment.
02Support player client seeds
Let players set a client seed. Use serverSeed, clientSeed, and nonce together in the deterministic RNG.
03Expose a complete bet record
Include the server seed hash, client seed, and nonce in bet logs. Provide a downloadable history containing those fields.
04Replace the detached checker
Use exactly the live game’s multipliers, RTP model, and RNG. Real serverSeed, clientSeed, and nonce inputs must reproduce the recorded outcome.
07
Conclusion and technical record
PROVABLY FAIR — FAIL
A black-box RNG game marketed as “provably fair.”
Moonroll’s implementation does not meet the requirements of a cryptographically verifiable fairness system.
Regardless of how the internal RTP behaves, the game cannot be independently audited by players, and its fairness claims are unsupported.
Why the system fails independent verification
There is no commit–reveal process, no client seed, no per-bet nonce, and no deterministic RNG formula that would allow players to independently verify results. The system provides no way to reproduce outcomes, no proof that server seeds were committed before bets, and no linkage between bets and disclosed randomness.
The “Plinko Fairness Checker” is not connected to the live game logic. It uses different multipliers, a different RTP model, and a different randomness flow. Because it cannot reproduce real bets, it cannot be considered a verifier.
The checker generates multipliers, derives a path from entered seeds, and displays a simulated result. Its target expected value is hardcoded to 0.95.
// Select the result display element
const resultDiv = document.getElementById('result');
const RISK_LEVELS = {
LOW: {
maxMultiplier: 10,
minMultiplier: 0.3,
riskFactor: 1.2,
},
MEDIUM: {
maxMultiplier: 33,
minMultiplier: 0.3,
riskFactor: 1.5,
},
HIGH: {
maxMultiplier: 1000,
minMultiplier: 0.2,
riskFactor: 9.5,
},
};
const error = message => {
resultDiv.innerHTML = `<div class="text-red-500 text-center">${message}</div>`;
};
const generateRandomNumber = (seed) => {
const hash = CryptoJS.SHA256(seed).toString();
const decimal = parseInt(hash.slice(0, 8), 16);
return decimal / 0xffffffff;
};
function calculatePositionProbabilities(nbRows) {
const nbBuckets = nbRows + 1;
const probabilities = new Array(nbBuckets).fill(0);
const totalPaths = Math.pow(2, nbRows);
for (let path = 0; path < totalPaths; path++) {
let position = 0;
for (let row = 0; row < nbRows; row++) {
if ((path & (1 << row)) !== 0) {
position++;
}
}
probabilities[position]++;
}
return probabilities.map((p) => p / totalPaths);
}
function generateMultipliers(riskLevel, nbRows) {
const config = RISK_LEVELS[riskLevel];
const nbBuckets = nbRows + 1;
const multipliers = new Array(nbBuckets);
const probabilities = calculatePositionProbabilities(nbRows);
const centerPosition = nbBuckets % 2 === 0
? Math.floor((nbBuckets - 1) / 2)
: Math.floor(nbBuckets / 2);
for (let i = 0; i <= centerPosition; i++) {
const distanceFromCenter = Math.abs(i - centerPosition);
const normalizedDistance = distanceFromCenter / centerPosition;
if (i === centerPosition) {
multipliers[i] = config.minMultiplier;
} else {
const progressionFactor = Math.pow(normalizedDistance, config.riskFactor);
multipliers[i] = config.minMultiplier + (config.maxMultiplier - config.minMultiplier) * progressionFactor;
}
}
for (let i = centerPosition + 1; i < nbBuckets; i++) {
multipliers[i] = multipliers[nbBuckets - i - 1];
}
const expectedValue = multipliers.reduce((sum, mult, i) => sum + mult * probabilities[i], 0);
const targetExpectedValue = 0.95;
const adjustmentFactor = targetExpectedValue / expectedValue;
return multipliers.map(mult => Math.round(mult * adjustmentFactor * 100) / 100);
}
function calculateResult(seed, riskLevel, nbRows) {
let position = 0;
const path = [];
for (let i = 0; i < nbRows; i++) {
const random = generateRandomNumber(seed + i.toString());
if (random < 0.5) {
position += 1;
path.push(1);
} else {
path.push(0);
}
}
const multipliers = generateMultipliers(riskLevel, nbRows);
return {
multiplier: multipliers[position],
position: position,
path: path
};
}
document.getElementById('verify').onclick = () => {
const serverSeed = document.getElementById('serverSeed').value;
const clientSeed = document.getElementById('clientSeed').value;
const numRows = parseInt(document.getElementById('numRows').value);
const riskLevel = document.getElementById('riskLevel').value;
if (!serverSeed) return error('Server seed is required');
if (!clientSeed) return error('Client seed is required');
if (!numRows || numRows < 8 || numRows > 16) return error('Number of rows must be between 8 and 16');
if (!RISK_LEVELS[riskLevel]) return error('Invalid risk level');
try {
const combinedSeed = clientSeed + serverSeed;
const gameResult = calculateResult(combinedSeed, riskLevel, numRows);
resultDiv.innerHTML = `
<div class="text-center">
<table class="w-full border-collapse border border-gray-600">
<thead>
<tr>
<th class="border border-gray-600 p-2">Position</th>
<th class="border border-gray-600 p-2">Multiplier</th>
<th class="border border-gray-600 p-2">Path</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-600 p-2">${gameResult.position}/${numRows}</td>
<td class="border border-gray-600 p-2">${gameResult.multiplier}x</td>
<td class="border border-gray-600 p-2">${gameResult.path.join(" → ")}</td>
</tr>
</tbody>
</table>
</div>`;
} catch (e) {
console.error('Error:', e);
error('An error occurred while generating the results.');
}
};Complete implementation from the published fairness checker.
TECHNICAL RECORD
Independent audit dated 13 November 2025. Scope: cryptographic fairness primitives and verifier-to-game conformance, assessed through the UI, API, HAR, cookies, and public checker code.

