Below is a list of bugs discovered in the roulette code base along with suggestions for addressing them.
const formattedChainNumber = (chainNumber, decimals) => {
return chainNumber
? parseFloat(chainNumber)
.toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals })
: "Loading...";
}When the numeric value is 0 the falsy check causes "Loading..." to be shown.
Fix: only show the loading message when chainNumber is undefined or null.
MostRecentSpinResults and SpinResult attach listeners outside a proper effect which can leak handlers.
rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
const copySpinResults = [...spinResults];
copySpinResults.push(parseInt(wheelNumber, 10));
setSpinResults(copySpinResults.slice(-20));
}
});Fix: register the listener inside useEffect with a cleanup function so it runs once per component lifecycle.
Roulette.handleSpinButtonClick attaches a new ExecutedWager listener every time the wheel spins.
executeWager(playerAddress)
.then((response) => {
setLatestBlockNumber(response.blockNumber);
})
.then(() => {
rouletteContractEvents.on('ExecutedWager', (playerAddr, wheelNum) => {
if (playerAddr === props.playerAddress) {
setWheelNumber(parseInt(wheelNum, 10));
setWheelIsSpinning(false);
}
});
});Fix: install a single event handler with useEffect and remove it on cleanup.
NumbersHitTracker and CompletionsCounter start a new setTimeout every state update.
useEffect(() => {
setTimeout(async () => {
const currentNumbers = await getPlayerNumberCompletionSetCurrent(props.playerAddress);
setCurrentSet(new Set(currentNumbers));
}, 1000);
}, [props.playerAddress, currentSet]);Each state change retriggers the effect leading to accumulating timers.
Fix: use setInterval with a cleanup function or remove the state variable from the dependency list.
ChipSelection invokes the Chip function instead of rendering <Chip />, so React does not apply keys properly.
return Chip({
id: `chip-${chipAmount}`,
key: chipAmount,
auxiliaryClassName: "chip-selection-chip",
chipAmount,
onClick: props.onClick,
isSelected,
});Fix: use JSX: <Chip key={chipAmount} ... />.
NumberHitGameCounter contains a repeated 220.
const outline = [40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 220, 230, 240, 250, 260, 270, 280, 290, 300].includes(i) ? "1px solid black" : "none";Fix: remove the duplicate to keep the intended sequence.
initializeChain.js approves a fixed roulette contract address.
const tx = await tokenContract.approve(
"0xCE3478A9E0167a6Bc5716DC39DbbbfAc38F27623",
ethers.utils.parseEther("100000")
);If the contract gets deployed to a different address the approvals go to the wrong target. Fix: read the deployed address from the deployment step or environment variables.
Below is a list of issues found in the repository along with a short explanation and suggested fixes. Line numbers correspond to the version in this commit.
handleSpinButtonClick registers a new ExecutedWager listener each spin without removing the previous one. Over time this leads to multiple handlers firing for a single event.
132: rouletteContractEvents.on('ExecutedWager', (playerAddr, wheelNum) => {
Suggested fix: move the listener into a useEffect with a cleanup function or call off before registering a new one.
The component subscribes to ExecutedWager outside of useEffect, causing a new callback to be added on each render.
11: rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
Suggested fix: register the listener once inside useEffect and remove it in the cleanup function.
SpinResult adds an ExecutedWager listener inside useEffect but never removes it. The dependency array also includes state that triggers repeated re‑attachment.
17: rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
Suggested fix: use useEffect with [props.playerAddress] as dependency and return a cleanup function calling off.
Including completionsCount in the dependency array makes the effect schedule a new timeout whenever the count updates, leading to unnecessary timers.
18: useEffect(() => {
...
23: }, [props.playerAddress, completionsCount]);
Suggested fix: remove completionsCount from the dependency array or use an interval.
Similar to the issue above, the effect fetches data and updates currentSet, but currentSet is listed as a dependency so the effect re-runs after each update.
11: useEffect(() => {
...
16: }, [props.playerAddress, currentSet]);
Suggested fix: depend only on props.playerAddress and clear the timeout in a cleanup function.
The repository also contains a historical KNOWN_BUGS.md file describing previously fixed issues. Review that document for more context.
The following issues have been discovered in the repository in addition to those already documented in KNOWN_BUGS.md.
Multiple components attach listeners to the ExecutedWager event every render or spin without removing them. Over time this can lead to duplicate handlers and memory leaks.
- Roulette.js registers a listener each time a spin occurs:
rouletteContractEvents.on('ExecutedWager', (playerAddr, wheelNum) => {
if (playerAddr === props.playerAddress) {
setWheelNumber(parseInt(wheelNum, 10));
setWheelIsSpinning(false);
}
});
【F:src/components/roulette/Roulette.js†L128-L138】
- SpinResult.js attaches a listener inside a
useEffectthat reruns whenever state updates:
rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
setBgColor(getWheelNumberColor(parseInt(wheelNumber, 10)));
setMostRecentSpinResultText(parseInt(wheelNumber, 10) === 37 ? "00" : parseInt(wheelNumber, 10));
}
});
【F:src/components/roulette/SpinResult.js†L17-L21】
- MostRecentSpinResults.js adds a listener outside any
useEffect, causing one to be created on every render:
rouletteContractEvents.on('ExecutedWager', (playerAddress, wheelNumber) => {
if (playerAddress === props.playerAddress) {
const copySpinResults = [...spinResults];
copySpinResults.push(parseInt(wheelNumber, 10));
setSpinResults(copySpinResults.slice(-20));
}
});
【F:src/components/roulette/MostRecentSpinResults.js†L11-L16】
Suggested fix: Register event listeners in useEffect with an empty dependency array and clean them up in the return function using .off.
NumbersHitTracker and CompletionsCounter include state they modify in their useEffect dependency arrays. Each update triggers another effect, causing continuous polling.
- NumbersHitTracker.js:
useEffect(() => {
setTimeout(async () => {
const currentNumbers = await getPlayerNumberCompletionSetCurrent(props.playerAddress);
setCurrentSet(new Set(currentNumbers));
}, 1000);
}, [props.playerAddress, currentSet]);
【F:src/components/roulette/NumbersHitTracker.js†L11-L16】
- CompletionsCounter.js:
useEffect(() => {
setTimeout(async () => {
const count = await getPlayerNumberCompletionSetsCounter(props.playerAddress);
setCompletionsCount(count);
}, 1000);
}, [props.playerAddress, completionsCount]);
【F:src/components/roulette/CompletionsCounter.js†L18-L23】
Suggested fix: Remove the state variables from the dependency arrays or switch to setInterval with proper cleanup so the effect runs predictably.
simulatePlayingGame.js nests setInterval calls without clearing them, creating runaway timers:
setInterval(() => {
let betsPlaced = 0;
setInterval(() => {
if (betsPlaced++ >= NUMBER_OF_BETS_TO_PLACE) return;
...
}, SECONDS_BETWEEN_BET_PLACEMENTS * 1000);
const spinButtonElement = document.getElementById("spin-button");
spinButtonElement.click();
}, (NUMBER_OF_BETS_TO_PLACE + 1) * SECONDS_BETWEEN_BET_PLACEMENTS * 1000);
【F:src/common/simulatePlayingGame.js†L14-L39】
Suggested fix: Use a single interval or clear the inner interval after betting. Alternatively, drive the demo with explicit timeouts.
The line break in HouseInfo.js contains a space (< br />) which prevents proper rendering:
House Balance
< br />
{formattedChainNumber(houseBalance, 3)}
【F:src/components/roulette/HouseInfo.js†L33-L39】
Suggested fix: Replace with <br />.
These issues are not recorded in KNOWN_BUGS.md and may still affect runtime behavior. Addressing them will improve the application's reliability.
Below is a list of issues discovered in the current codebase along with suggested fixes.
SpinResult.js registers a listener to the ExecutedWager event inside a useEffect whose dependency array includes state values. Each render attaches a new listener without removal, leading to duplicate callbacks.
Suggestion: Wrap the subscription in a useEffect with an empty dependency array and return a cleanup function that removes the listener.
MostRecentSpinResults.js calls rouletteContractEvents.on directly inside the component body, meaning a new listener is added on every render.
Suggestion: Move the registration into useEffect with a cleanup to remove the listener on unmount.
NumbersHitTracker.js and CompletionsCounter.js both include their own state variables in the useEffect dependency arrays while updating those same states. This schedules a new fetch every time the data updates, causing unnecessary loops.
Suggestion: Remove the state variables from the dependency arrays and only depend on the player address.
SpinButton.js sets shouldDisplayExtraMessage inside a useEffect that also uses the value to set zIndex and color. Because the previous state is used, the rendered values may be one step behind.
Suggestion: Compute a local variable for the desired flag and use it for all state updates inside the effect.
HouseInfo.js and PlayerInfo.js contain < br /> with a leading space, producing invalid JSX.
Suggestion: Replace < br /> with <br />.
NumberHitGameCounter.js has 220 listed twice in the outline breakpoints array which appears to be accidental.
Suggestion: Remove the duplicate entry so the array progresses as intended.