diff --git a/src/components/Hero.jsx b/src/components/Hero.jsx new file mode 100644 index 0000000..7d85326 --- /dev/null +++ b/src/components/Hero.jsx @@ -0,0 +1,297 @@ +import React, { useEffect, useState } from 'react'; + +import { ethers, BigNumber } from 'ethers'; +import { useAccount, useContractReads, useContractRead, usePrepareContractWrite, useContractWrite, useWaitForTransaction, erc721ABI } from 'wagmi'; +import { NotificationContainer, NotificationManager } from 'react-notifications'; +import debounce from 'lodash'; + +import MainABI from '../abi/main.json'; +import '../styles/hero.css'; +import Unaboomer from '../img/unaboomer.png'; +import Mailbomb from '../img/mailbomb.png'; +import Explosion from '../img/explosion.png'; +import Arrow from '../img/arrow_right.png'; + +export function Hero(props) { + const contractAddress = props.contractAddress; + const [tokensMinted, setTokensMinted] = useState(() => { + const saved = localStorage.getItem('tokensMinted-v1'); + return saved || JSON.stringify([]) + }); + + useEffect(() => { + localStorage.setItem('tokensMinted-v1', tokensMinted); + }, [tokensMinted]); + + const [options, setOptions] = useState({ + unaboomerAmount: 1, + bombAmount: 1, + sendBombAmount: 1, + unaboomerPreviewAmount: 1, + bombPreviewAmount: 1, + sendBombPreviewAmount: 1, + unaboomerPrice: 0, + bombPrice: ethers.utils.parseEther('.01'), + unaboomersMinted: 0, + unaboomerBalance: 0, + bombBalance: 0, + unaboomersRadicalized: 0, + bombsAssembled: 0, + bombsExploded: 0, + unaboomersKilled: 0, + leaderboardPointer: 0, + leaderAddress: '', + leaderKillCount: 0, + unaboomerMaxSupply: 0, + unaboomerMaxSurvivorCount: 0, + unaboomerMaxMintPerWallet: 0, + readWTF: false, + balancesFetched: false, + unaboomersByAddress: [] + }); + function handleStateChange(obj) { + setOptions(preState => ({...preState , ...obj})); + } + const { isConnected, address } = useAccount(); + const defOpt = { + address: contractAddress, + abi: MainABI, + enabled: isConnected + } + // Fetch this stuff and cache aggressively since it doesn't change + useContractReads({ + contracts: [ + {...defOpt, functionName: 'unaboomerPrice'}, + {...defOpt, functionName: 'bombPrice'}, + {...defOpt, functionName: 'unaboomerMaxSupply'}, + {...defOpt, functionName: 'unaboomerMaxSurvivorCount'}, + {...defOpt, functionName: 'unaboomerMaxMintPerWallet'} + ], + watch: true, + cacheTime: 30_000, + onSuccess(data) { + handleStateChange({ + unaboomerPrice: data[0].toString(), + bombPrice: data[1].toString(), + unaboomerMaxSupply: Number(data[2]), + unaboomerMaxSurvivorCount: Number(data[3]), + unaboomerMaxMintPerWallet: Number(data[4]) + }); + } + }); + // Fetch this stuff more frequently as it updates constantly + useContractReads({ + contracts: [ + {...defOpt, functionName: 'unaboomersMinted', args: [address]}, + {...defOpt, functionName: 'bombBalance', args: [address]}, + {...defOpt, functionName: 'unaboomersRadicalized'}, + {...defOpt, functionName: 'bombsAssembled'}, + {...defOpt, functionName: 'bombsExploded'}, + {...defOpt, functionName: 'unaboomersKilled'}, + {...defOpt, functionName: 'leaderboardPointer'}, + {...defOpt, functionName: 'unaboomerBalance', args: [address]} + ], + watch: true, + cacheTime: 6_000, + onSuccess(data) { + handleStateChange({ + unaboomersMinted: Number(data[0]), + bombBalance: Number(data[1]), + unaboomersRadicalized: Number(data[2]), + bombsAssembled: Number(data[3]), + bombsExploded: Number(data[4]), + unaboomersKilled: Number(data[5]), + leaderboardPointer: Number(data[6]), + balancesFetched: true, + unaboomerBalance: Number(data[7]) + }); + } + }); + useContractRead({ + address: contractAddress, + abi: MainABI, + enabled: options.leaderboardPointer > 0, + functionName: 'leaderboard', + args: [options.leaderboardPointer], + watch: true, + cacheTime: 8_000, + onSuccess: async (data) => { + handleStateChange({ + leaderAddress: data, + }); + } + }); + useContractRead({ + address: contractAddress, + abi: MainABI, + enabled: options.leaderAddress.length > 0, + functionName: 'killCount', + args: [options.leaderAddress], + watch: true, + cacheTime: 8_000, + onSuccess(data) { + handleStateChange({ + leaderKillCount: Number(data), + }); + } + }); + const radicalizeBoomersPrepare = usePrepareContractWrite({ + address: contractAddress, + abi: MainABI, + enabled: isConnected && options.unaboomersMinted < options.unaboomerMaxMintPerWallet && options.unaboomerAmount > 0 && options.balancesFetched, + functionName: 'radicalizeBoomers', + args: [options.unaboomerAmount], + overrides: { + from: address, + value: BigNumber.from((options.unaboomerPrice * options.unaboomerAmount).toString()) + } + }); + const radicalizeBoomersWrite = useContractWrite({ + ...radicalizeBoomersPrepare.config, + onError(data) { + if (data.message.startsWith('user rejected transaction')) NotificationManager.info(`tx cancelled`, 'ok', 4000); + } + }); + const assembleBombsPrepare = usePrepareContractWrite({ + address: contractAddress, + abi: MainABI, + enabled: isConnected && options.bombAmount > 0, + functionName: 'assembleBombs', + args: [options.bombAmount], + overrides: { + from: address, + value: BigNumber.from((options.bombPrice * options.bombAmount).toString()) + } + }); + const assembleBombsWrite = useContractWrite({ + ...assembleBombsPrepare.config, + onError(data) { + if (data.message.startsWith('user rejected transaction')) NotificationManager.info(`tx cancelled`, 'ok', 4000); + } + }); + const sendBombsPrepare = usePrepareContractWrite({ + address: contractAddress, + abi: MainABI, + enabled: isConnected && options.bombBalance > 0, + functionName: 'sendBombs', + cacheTime: 0, + staleTime: 400, + args: [options.sendBombAmount] + }); + const sendBombsWrite = useContractWrite({ + ...sendBombsPrepare.config, + onError(data) { + if (data.message.startsWith('user rejected transaction')) NotificationManager.info(`tx cancelled`, 'ok', 4000); + } + }); + useWaitForTransaction({ + hash: radicalizeBoomersWrite.data?.hash, + enabled: radicalizeBoomersWrite.status === 'success', + onSuccess(data) { + let tm = JSON.parse(tokensMinted); + const iface = new ethers.utils.Interface(erc721ABI); + data.logs.filter(log => + log.topics[0] === '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' + ).map((log) => { + const res = iface.decodeEventLog("Transfer", log.data, log.topics); + NotificationManager.success(`You radicalized Unaboomer #${res.tokenId.toString()}`, '', 4000); + tm.push(Number(res.tokenId)); + }); + setTokensMinted(JSON.stringify(tm)); + console.log(tm); + } + }) + useWaitForTransaction({ + hash: sendBombsWrite.data?.hash, + enabled: sendBombsWrite.status === 'success', + onSuccess(data) { + const iface = new ethers.utils.Interface(MainABI); + data.logs.filter(log => + log.topics[0] === '0x38c7e2e8001cefb27535bd6bcc6363c6d6a6a83dbb77f092b78956334596f9ed' + ).map((log) => { + const res = iface.decodeEventLog("SentBomb", log.data, log.topics); + if (res.hit) { + if (res.owned) { + NotificationManager.error(`Your bomb exploded during assembly and killed your Unaboomer ${res.tokenId.toString()}`, '', 8000); + } else { + NotificationManager.success(`Your bomb killed Unaboomer ${res.tokenId.toString()}`, '', 8000); + } + } else { + NotificationManager.warning(`Your bomb for Unaboomer ${res.tokenId.toString()} was a dud`, '', 8000); + } + }); + } + }); + + return ( +
+

the web3 revolution & its consequences have been a disaster for the human race.

+

+ Technology is advancing at an exponential rate and boomers are being left behind. A.I., Bitcoin, + and expensive JPEGs have made them angry.

+ They've begun to radicalize. {options.unaboomerMaxSupply > 0 ? '35,000': options.unaboomerMaxSupply} Boomers have joined forces + to lash out at the system.

+

+
+
+ Pixelated Unaboomer profile picture - a hooded man with dark sunglasses. + + {options.unaboomersMinted > 0 && ( +

+ You have minted {options.unaboomersMinted} Unaboomers. + {options.unaboomersKilled > 0 && <>{options.unaboomersKilled} are dead.} +

+ )} +
+ {isConnected + && options.unaboomersMinted < options.unaboomerMaxMintPerWallet + && options.unaboomersKilled + options.unaboomersRadicalized < options.unaboomerMaxSupply + && ( + <> + { + handleStateChange({unaboomerAmount: v.target.value}); + radicalizeBoomersWrite.reset() + }}> + +

({ethers.utils.formatEther((options.unaboomerPrice * options.unaboomerAmount).toString())} ETH)

+ + ) || options.unaboomersMinted == options.unaboomerMaxMintPerWallet && options.unaboomersKilled + options.unaboomersRadicalized < options.unaboomerMaxSupply && ( +

max per wallet reached

+ ) || options.unaboomersRadicalized == options.unaboomerMaxSupply && options.unaboomerMaxSupply > 0 && ( +

max supply reached

+ )} +
+
+ Arrow pointing to the next step. +
+
+ Pixelated Mailbomb image - handle with caution. +

