Enables registrations with RGCVII and ETH
Some checks failed
CI / Foundry project (push) Waiting to run
CI / Foundry project (pull_request) Has been cancelled

This commit is contained in:
mic0 2024-10-28 18:31:41 +01:00
parent bd1f418e11
commit 8c8ca58607
Signed by: mico
GPG Key ID: A3F8023524CF1C8D
20 changed files with 272 additions and 371 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
# Compiler files # Compiler files
cache/ cache/
out/ out/
broadcast/*
# Ignores development broadcast logs # Ignores development broadcast logs
!/broadcast !/broadcast

View File

@ -6,13 +6,22 @@ Idle game & shitcoin advanture dedicated to cohort VII of Raid Guild.
### 1. Run `anvil` to setup local RPC as a fork of base mainnet ### 1. Run `anvil` to setup local RPC as a fork of base mainnet
`anvil --block-time 5 --rpc-url <you'r base mainnet rpc url>` `anvil --fork-url <YOUR BASE MAINNET RPC URL> --block-time 10 --chain-id 31337`
You can get a free rpc url by registering with https://alchemy.com and creating and app You can get a free rpc url by registering with https://alchemy.com and creating an app
Be sure to set --chain-id to 31337 if you are forking mainnet base, otherwise it will deploy with Base chain id and metamask will glitch out.
### 2. Deploy contract ### 2. Deploy contract
Either use `./deploy_contract.sh` script (!! change contract values and set private key to $DEV_PRIVATE_KEY for it to work) or call those things by hand. Use `./deploy_contract.sh` script
This will deploy the contract and give you ETH and DAO Token (RGCVII) funds.
1. Make sure to change `DEV_WALLET` var to your own.
2. Make sure you have your private key on `DEV_PRIVATE_KEY` environment variable
Alternatively, check the script and run the steps as you see fit.
### 3. Run dev app ### 3. Run dev app
@ -20,8 +29,16 @@ Move to `app` dir, install deps via `npm install` and run `npm run dev` to start
#### 3. 1. Point Metamask to Anvil network for local dev #### 3. 1. Point Metamask to Anvil network for local dev
Add network `http://127.0.0.1:8545` with chain id `31337`
#### 3. 2. Change `app/contract_address.ts` to match your program address if needed #### 3. 2. Change `app/contract_address.ts` to match your program address if needed
#### 3. 3. Reset metamask transaction history between anvil deployments
If u re-run `anvil` and redeploy the contract, do clear your history in Metamask under Advanced Settings, otherwise Metamask glitches because of its cache (?)
### 4. Fork tests ### 4. Fork tests
forge test --rpc-url <your base mainnet rpc url> Run `forge test --rpc-url <your base mainnet rpc url>`
You can get a free rpc url by registering with https://alchemy.com and creating an app

View File

@ -1,4 +1,8 @@
const contractAddress = "0xb2fc8F28aD37290245241C6cb0E411c9fff6A1d7"; import { Address } from "viem"
const contracts: Record<string, Address> = {
contractAddress: "0xbd06B0878888bf4c6895704fa603a5ADf7e65c66",
daoTokenAddress: "0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8"
}
export default contractAddress export default contracts

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -5,10 +5,12 @@ import { usePlayer } from "../providers/PlayerProvider";
import { useAccount } from 'wagmi'; import { useAccount } from 'wagmi';
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Counter, { toReadable } from "./Counter"; import Counter, { toReadable } from "./Counter";
import { useModal } from "../providers/ModalProvider";
const Header = () => { const Header = () => {
const { isConnected } = useAccount(); const { isConnected } = useAccount();
const { isRegistered, register, army } = usePlayer(); const { isRegistered, army } = usePlayer();
const { openRegistrationModal } = useModal();
const title = useMemo(() => { const title = useMemo(() => {
return isRegistered ? `SLAY THE MOLOCH` : return isRegistered ? `SLAY THE MOLOCH` :
@ -33,8 +35,8 @@ const Header = () => {
const onRegister = useCallback(() => { const onRegister = useCallback(() => {
if (isRegistered) return if (isRegistered) return
register(); openRegistrationModal()
}, [isRegistered, register]) }, [isRegistered, openRegistrationModal])
return <header onClick={onRegister} className={styles.header}> return <header onClick={onRegister} className={styles.header}>
<h1 className={`${styles.title} ${isConnected && !isRegistered ? bgStyles.excited : ""}`}>{title}</h1> <h1 className={`${styles.title} ${isConnected && !isRegistered ? bgStyles.excited : ""}`}>{title}</h1>

View File

@ -0,0 +1,26 @@
import { useCallback } from "react";
import { usePlayer } from "../providers/PlayerProvider";
import styles from "../styles/Modal.module.css";
interface RegistrationModalProps {
isOpen: boolean;
setIsOpen: (val: boolean) => void
}
const RegistrationModal = ({ isOpen, setIsOpen }: RegistrationModalProps) => {
const { register } = usePlayer()
const onRegister = useCallback((mode: "ETH" | "RGCVII") => {
register(mode);
setIsOpen(false);
}, [register, setIsOpen])
if (!isOpen) return null;
return <div className={styles.modal}>
<h2>Insert coins to continue</h2>
<div>
<button onClick={() => onRegister("RGCVII")}>50 RGCVII</button>
<button onClick={() => onRegister("ETH")}>0.0005 ETH</button>
</div>
</div>
}
export default RegistrationModal

View File

@ -0,0 +1,31 @@
import { useEffect } from "react";
import { Hash } from "viem"
import { useWaitForTransactionReceipt } from "wagmi";
import styles from "../styles/Modal.module.css"
interface WaitingForTxModalProps {
hash: Hash,
callbackFn: () => void;
}
const WaitingForTxModal = ({
hash,
callbackFn
}: WaitingForTxModalProps) => {
const { isFetched } = useWaitForTransactionReceipt({ hash })
useEffect(() => {
if (isFetched) {
callbackFn()
}
}, [isFetched, callbackFn])
return <div className={styles.modal}>
<div className={styles.loadingImage}>
<div className={styles.loadingHamsterWheelStand} />
<div className={styles.loadingHamsterWheel} />
<div className={styles.loadingHamster} />
</div>
<p className={styles.loadingText}>Writing contract ...</p>
</div>
}
export default WaitingForTxModal

View File

@ -7,6 +7,7 @@ import { RainbowKitProvider, midnightTheme } from '@rainbow-me/rainbowkit';
import { config } from '../wagmi'; import { config } from '../wagmi';
import { Texturina } from 'next/font/google' import { Texturina } from 'next/font/google'
import PlayerProvider from '../providers/PlayerProvider'; import PlayerProvider from '../providers/PlayerProvider';
import ModalProvider from '../providers/ModalProvider';
const client = new QueryClient(); const client = new QueryClient();
const font = Texturina({ weight: ['400'], subsets: ["latin"] }) const font = Texturina({ weight: ['400'], subsets: ["latin"] })
@ -17,7 +18,7 @@ function MyApp({ Component, pageProps }: AppProps) {
<QueryClientProvider client={client}> <QueryClientProvider client={client}>
<RainbowKitProvider theme={midnightTheme()}> <RainbowKitProvider theme={midnightTheme()}>
<style jsx global>{` <style jsx global>{`
html, body, p, span, a { html, body, p, span, a, button {
font-family: ${font.style.fontFamily}; font-family: ${font.style.fontFamily};
} }
h1, h2, h3, h4, h5, h6, .title { h1, h2, h3, h4, h5, h6, .title {
@ -25,7 +26,9 @@ function MyApp({ Component, pageProps }: AppProps) {
} }
`}</style> `}</style>
<PlayerProvider> <PlayerProvider>
<ModalProvider>
<Component {...pageProps} /> <Component {...pageProps} />
</ModalProvider>
</PlayerProvider> </PlayerProvider>
</RainbowKitProvider> </RainbowKitProvider>
</QueryClientProvider> </QueryClientProvider>

View File

@ -0,0 +1,33 @@
import React, { createContext, ReactNode, useCallback, useContext, useState } from 'react'
import RegistrationModal from '../components/RegistrationModal';
export interface PlayerContextType {
openRegistrationModal: () => void
}
const ModalContext = createContext<PlayerContextType>({
openRegistrationModal: () => { }
});
const ModalProvider = ({ children }: { children: ReactNode }) => {
const [registrationModalOpen, setIsRegistrationModalOpen] = useState(false)
const openRegistrationModal = useCallback(() => {
setIsRegistrationModalOpen(true)
}, [])
return (
<ModalContext.Provider value={{
openRegistrationModal
}}>
{children}
<RegistrationModal setIsOpen={setIsRegistrationModalOpen} isOpen={registrationModalOpen} />
</ModalContext.Provider>
);
}
export const useModal = () => {
return useContext(ModalContext);
}
export default ModalProvider

View File

@ -1,9 +1,11 @@
import React, { createContext, ReactNode, useCallback, useContext, useEffect } from 'react' import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react'
import { useAccount, useReadContract, useWriteContract } from 'wagmi' import { useAccount, useReadContract, useWriteContract } from 'wagmi'
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json" import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
import { parseEther } from 'viem' import { Hash, parseEther } from 'viem'
import contractAddress from '../../contract_address' import contracts from '../../contract_address'
import WaitingForTxModal from '../components/WaitingForTxModal'
const { contractAddress, daoTokenAddress } = contracts
const abi = contractAbi.abi const abi = contractAbi.abi
export type UnitType = 0 | 1 | 2 | 3 export type UnitType = 0 | 1 | 2 | 3
@ -26,7 +28,7 @@ export interface PlayerContextType {
player: null | Player, player: null | Player,
army: null | Army, army: null | Army,
balance: bigint, balance: bigint,
register: () => void, register: (arg: "ETH" | "RGCVII") => void,
raid: () => void, raid: () => void,
addUnit: (unit: UnitType) => void addUnit: (unit: UnitType) => void
} }
@ -44,11 +46,16 @@ const PlayerContext = createContext<PlayerContextType>({
const PlayerProvider = ({ children }: { children: ReactNode }) => { const PlayerProvider = ({ children }: { children: ReactNode }) => {
const { address, isConnected } = useAccount(); const { address, isConnected } = useAccount();
const { writeContract, error } = useWriteContract(); const { writeContract, error } = useWriteContract();
const [[txHash, callbackFn], setHashAndCallback] = useState<[Hash | null, () => void]>([null, () => { }])
useEffect(() => { useEffect(() => {
console.warn(error) console.warn(error)
}, [error]) }, [error])
const resetHashAndCallback = useCallback(() => {
setHashAndCallback([null, () => { }])
}, [])
const { data: isRegistered } = useReadContract({ const { data: isRegistered } = useReadContract({
address: contractAddress, address: contractAddress,
abi, abi,
@ -56,7 +63,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
args: [address], args: [address],
query: { query: {
enabled: isConnected, enabled: isConnected,
refetchInterval: 5, refetchInterval: 15,
} }
}); });
@ -66,7 +73,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
functionName: 'balanceOf', functionName: 'balanceOf',
args: [address], args: [address],
query: { query: {
refetchInterval: 5, refetchInterval: 15,
enabled: isConnected enabled: isConnected
} }
}); });
@ -95,14 +102,42 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
console.log(balance, player, army) console.log(balance, player, army)
const register = useCallback(() => { const register = useCallback((arg: "RGCVII" | "ETH") => {
if (arg === 'ETH') {
writeContract({ writeContract({
abi, abi,
address: contractAddress, address: contractAddress,
functionName: 'register', functionName: 'register_eth',
value: parseEther("0.0005"), value: parseEther("0.0005"),
}, {
onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback])
}
}) })
}, [writeContract]) } else if (arg === "RGCVII") {
writeContract({
abi,
address: daoTokenAddress,
functionName: 'approve',
args: [contractAddress, parseEther("50")],
}, {
onSuccess: (hash) => {
setHashAndCallback([
hash,
() => writeContract({
abi,
address: contractAddress,
functionName: 'register_dao',
}, {
onSuccess: (hash) => {
setHashAndCallback([hash, resetHashAndCallback])
}
})
])
}
});
}
}, [writeContract, resetHashAndCallback])
const raid = useCallback(() => { const raid = useCallback(() => {
writeContract({ writeContract({
@ -132,6 +167,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
addUnit addUnit
}}> }}>
{children} {children}
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
</PlayerContext.Provider> </PlayerContext.Provider>
); );
} }

View File

@ -0,0 +1,67 @@
.modal {
position: fixed;
margin: 0 auto;
height: auto;
background: var(--bg-color);
border-width: 8px;
border-image: url("/background/frame.png") 22 fill / auto space;
padding: 44px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
& button {
font-size: 1.4rem;
margin: 0 11px;
}
& h2 {
margin-top: 0;
}
.loadingImage {
position: relative;
width: 240px;
height: 240px;
}
.loadingHamster {
position: absolute;
background-image: url("/loader/hamster.png");
width: 240px;
height: 240px;
animation: jump 0.2s ease infinite;
}
.loadingHamsterWheel {
position: absolute;
background-image: url("/loader/hamster_wheel.png");
width: 240px;
height: 240px;
animation: spin 3.5s linear infinite;
}
.loadingHamsterWheelStand {
position: absolute;
background-image: url("/loader/hamster_stand.png");
width: 240px;
height: 240px;
}
.loadingText {
text-align: center;
font-size: 1.1rem;
margin-bottom: 0;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes jump {
0%,
100% {
transform: translate(0, 0);
}
50% {
transform: translate(0, -10px);
}
}

View File

@ -1,5 +1,6 @@
:root { :root {
--bg-color: #1a1a1a; --bg-color: #1a1a1a;
--bg-color-button: #000;
--text-color: #ffffff; --text-color: #ffffff;
--accent-color: #f00000; --accent-color: #f00000;
--border-color: #800000; --border-color: #800000;
@ -40,8 +41,8 @@ a:hover {
} }
button { button {
background-color: var(--accent-color); background-color: var(--bg-color-button);
color: var(--bg-color); color: var(--text-color);
border: 2px solid var(--border-color); border: 2px solid var(--border-color);
padding: 10px; padding: 10px;
cursor: pointer; cursor: pointer;
@ -49,6 +50,7 @@ button {
button:hover { button:hover {
background-color: var(--hover-color); background-color: var(--hover-color);
color: var(--bg-color-button);
} }
header, header,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,23 @@
#!/bin/sh #!/bin/bash
cast rpc anvil_setBalance 0x3295CCA2d922c637d35b258fc6c9C7e471803b45 0xDE0B6B3A7640000 --rpc-url http://127.0.0.1:8545
forge script script/RaidGeld.s.sol:RaidGeldScript --rpc-url 127.0.0.1:8545 --broadcast --private-key $DEV_PRIVATE_KEY # YOUR WALLET, change to you account below:
cast rpc anvil_mine DEV_WALLET="0x3295CCA2d922c637d35b258fc6c9C7e471803b45"
DAO_OWNER="0x4d5A5B4a679b10038e1677C84Cb675d10d29fFFD"
DAO_CONTRACT="0x11dC980faf34A1D082Ae8A6a883db3A950a3c6E8"
# Set balance for the dev wallet (1eth)
cast rpc anvil_setBalance $DEV_WALLET 0xDE0B6B3A7640000 --rpc-url http://127.0.0.1:8545
cast rpc anvil_setBalance $DAO_OWNER 0xDE0B6B3A7640000 --rpc-url http://127.0.0.1:8545
# Deploy RaidGeld
forge script script/RaidGeld.s.sol:RaidGeldScript --rpc-url http://127.0.0.1:8545 --broadcast --private-key $DEV_PRIVATE_KEY
# Impersonate the DAO owner account
cast rpc anvil_impersonateAccount $DAO_OWNER
# Send the mint transaction as the impersonated owner
cast send $DAO_CONTRACT "mint(address,uint256)" $DEV_WALLET 0x00000000000000000000000000000000000000000000003635c9adc5dea00000 --from $DAO_OWNER --rpc-url http://127.0.0.1:8545 --unlocked --gas-limit 300000
# Stop impersonating the DAO owner
cast rpc anvil_stopImpersonatingAccount $DAO_OWNER