Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Init wagmi and connect wallet functionality #17

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
72 changes: 39 additions & 33 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -39,29 +44,35 @@ 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)

// 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")) {
Expand All @@ -81,38 +92,33 @@ function App() {
}

return (
<>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={new QueryClient()}>
<div>

<header>
<ConnectWallet onAddressChange={setCurrentAddress} />
</header>
{/* Modals for approving transactions and messages */}
{approvalModal && <ApprovalModal account={currentAcc} close={() => setApprovalModal(false)}/>}
{lowFundsWarn && <LowFunds account={currentAcc} close={() => setLowFundsWarn(false)}/>}
{approvalModal && <ApprovalModal account={currentAddress} close={() => setApprovalModal(false)}/>}
{lowFundsWarn && <LowFunds account={currentAddress} close={() => setLowFundsWarn(false)}/>}
{playedToday && <AlreadyPlayed close={() => setPlayedToday(false)}/>}


{/* Playground: list accounts to experiment */}
<h4>Here are some addresses:</h4>
<select onChange={(e) => setCurrentAcc(local_accounts.find(a => a.address === e.target.value))}>
<option value="">Select an address</option>
{local_accounts.map((address) => (
<option key={address.address} value={address.address}>{address.address}</option>
))}
</select>
<p>WDT Balance: {wdtBalance ? `${wdtBalance.toString()} WDT` : '???'}</p>

{/* Game space */}
{!isPlaying ?
(<div className={"game-buttons"}>
<button onClick={() => handleInitAttempts(currentAcc)}>Play</button>
<button onClick={() => handleInitAttempts(currentAddress)}>Play</button>
</div>) : (
<div>
<input placeholder='Enter guess' onChange={handleGuessChange}/>
<button onClick={() => handleNewGuess(currentAcc, guess)}>Submit</button>
<button onClick={() => handleNewGuess(currentAddress, guess)}>Submit</button>
<GameSpace playerGuesses={playerGuesses}/>
<Alphabet playerAlphabet={alphabet}/>
</div>)}
</div>
</>
</QueryClientProvider>
</WagmiProvider>
)
}

Expand Down
24 changes: 24 additions & 0 deletions frontend/src/clients/publicClient.ts
Original file line number Diff line number Diff line change
@@ -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,
});
50 changes: 50 additions & 0 deletions frontend/src/clients/walletClient/ConnectWallet.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
{ensAvatar && <img alt="ENS Avatar" src={ensAvatar} />}
{address && <div>{ensName ? `${ensName} (${address})` : address}</div>}
<button onClick={() => disconnect()}>Disconnect</button>
</div>
)
}

/**
* 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) => (
<button key={connector.uid} onClick={() => connect({ connector })}>
{connector.name}
</button>
))
}

/**
* 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 <Account onAddressChange={onAddressChange} />
return <WalletOptions />
}
24 changes: 12 additions & 12 deletions frontend/src/hooks/WordleHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions frontend/wagmi.config.ts
Original file line number Diff line number Diff line change
@@ -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(),
]
Comment on lines +10 to +14
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added these for the time being, but more can be added later. wagmi also auto-detects wallets.

})