+ Don't let the Boomers win, fight back! Assemble a mailbomb and blow 'em to smithereens.

+ (Warning: Some bombs are duds!) +

+ +

(.01 ETH)

+
+
+ Arrow pointing to the next step. +
+
+ Pixelated explosion image. +

+ Ready for mayhem? Burn your mailrooms to blow up a random boomer.

+ If your bomb is live it will destroy a Boomer PFP forever.

+ (Warning: Your Boomer might explode!) +

+ +

(0 ETH)

+
+
+
+ ) +} diff --git a/src/sections/Navbar.jsx b/src/components/Navbar.jsx similarity index 100% rename from src/sections/Navbar.jsx rename to src/components/Navbar.jsx diff --git a/src/index.js b/src/index.js index 538926c..fec2789 100644 --- a/src/index.js +++ b/src/index.js @@ -11,13 +11,13 @@ import { mainnet, goerli, localhost } from 'wagmi/chains'; import { infuraProvider } from 'wagmi/providers/infura' import { publicProvider } from 'wagmi/providers/public'; -import { Navbar } from "./sections/Navbar"; -import { Hero } from "./sections/Hero"; +import { Navbar } from "./components/Navbar"; +import { Hero } from "./components/Hero"; const { chains, provider, webSocketProvider } = configureChains( [mainnet, goerli, localhost], [ - infuraProvider({ apiKey: 'e7674eea9d72437d9129b41ed018183d' }), + // infuraProvider({ apiKey: 'e7674eea9d72437d9129b41ed018183d' }), publicProvider() ] ); @@ -37,10 +37,8 @@ const wagmiClient = createClient({ ReactDOM.createRoot(document.getElementById("root")).render( -
- - -
+ +
); diff --git a/src/sections/Hero.jsx b/src/sections/Hero.jsx deleted file mode 100644 index 497152e..0000000 --- a/src/sections/Hero.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; - -import '../styles/hero.css'; -import Unaboomer from '../img/unaboomer.png'; -import Mailbomb from '../img/mailbomb.png'; -import Explosion from '../img/explosion.png'; -import Arrow from '../img/arrow_right.png'; - -export class Hero extends React.Component { - render() { - return ( -
-

the web3 revolution & its consequences have been a disaster for the human race.

-
-
- Pixelated Unaboomer profile picture - a hooded man with dark sunglasses. -

- Technology is advancing at an exponential rate and boomers are being left behind. A.I., Bitcoin, - and expensive JPEGs have made them angry.

- They've begun to radicalize. 35,000 Boomers have joined forces - to lash out at the system. -

- -

(0 ETH)

-
-
- Arrow pointing to the next step. -
-
- Pixelated Mailbomb image - handle with caution. -

- Don't let the Boomers win, fight back! Assemble a mailbomb and blow 'em to smithereens.

- (Warning: Some bombs are duds!) -

- -

(.01 ETH)

-
-
- Arrow pointing to the next step. -
-
- Pixelated explosion image. -

- Ready for mayhem? Burn your mailrooms to blow up a random boomer.

- If your bomb is live it will destroy a Boomer PFP forever.

- (Warning: Your Boomer might explode!) -

- -

(0 ETH)

-
-
-
- ) - } -} diff --git a/src/styles/hero.css b/src/styles/hero.css index b88753e..10c9e76 100644 --- a/src/styles/hero.css +++ b/src/styles/hero.css @@ -18,7 +18,6 @@ .heroFlexItem { flex: 0 25%; height: 100%; - width: 200px; text-align: center; } @@ -33,4 +32,16 @@ button.doThing { margin: 1em; + cursor: pointer; + transition: .1s ease all; +} + +button.doThing:hover { + transform: translate(4px, 10%); + box-shadow: 1px 1px 0px #f9f9f9; + transition: .1s ease all; +} + +button.doThing:disabled { + background-color: grey; } \ No newline at end of file diff --git a/src/styles/main.css b/src/styles/main.css index 466a36f..0016f9c 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -20,8 +20,8 @@ button { padding: 1em; border-radius: 0; border: 0; - box-shadow: 0 5px #f9f9f9; - clip-path: polygon( + box-shadow: 5px 5px 0 #f9f9f9; + /* clip-path: polygon( 0px 20px, 4px 20px, 4px 12px, @@ -62,5 +62,5 @@ button { 4px calc(100% - 16px), 4px calc(100% - 20px), 0px calc(100% - 20px) - ); + ); */ }