dao-inegration #2
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@
|
||||
# Compiler files
|
||||
cache/
|
||||
out/
|
||||
broadcast/*
|
||||
|
||||
# Ignores development broadcast logs
|
||||
!/broadcast
|
||||
|
||||
25
README.md
25
README.md
@ -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
|
||||
|
||||
`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
|
||||
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
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. 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
|
||||
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
BIN
app/public/loader/hamster.png
Normal file
BIN
app/public/loader/hamster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
app/public/loader/hamster_stand.png
Normal file
BIN
app/public/loader/hamster_stand.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
BIN
app/public/loader/hamster_wheel.png
Normal file
BIN
app/public/loader/hamster_wheel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
@ -5,10 +5,12 @@ import { usePlayer } from "../providers/PlayerProvider";
|
||||
import { useAccount } from 'wagmi';
|
||||
import dynamic from "next/dynamic";
|
||||
import Counter, { toReadable } from "./Counter";
|
||||
import { useModal } from "../providers/ModalProvider";
|
||||
|
||||
const Header = () => {
|
||||
const { isConnected } = useAccount();
|
||||
const { isRegistered, register, army } = usePlayer();
|
||||
const { isRegistered, army } = usePlayer();
|
||||
const { openRegistrationModal } = useModal();
|
||||
|
||||
const title = useMemo(() => {
|
||||
return isRegistered ? `SLAY THE MOLOCH` :
|
||||
@ -33,8 +35,8 @@ const Header = () => {
|
||||
|
||||
const onRegister = useCallback(() => {
|
||||
if (isRegistered) return
|
||||
register();
|
||||
}, [isRegistered, register])
|
||||
openRegistrationModal()
|
||||
}, [isRegistered, openRegistrationModal])
|
||||
|
||||
return <header onClick={onRegister} className={styles.header}>
|
||||
<h1 className={`${styles.title} ${isConnected && !isRegistered ? bgStyles.excited : ""}`}>{title}</h1>
|
||||
|
||||
26
app/src/components/RegistrationModal.tsx
Normal file
26
app/src/components/RegistrationModal.tsx
Normal 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
|
||||
31
app/src/components/WaitingForTxModal.tsx
Normal file
31
app/src/components/WaitingForTxModal.tsx
Normal 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
|
||||
@ -7,6 +7,7 @@ import { RainbowKitProvider, midnightTheme } from '@rainbow-me/rainbowkit';
|
||||
import { config } from '../wagmi';
|
||||
import { Texturina } from 'next/font/google'
|
||||
import PlayerProvider from '../providers/PlayerProvider';
|
||||
import ModalProvider from '../providers/ModalProvider';
|
||||
|
||||
const client = new QueryClient();
|
||||
const font = Texturina({ weight: ['400'], subsets: ["latin"] })
|
||||
@ -17,7 +18,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
<QueryClientProvider client={client}>
|
||||
<RainbowKitProvider theme={midnightTheme()}>
|
||||
<style jsx global>{`
|
||||
html, body, p, span, a {
|
||||
html, body, p, span, a, button {
|
||||
font-family: ${font.style.fontFamily};
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6, .title {
|
||||
@ -25,7 +26,9 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
}
|
||||
`}</style>
|
||||
<PlayerProvider>
|
||||
<ModalProvider>
|
||||
<Component {...pageProps} />
|
||||
</ModalProvider>
|
||||
</PlayerProvider>
|
||||
</RainbowKitProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
33
app/src/providers/ModalProvider.tsx
Normal file
33
app/src/providers/ModalProvider.tsx
Normal 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
|
||||
|
||||
@ -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 contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json"
|
||||
import { parseEther } from 'viem'
|
||||
import contractAddress from '../../contract_address'
|
||||
import { Hash, parseEther } from 'viem'
|
||||
import contracts from '../../contract_address'
|
||||
import WaitingForTxModal from '../components/WaitingForTxModal'
|
||||
|
||||
const { contractAddress, daoTokenAddress } = contracts
|
||||
const abi = contractAbi.abi
|
||||
|
||||
export type UnitType = 0 | 1 | 2 | 3
|
||||
@ -26,7 +28,7 @@ export interface PlayerContextType {
|
||||
player: null | Player,
|
||||
army: null | Army,
|
||||
balance: bigint,
|
||||
register: () => void,
|
||||
register: (arg: "ETH" | "RGCVII") => void,
|
||||
raid: () => void,
|
||||
addUnit: (unit: UnitType) => void
|
||||
}
|
||||
@ -44,11 +46,16 @@ const PlayerContext = createContext<PlayerContextType>({
|
||||
const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { address, isConnected } = useAccount();
|
||||
const { writeContract, error } = useWriteContract();
|
||||
const [[txHash, callbackFn], setHashAndCallback] = useState<[Hash | null, () => void]>([null, () => { }])
|
||||
|
||||
useEffect(() => {
|
||||
console.warn(error)
|
||||
}, [error])
|
||||
|
||||
const resetHashAndCallback = useCallback(() => {
|
||||
setHashAndCallback([null, () => { }])
|
||||
}, [])
|
||||
|
||||
const { data: isRegistered } = useReadContract({
|
||||
address: contractAddress,
|
||||
abi,
|
||||
@ -56,7 +63,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
args: [address],
|
||||
query: {
|
||||
enabled: isConnected,
|
||||
refetchInterval: 5,
|
||||
refetchInterval: 15,
|
||||
}
|
||||
});
|
||||
|
||||
@ -66,7 +73,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
functionName: 'balanceOf',
|
||||
args: [address],
|
||||
query: {
|
||||
refetchInterval: 5,
|
||||
refetchInterval: 15,
|
||||
enabled: isConnected
|
||||
}
|
||||
});
|
||||
@ -95,14 +102,42 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
console.log(balance, player, army)
|
||||
|
||||
const register = useCallback(() => {
|
||||
const register = useCallback((arg: "RGCVII" | "ETH") => {
|
||||
if (arg === 'ETH') {
|
||||
writeContract({
|
||||
abi,
|
||||
address: contractAddress,
|
||||
functionName: 'register',
|
||||
functionName: 'register_eth',
|
||||
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(() => {
|
||||
writeContract({
|
||||
@ -132,6 +167,7 @@ const PlayerProvider = ({ children }: { children: ReactNode }) => {
|
||||
addUnit
|
||||
}}>
|
||||
{children}
|
||||
{txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
|
||||
</PlayerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
67
app/src/styles/Modal.module.css
Normal file
67
app/src/styles/Modal.module.css
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
:root {
|
||||
--bg-color: #1a1a1a;
|
||||
--bg-color-button: #000;
|
||||
--text-color: #ffffff;
|
||||
--accent-color: #f00000;
|
||||
--border-color: #800000;
|
||||
@ -40,8 +41,8 @@ a:hover {
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--bg-color);
|
||||
background-color: var(--bg-color-button);
|
||||
color: var(--text-color);
|
||||
border: 2px solid var(--border-color);
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
@ -49,6 +50,7 @@ button {
|
||||
|
||||
button:hover {
|
||||
background-color: var(--hover-color);
|
||||
color: var(--bg-color-button);
|
||||
}
|
||||
|
||||
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
@ -1,4 +1,23 @@
|
||||
#!/bin/sh
|
||||
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
|
||||
cast rpc anvil_mine
|
||||
#!/bin/bash
|
||||
|
||||
# YOUR WALLET, change to you account below:
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user