diff --git a/frontend/package.json b/frontend/package.json index 59e6967..d5496dc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/react-query": "^5.59.20", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "wagmi": "^2.12.27" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index dc2796a..e8a5270 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,26 +1,31 @@ import {useEffect, useState} from 'react' import './App.css' -import {local_accounts} from './clients/testClient' +// import wordle hooks import { getAlphabet, getWDTBalance, getPlayerGuesses, - getHitmap, - canPlay, - useFaucet, initAttempts, - tryGuess + tryGuess, } from './hooks/WordleHooks' + +// import components import {Alphabet} from './components/Alphabet/Alphabet' import { GameSpace } from './components/GameSpace/GameSpace' import { ApprovalModal } from './components/ApprovalModal/ApprovalModal' import { LowFunds } from './components/LowFunds/LowFunds' import { AlreadyPlayed } from './components/AlreadyPlayed/AlreadyPlayed' +// wagmi connection +import { ConnectWallet } from './clients/walletClient/ConnectWallet' +import { wagmiConfig } from '../wagmi.config' +import { WagmiProvider } from 'wagmi' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + function App() { // Current account and respetive variables - const [currentAcc, setCurrentAcc] = useState(local_accounts[0]) + const [currentAddress, setCurrentAddress] = useState(undefined) const [alphabet, setAlphabet] = useState(undefined) // TODO: remove after testing and prototyping @@ -39,17 +44,22 @@ function App() { // Gets the current account's WDT balance, eligibility to play, player attempts, guesses and alphabet. // this is constantly pinging the network, so i'm unsure if this is the right way to go useEffect(() => { - if (currentAcc) { - getWDTBalance(currentAcc).then(setWdtBalance) - - getAlphabet(currentAcc).then(res => { - if (res.length === 0) setIsPlaying(false) - else setIsPlaying(true) - setAlphabet(res) - }) - getPlayerGuesses(currentAcc).then(setPlayerGuesses) + + const getParams = async (address) => { + const balance = await getWDTBalance(address) + const alphabet = await getAlphabet(address) + const playerGuesses = await getPlayerGuesses(address) + if (alphabet.length > 0) {setIsPlaying(true)} + if (balance) setWdtBalance(balance) + if (alphabet) setAlphabet(alphabet) + if (playerGuesses) setPlayerGuesses(playerGuesses) + } + + if (currentAddress) { + getParams(currentAddress) } - }, [currentAcc, playerGuesses]) + + }, [currentAddress]) // handle the guess input const handleGuessChange = (e) => setGuess(e.target.value) @@ -57,11 +67,12 @@ function App() { // handle new guess const handleNewGuess = (account, guess) => { tryGuess(account, guess) + setTimeout(() => getPlayerGuesses(account).then(setPlayerGuesses), 500) } const handleInitAttempts = async (account) => { try { - const res = await initAttempts(account) + await initAttempts(account) } catch (err) { // placeholder for token faucet user flow if (err.message.includes("You don't have enough tokens to play")) { @@ -81,38 +92,33 @@ function App() { } return ( - <> + +
- +
+ +
{/* Modals for approving transactions and messages */} - {approvalModal && setApprovalModal(false)}/>} - {lowFundsWarn && setLowFundsWarn(false)}/>} + {approvalModal && setApprovalModal(false)}/>} + {lowFundsWarn && setLowFundsWarn(false)}/>} {playedToday && setPlayedToday(false)}/>} - - {/* Playground: list accounts to experiment */} -

Here are some addresses:

-

WDT Balance: {wdtBalance ? `${wdtBalance.toString()} WDT` : '???'}

