diff --git a/README.md b/README.md index ebe66ef..5f97f72 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,95 @@ -# Dice Demonstration +# PKDice -Random numbers, dice images and animations can be taken from the dice challenge repo here:
-https://github.com/scaffold-eth/se-2-challenges/tree/challenge-3-dice-game +## Description -Live view of this repo for context:
-https://buidlguidl-g33iv38ol-brycehytans-projects.vercel.app/dice +This game is designed to demonstrate the difficulty of guessing or brute-forcing a wallet's private key. A host creates a game with a reward and a concealed private key, and participants join the race to guess this hidden key. -### Project Overview -This will become a demonstration to use during presentations with a live audience using their phones to participate. A host will control the metrics of the game, and control a private key that will contain some value the users are trying to win. The host determines how many dice can be rolled and what rolling mode the users will be using. The users will try to roll their dice to match the hosts. If they do, some of the prize money is sent to them along with a winning message. Eventually the hosts number will have too many characters (dice) for the users to be able to match. This will show the security of a 64 character private key. +## Project Overview + +This project is a live demonstration intended for use during presentations with a live audience using their phones to participate. A host controls the game metrics and a private key that contains some value the users are trying to win. The host determines the number of dice that can be rolled and the rolling mode the users will use. Participants try to roll their dice to match the host's. If they succeed, they receive a portion of the prize money and a winning message. As the number of dice increases, it becomes increasingly difficult for users to match the host's dice, demonstrating the security of a 64-character private key. + +## Features ### Dice -- Each dice will have a value in HEX (0-F) -- Dice images and rolling animations can be grabbed from the Dice Challenge repo at the top of this page. +- Each die has a value in HEX (0-F). +- Dice images and rolling animations can be sourced from the Dice Challenge [repo](https://github.com/scaffold-eth/se-2-challenges/tree/challenge-3-dice-game). ### Host -- The host can at any time change the number of dice, in a range from 1-64. -- The host can update the rolling mode. -- The host can enable/disable user rolling. This may be necessary for updating the dice count and modes. -- The host computer will be on display at the presentations. What information is necessary to show/hide? -- Maybe the host can do an initial roll on the big screen to get their number? -- We should display winners on the host screen so everyone can see when there was a winner. +- Can change the number of dice (1-64 range). +- Can update the rolling mode. +- Can enable/disable user rolling (useful for updating dice count and modes). +- Host computer will be displayed during presentations. +- Initial roll can be displayed on the big screen to generate the host's number. +- Winner is displayed on the host screen for visibility. ### Users -- Unsure of the number of users we will have at one time, a good guess is in the 10-30 range. -- Users will get a burner wallet when they connect that we will use for display purposes, and to send any winnings. -- The users should see a cool dice rolling display on their (phone) screens. This will get tricky as more dice are added, up to a max of 64. -- If they match the hosts, a cool winning message will be display and some of the value in the hosts private key will be sent to them. Need a cool message they can show off to others. -- All the user's dice will roll at the same time. e.g if the count of dice is currently 24, and example roll from a user would be F18053525893D3537EAB615C -- They should see a cool animation when rolling +- Each user gets a burner wallet upon connecting for display purposes and to receive winnings. +- Users see a dice rolling display on their phones, which scales with the number of dice (up to 64). +- Cool winning message and some value from the host's private key are sent to users upon winning. +- All user dice roll simultaneously. ### Roll Modes -- Users click a roll button to manually roll the dice once. This will mostly be used when there is a small number of dice. -- Users click a button to start the continous rolling of dice. It will automatically keep rolling until manually stopped or it finds the match. -- Users click a button to start continous rolling, but this time all users are coordinating trying to guess different numbers of the hosts instead of racing. Namespaces can be used here. Austin can put together a quick algorithm to accompolish this, or feel free to tackle it yourself. +- **Manual Roll:** Users click a roll button to manually roll the dice once. Best for a small number of dice. +- **Auto Rolling:** Users click a button to start continuous rolling until manually stopped or a hidden character(s) are found. +- **Brute Continuous Rolling:** Users click a button to start rolling continuously as quickly as possible, coordinating with each other to guess different numbers of the host’s dice. + +## Instructions for Hosts + +1. **Setup:** Configure the number of dice and rolling mode. +3. **Enable Rolling:** Allow users to start rolling. +4. **Monitor:** Display winners on the host screen for everyone to see. +5. **Adjust Settings:** Enable/disable user rolling as necessary to update dice count and modes. + +## Instructions for Users + +1. **Connect:** Join the game using your phone. +2. **Burner Wallet:** Receive a burner wallet for display and receiving winnings. +3. **Rolling Dice:** Click the roll button to roll the dice once. +4. **Winning:** If your roll matches the host’s, receive a cool winning message and some of the host’s prize money. + +## Rolling Modes -## Contributing +1. **Manual Roll:** + - Click to roll the dice once. +2. **Auto Rolling:** + - Click to start rolling until stopped or a match is found. +3. **Brute Rolling:** + - Click to start rolling as quickly as possible. -Step by step "fork-and-pull" Github contributing using CLI refresher here: -https://gist.github.com/ZakGriffith/69d1eb8baebddd7d370b87a65a7e3ec0 +## Credits + +- Dice images and rolling animations sourced from the Dice Challenge [repo](https://github.com/scaffold-eth/se-2-challenges/tree/challenge-3-dice-game). + + +## License + +This project is licensed under the MIT License. See the LICENSE file for more information. --- +## Setting Up Locally + + +1. Clone this repo & install dependencies + +``` +git clone https://github.com/Buidlguidl/private-key-dice.git +cd private-key-dice +yarn install +``` -### initial notes from austin: +2. Run the backend in the first terminal: -the essence of what I want to demonstrate is that technically you can guess someone's private key but if you use enough bytes randomness it becomes impossible +``` +yarn backend +``` -the idea comes from my buddy griffin who does a "guess the number" kind of game that is similar -- I want to use our amazing hex dice from mr dee and SE2 to make a pretty mobile experience that is controlled from and admin screen +This command starts a local backend on port `6001` and can be used for testing and development. You can customize the configuration in `backend.config.ts` and add your very own `.env` file following the `packages/backend/.env.example`. -this would be a demo for technical groups and normies - I lock money up in a private key where we know all of the bytes except one and everyone has to click to roll the dice and if they land on the right hex character it makes a private key, checks if there is money on it, and sweeps it with a celebration screen +3. On a third terminal, start your NextJS app: -then from the admin side I lock up a little money and we roll two dice and then three and then four - we'll have to experiment with where it gets so difficult a room wont be able to get it and then we can allow them to upgrade their roller to *automatic roll* and *group brute force roll* where everyone works on different sections trying to find the solution +``` +yarn start +``` -the payoff is when I have 64 dice on a single screen and I show some smart contract with billions of dollars in it -- all your phone has to do is guess the right 64 hex characters and that billion dollars is yours +Visit your app on: `http://localhost:3000`. Change `isLocal` variable to `true` in the config in `packages/nextjs/server.config.ts`. You can also add your very own `.env.local` file following the `packages/nextjs/.env.example`. \ No newline at end of file diff --git a/packages/backend/.env.example b/packages/backend/.env.example index e69de29..8a97864 100644 --- a/packages/backend/.env.example +++ b/packages/backend/.env.example @@ -0,0 +1,11 @@ + +# Create a database at https://www.mongodb.com/ +MONGO_URL= + +# Hard string for for signing verification tokens +JWT_SECRET= + +PORT=6001 + +# create a realtime app at https://ably.com/ +ABLY_API_KEY= \ No newline at end of file diff --git a/packages/backend/backend.config.ts b/packages/backend/backend.config.ts new file mode 100644 index 0000000..e3e2f4e --- /dev/null +++ b/packages/backend/backend.config.ts @@ -0,0 +1,8 @@ +const backendConfig = { + ablyApi: "Fbq6sA.xC_GgQ:a9uQJKCunyvMmh1nVvcZaZFuZw_2LYbcHvTno5uPV5c", + mongo_url: "mongodb+srv://dicedemo:JLFrqOwOfDe2lVdv@cluster0.8rq1k5q.mongodb.net/?retryWrites=true&w=majority", + port: "6001", + jwt_secret: "superhardstring", +}; + +export default backendConfig; diff --git a/packages/backend/controllers/Admin.ts b/packages/backend/controllers/Admin.ts index 6c372f6..1d406e3 100644 --- a/packages/backend/controllers/Admin.ts +++ b/packages/backend/controllers/Admin.ts @@ -4,8 +4,9 @@ import bcrypt from "bcrypt"; import { Request, Response } from "express"; import jwt from "jsonwebtoken"; import { ably } from ".."; +import backendConfig from "../backend.config"; -const JWT_SECRET = process.env.JWT_SECRET || "superhardstring"; +const JWT_SECRET = process.env.JWT_SECRET || backendConfig.jwt_secret; async function generateUniqueInvite(length: number) { let invites = await Invites.findOne(); @@ -41,7 +42,7 @@ async function generateUniqueInvite(length: number) { export const createGame = async (req: Request, res: Response) => { try { - const { diceCount, privateKey, hiddenPrivateKey, mode, adminAddress } = req.body; + const { diceCount, hiddenPrivateKey, mode, adminAddress } = req.body; const salt = await bcrypt.genSalt(); // const privateKeyHash = await bcrypt.hash(privateKey, salt); @@ -52,7 +53,6 @@ export const createGame = async (req: Request, res: Response) => { inviteCode: await generateUniqueInvite(8), diceCount, mode, - privateKey, hiddenPrivateKey, }); @@ -69,7 +69,7 @@ export const createGame = async (req: Request, res: Response) => { export const restartWithNewPk = async (req: Request, res: Response) => { try { - const { diceCount, privateKey, hiddenPrivateKey, adminAddress } = req.body; + const { diceCount, hiddenPrivateKey, adminAddress } = req.body; const { id } = req.params; const game = await Game.findById(id); @@ -78,7 +78,6 @@ export const restartWithNewPk = async (req: Request, res: Response) => { } game.diceCount = diceCount; - game.privateKey = privateKey; game.hiddenPrivateKey = hiddenPrivateKey; game.mode = "manual"; game.adminAddress = adminAddress; diff --git a/packages/backend/controllers/Player.ts b/packages/backend/controllers/Player.ts index 50af732..b3f0ffb 100644 --- a/packages/backend/controllers/Player.ts +++ b/packages/backend/controllers/Player.ts @@ -2,8 +2,9 @@ import Game from "../models/Game"; import { Response, Request } from "express"; import jwt from "jsonwebtoken"; import { ably } from ".."; +import backendConfig from "../backend.config"; -const JWT_SECRET = process.env.JWT_SECRET || "superhardstring"; +const JWT_SECRET = process.env.JWT_SECRET || backendConfig.jwt_secret; export const join = async (req: Request, res: Response) => { try { @@ -36,8 +37,3 @@ export const join = async (req: Request, res: Response) => { res.status(500).json({ error: (err as Error).message }); } }; - -export const leave = () => {}; - -export const sweepPrize = () => {}; -export const markSlotsAsFoundPerPlayer = () => {}; diff --git a/packages/backend/index.ts b/packages/backend/index.ts index 03fa96c..c3c2bc8 100644 --- a/packages/backend/index.ts +++ b/packages/backend/index.ts @@ -8,6 +8,7 @@ import gameRoutes from "./routes/game"; import http from "http"; import Ably from "ably"; import path = require("path"); +import backendConfig from "./backend.config"; declare global { namespace Express { @@ -28,12 +29,12 @@ app.use(cors()); /**Ably Setup */ -export const ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); +export const ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY || backendConfig.ablyApi }); const server = http.createServer(app); /* MONGOOSE SETUP */ -const PORT = process.env.PORT || 6001; -const MONGO_URL = process.env.MONGO_URL || ""; +const PORT = process.env.PORT || backendConfig.port; +const MONGO_URL = process.env.MONGO_URL || backendConfig.mongo_url; app.use("/admin", adminRoutes); app.use("/player", playerRoutes); diff --git a/packages/backend/middleware/auth.ts b/packages/backend/middleware/auth.ts index 1f83874..db2a4de 100644 --- a/packages/backend/middleware/auth.ts +++ b/packages/backend/middleware/auth.ts @@ -1,7 +1,8 @@ import jwt from "jsonwebtoken"; import { Request, Response } from "express"; +import backendConfig from "../backend.config"; -const JWT_SECRET = process.env.JWT_SECRET || "superhardstring"; +const JWT_SECRET = process.env.JWT_SECRET || backendConfig.jwt_secret; export const verifyToken = async (req: Request, res: Response, next: () => void) => { try { diff --git a/packages/backend/models/Game.ts b/packages/backend/models/Game.ts index ba6b394..aee0af7 100644 --- a/packages/backend/models/Game.ts +++ b/packages/backend/models/Game.ts @@ -26,10 +26,6 @@ const gameSchema = new mongoose.Schema( enum: ["auto", "manual", "brute"], required: true, }, - privateKey: { - type: String, - required: true, - }, hiddenPrivateKey: { type: String, required: true, diff --git a/packages/backend/models/Player.ts b/packages/backend/models/Player.ts index f91d7ad..545db0f 100644 --- a/packages/backend/models/Player.ts +++ b/packages/backend/models/Player.ts @@ -26,10 +26,6 @@ const playerSchema = new mongoose.Schema( enum: ["auto", "manual"], required: true, }, - privateKey: { - type: String, - required: true, - }, type: { key: String, value: String, diff --git a/packages/nextjs/.env.example b/packages/nextjs/.env.example index 411f08e..6736dd6 100644 --- a/packages/nextjs/.env.example +++ b/packages/nextjs/.env.example @@ -11,4 +11,7 @@ # More info: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables NEXT_PUBLIC_ALCHEMY_API_KEY= NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID= -NEXT_PUBLIC_ABLY_API_KEY= \ No newline at end of file + +# create a realtime app at https://ably.com/ +# Api must tally with backend +NEXT_PUBLIC_ABLY_API_KEY= diff --git a/packages/nextjs/components/dicedemo/GameCreateForm.tsx b/packages/nextjs/components/dicedemo/GameCreateForm.tsx index 89ccda9..66273f6 100644 --- a/packages/nextjs/components/dicedemo/GameCreateForm.tsx +++ b/packages/nextjs/components/dicedemo/GameCreateForm.tsx @@ -9,7 +9,6 @@ import { notification } from "~~/utils/scaffold-eth"; interface FormData { diceCount: number; mode: "auto" | "manual" | "brute"; - privateKey: string; hiddenPrivateKey: string; adminAddress: string | undefined; } @@ -25,7 +24,6 @@ const GameCreationForm = () => { diceCount: 1, mode: "manual", hiddenPrivateKey: "*" + initialPrivateKey.slice(1), - privateKey: initialPrivateKey, adminAddress, }); @@ -82,7 +80,6 @@ const GameCreationForm = () => { setFormData({ diceCount: 0, mode: "auto", - privateKey: loadBurnerSK(), hiddenPrivateKey: "", adminAddress, }); diff --git a/packages/nextjs/components/dicedemo/GameJoinForm.tsx b/packages/nextjs/components/dicedemo/GameJoinForm.tsx index c2265f0..83f71d6 100644 --- a/packages/nextjs/components/dicedemo/GameJoinForm.tsx +++ b/packages/nextjs/components/dicedemo/GameJoinForm.tsx @@ -3,8 +3,7 @@ import { useRouter } from "next/router"; import { InputBase } from "../scaffold-eth"; import QrReader from "react-qr-reader-es6"; import { useAccount } from "wagmi"; -import serverConfig from "~~/server.config"; -import { saveGameState } from "~~/utils/diceDemo/game"; +import { joinGame } from "~~/utils/diceDemo/apiUtils"; import { notification } from "~~/utils/scaffold-eth"; const GameJoinForm = ({ @@ -25,24 +24,14 @@ const GameJoinForm = ({ }; const { address: playerAddress } = useAccount(); - const serverUrl = serverConfig.isLocal ? serverConfig.localUrl : serverConfig.liveUrl; const handleJoinGame = async (invite: string) => { setLoading(true); - const response = await fetch(`${serverUrl}/player/join`, { - method: "PATCH", - headers: { - Authorization: `Bearer`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ inviteCode: invite, playerAddress }), - }); - - const updatedGame = await response.json(); - saveGameState(JSON.stringify(updatedGame)); - setLoading(false); - if (updatedGame.error) { - notification.error(updatedGame.error); + try { + await joinGame(invite, playerAddress as string); + setLoading(false); + } catch (error) { + setLoading(false); return; } await router.push({ diff --git a/packages/nextjs/components/dicedemo/RestartWithNewPk.tsx b/packages/nextjs/components/dicedemo/RestartWithNewPk.tsx index c97ed69..ee66aa8 100644 --- a/packages/nextjs/components/dicedemo/RestartWithNewPk.tsx +++ b/packages/nextjs/components/dicedemo/RestartWithNewPk.tsx @@ -9,7 +9,6 @@ import { notification } from "~~/utils/scaffold-eth"; interface FormData { diceCount: number; - privateKey: string; hiddenPrivateKey: string; adminAddress: string | undefined; } @@ -27,7 +26,6 @@ const RestartWithNewPk = ({ isOpen, setIsOpen }: { isOpen: boolean; setIsOpen: D const [formData, setFormData] = useState({ diceCount: 0, hiddenPrivateKey: "", - privateKey: "", adminAddress: undefined, }); @@ -92,7 +90,6 @@ const RestartWithNewPk = ({ isOpen, setIsOpen }: { isOpen: boolean; setIsOpen: D diceCount: 1, adminAddress: account.address, hiddenPrivateKey: "*" + pk.slice(1), - privateKey: pk, })); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/packages/nextjs/pages/game/[id].tsx b/packages/nextjs/pages/game/[id].tsx index 1f9cb95..a9cd519 100644 --- a/packages/nextjs/pages/game/[id].tsx +++ b/packages/nextjs/pages/game/[id].tsx @@ -4,6 +4,7 @@ import { useRouter } from "next/router"; import Ably from "ably"; import QRCode from "qrcode.react"; import CopyToClipboard from "react-copy-to-clipboard"; +import { privateKeyToAccount } from "viem/accounts"; import { useAccount, useBalance } from "wagmi"; import { CheckCircleIcon, @@ -16,17 +17,19 @@ import PlayerAnnouncement from "~~/components/dicedemo/PlayerAnnoucement"; import RestartWithNewPk from "~~/components/dicedemo/RestartWithNewPk"; import { Address } from "~~/components/scaffold-eth"; import { Price } from "~~/components/scaffold-eth/Price"; +import { loadBurnerSK } from "~~/hooks/scaffold-eth"; import useGameData from "~~/hooks/useGameData"; import useSweepWallet from "~~/hooks/useSweepWallet"; +import serverConfig from "~~/server.config"; import { Game } from "~~/types/game/game"; import { kickPlayer, pauseResumeGame, toggleMode, varyHiddenPrivatekey } from "~~/utils/diceDemo/apiUtils"; +import { joinGame } from "~~/utils/diceDemo/apiUtils"; import { calculateLength, generateRandomHex } from "~~/utils/diceDemo/gameUtils"; -import { privateKeyToAccount } from "viem/accounts"; function GamePage() { const router = useRouter(); const { id } = router.query; - const ablyApiKey = process.env.NEXT_PUBLIC_ABLY_API_KEY; + const ablyApiKey = process.env.NEXT_PUBLIC_ABLY_API_KEY || serverConfig.ably_api_key; const { loadGameState, updateGameState } = useGameData(); const { address } = useAccount(); @@ -49,15 +52,14 @@ function GamePage() { const [bruteRolling, setBruteRolling] = useState(false); const [screenwidth, setScreenWidth] = useState(768); const [isHacked, setIsHacked] = useState(false); + const [isAdmin, setIsAdmin] = useState(false); + const [isPlayer, setIsPlayer] = useState(false); const prize = useBalance({ address: game?.adminAddress }); const { sweepWallet, isSweeping, sweepMessage } = useSweepWallet({ game, token }); const length = calculateLength(game?.diceCount as number); - const isAdmin = address == game?.adminAddress; - const isPlayer = game?.players?.includes(address as string); - const rollTheDice = () => { if (game) { setIsRolling(true); @@ -112,24 +114,62 @@ function GamePage() { }; useEffect(() => { - const { token, game: gameState } = loadGameState(); - setGame(gameState); - setToken(token); - setIsUnitRolling(Array.from({ length: gameState?.diceCount }, () => false)); + const loadGame = async () => { + const game = loadGameState(); - if (typeof window !== "undefined") { - const currentUrl = window.location.href; - const rootPath = new URL(currentUrl).origin; - setInviteUrl(rootPath + "?invite=" + gameState?.inviteCode); + /* Uncomment to allow a kicked player join back on web refresh */ + // if (game && game.game && !game.game.players.includes(address)) { + // if (address && id) { + // await joinGame(id as string, address); + // setIsPlayer(true); + // } + // } + + if (game && game.game) { + const { token, game: gameState } = game; + if (address === gameState.adminAddress) setIsAdmin(true); + if (gameState.players.includes(address)) setIsPlayer(true); + setToken(token); + setGame(gameState); + setIsUnitRolling(Array.from({ length: gameState?.diceCount }, () => false)); + + if (typeof window !== "undefined") { + const currentUrl = window.location.href; + const rootPath = new URL(currentUrl).origin; + setInviteUrl(rootPath + "?invite=" + gameState?.inviteCode); + } + } else { + if (address && id) { + await joinGame(id as string, address); + setIsPlayer(true); + return; + } + } + }; + + loadGame(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [address, id]); + + useEffect(() => { + if (!game && isPlayer) { + const game = loadGameState(); + if (game && game.game) { + const { token, game: gameState } = game; + setGame(gameState); + setToken(token); + } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [isPlayer]); useEffect(() => { let isHiiddenChars; + let pk; if (rolled && rolledResult.length > 0 && game?.hiddenPrivateKey) { - const pk: `0x{string}` = `0x${rolledResult.join("")}${game?.hiddenPrivateKey.replaceAll("*", "")}` as `0x{string}`; + pk = `0x${rolledResult.join("")}${game?.hiddenPrivateKey.replaceAll("*", "")}` as `0x{string}`; const account = privateKeyToAccount(pk); isHiiddenChars = account.address == game?.adminAddress; } @@ -141,7 +181,7 @@ function GamePage() { setSpinning(false); setCongratsOpen(true); setIsHacked(true); - sweepWallet(game?.privateKey as string); + sweepWallet(pk as string); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [rolledResult]); @@ -224,6 +264,11 @@ function GamePage() { setIsRolling(false); setHostAnnOpen(true); } + if (game?.players.includes(address as string)) { + setIsPlayer(true); + } else { + setIsPlayer(false); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [game]); @@ -247,7 +292,7 @@ function GamePage() { {isAdmin && (
-
+
Copy Invite Url {inviteUrlCopied ? ( @@ -304,22 +349,20 @@ function GamePage() { )}
-
+
- Status: {game.status} - + Status: {game.status} pauseResumeGame(game, token)} - checked={game?.status == "ongoing"} + checked={game?.status === "ongoing"} />
- Mode: {game.mode} - + Mode: {game.mode}
@@ -339,9 +382,9 @@ function GamePage() { type="radio" name="radio-10" className="radio checked:bg-blue-500" - checked={game?.mode == "manual"} + checked={game?.mode === "manual"} onClick={() => { - if (game?.mode != "manual") toggleMode(game, "manual", token); + if (game?.mode !== "manual") toggleMode(game, "manual", token); }} /> @@ -351,9 +394,9 @@ function GamePage() { type="radio" name="radio-10" className="radio checked:bg-blue-500" - checked={game?.mode == "brute"} + checked={game?.mode === "brute"} onClick={() => { - if (game?.mode != "brute") toggleMode(game, "brute", token); + if (game?.mode !== "brute") toggleMode(game, "brute", token); }} /> @@ -362,11 +405,11 @@ function GamePage() {
{screenwidth <= 768 && (
-
-

PRIVATE KEY

+
+

PRIVATE KEY

-

+

{Object.values(game?.hiddenPrivateKey)}

@@ -374,19 +417,19 @@ function GamePage() { className="btn btn-sm btn-ghost tooltip tooltip-left" data-tip="increase" onClick={() => { - varyHiddenPrivatekey(game, token, "increase"); + varyHiddenPrivatekey(game, token, "increase", loadBurnerSK().toString().substring(2)); }} > -
@@ -399,7 +442,7 @@ function GamePage() { Winner
)} - {/* {isAdmin && game.winner && ( + {isAdmin && game.winner && ( - )} */} + )}
{screenwidth > 768 && (
{isAdmin && (
-
-

PRIVATE KEY

+
+

PRIVATE KEY

@@ -426,19 +469,19 @@ function GamePage() { className="btn btn-sm btn-ghost tooltip tooltip-left" data-tip="increase" onClick={() => { - varyHiddenPrivatekey(game, token, "increase"); + varyHiddenPrivatekey(game, token, "increase", loadBurnerSK().toString().substring(2)); }} > -

@@ -447,17 +490,15 @@ function GamePage() {
-

+

PLAYERS: {game?.players.length}

{isAdmin && ( -
+
{game?.players?.map((player: string) => (
-
+
{isAdmin && (
)}
- {isPlayer && (
{" "} +
{(isHacked || game.winner) && (
-

PLAYERS

+

PLAYERS

- {game?.players?.map((player: string) => ( -
-
+ {game.players.map(player => ( +
+
{isAdmin && (