{/* Game space */} {!isPlaying ? (
- +
) : (
- +
)}
- +
+
) } diff --git a/frontend/src/clients/publicClient.ts b/frontend/src/clients/publicClient.ts new file mode 100644 index 0000000..0796f9a --- /dev/null +++ b/frontend/src/clients/publicClient.ts @@ -0,0 +1,24 @@ +import { createPublicClient, http, publicActions, walletActions, getContract } from 'viem'; +import { foundry } from 'viem/chains'; +import WordleABI from '../abis/WordleABI.json'; +import ERC20ABI from '../abis/ERC20ABI.json'; + +// Public client +export const publicClient = createPublicClient({ + chain: foundry, + transport: http(), +}); + +// Wordle3 Contract +export const publicWordle = getContract({ + address: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0', + abi: WordleABI, + client: publicClient, +}); + +// Wordle Token contract +export const publicWdt = getContract({ + address: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512', + abi: ERC20ABI, + client: publicClient, +}); diff --git a/frontend/src/clients/walletClient/ConnectWallet.tsx b/frontend/src/clients/walletClient/ConnectWallet.tsx new file mode 100644 index 0000000..4d1098a --- /dev/null +++ b/frontend/src/clients/walletClient/ConnectWallet.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import { useConnect } from 'wagmi' +import { useAccount, useDisconnect, useEnsAvatar, useEnsName } from 'wagmi' + + +/** + * Account component that displays connected account and passes on + * the address to the parent component for interaction. + */ +function Account({onAddressChange}) { + const { address } = useAccount() + const { disconnect } = useDisconnect() + const { data: ensName } = useEnsName({ address }) + const { data: ensAvatar } = useEnsAvatar({ name: ensName! }) + + if (address) onAddressChange(address); + + return ( +
+ {ensAvatar && ENS Avatar} + {address &&
{ensName ? `${ensName} (${address})` : address}
} + +
+ ) +} + +/** + * Account component that displays available wallet connectors + * Connectors are also pulled from ../../wagmi.config.ts + */ +function WalletOptions() { + const { connectors, connect } = useConnect() + + return connectors.map((connector) => ( + + )) +} + +/** + * Wrapper for the login user flow. + * Receives onAddressChange from parent component to lift the address + * for smart contract operations + */ +export function ConnectWallet({onAddressChange}) { + const { isConnected } = useAccount() + if (isConnected) return + return +} \ No newline at end of file diff --git a/frontend/src/hooks/WordleHooks.ts b/frontend/src/hooks/WordleHooks.ts index d3ddf88..77593fd 100644 --- a/frontend/src/hooks/WordleHooks.ts +++ b/frontend/src/hooks/WordleHooks.ts @@ -5,36 +5,36 @@ import ERC20ABI from '../abis/ERC20ABI.json' // Get number of remaining attempts a player still has export async function getAttempts(account) { - const attempts = await wordle.read.getPlayerAttempts([account.address]) - return attempts.toString() + const attempts = await wordle.read.getPlayerAttempts([account]) + return formatEther(attempts) } // Get the current state of the player's alphabet hitmap export async function getAlphabet(account) { - return await wordle.read.getAlphabet([account.address]) + return await wordle.read.getAlphabet([account]) } // Get the current player hitmap state export async function getHitmap(account) { - return await wordle.read.getHiddenWord([account.address]) + return await wordle.read.getHiddenWord([account]) } // Gets the WDT balance of a player export async function getWDTBalance(account) { - const res = await wdt.read.balanceOf([account.address]) + const res = await wdt.read.balanceOf([account]) return formatEther(res) } // Gets player used guesses array export async function getPlayerGuesses(account) { - const res = await wordle.read.getPlayerGuesses([account.address]) + const res = await wordle.read.getPlayerGuesses([account]) return res } // Checks if a player is eligible to play export async function canPlay(account) { - return await wordle.read.canPlay([account.address]) + return await wordle.read.canPlay([account]) } // Give a player some WDT @@ -44,7 +44,7 @@ export async function useFaucet(account) { address: wordle.address, abi: WordleABI, functionName: 'tokenFaucet', - args: [account.address], + args: [account], }) await client.writeContract(request) } @@ -60,12 +60,12 @@ export async function approveGame(account) { args: [wordle.address, 200 * decimals], }) await client.writeContract(request) - const allowance = await wdt.read.allowance([account.address, wordle.address]) + const allowance = await wdt.read.allowance([account, wordle.address]) } // Initialize the player's attempts export async function initAttempts(account) { - const allowance = await wdt.read.allowance([account.address, wordle.address]) + const allowance = await wdt.read.allowance([account, wordle.address]) // simulate/validate the contract to ensure contract exists // and is valid (+info: https://viem.sh/docs/contract/writeContract) @@ -74,7 +74,7 @@ export async function initAttempts(account) { address: wordle.address, abi: WordleABI, functionName: 'initAttempts', - args: [account.address], + args: [account], }) if (request) await client.writeContract(request) @@ -92,7 +92,7 @@ export async function tryGuess(account, guess) { address: wordle.address, abi: WordleABI, functionName: 'tryGuess', - args: [guess, account.address], + args: [guess, account], }) if (request) await client.writeContract(request) diff --git a/frontend/wagmi.config.ts b/frontend/wagmi.config.ts new file mode 100644 index 0000000..4752cd9 --- /dev/null +++ b/frontend/wagmi.config.ts @@ -0,0 +1,15 @@ +import { http, createConfig } from 'wagmi' +import { anvil } from 'wagmi/chains' +import { injected, metaMask, safe } from 'wagmi/connectors' + +export const wagmiConfig = createConfig({ + chains: [anvil], + transports: { + [anvil.id]: http(), + }, + connectors: [ + injected(), + metaMask(), + safe(), + ] +}) \ No newline at end of file