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 ( +
+ 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.
+
+ You have minted {options.unaboomersMinted} Unaboomers. + {options.unaboomersKilled > 0 && <>{options.unaboomersKilled} are dead.>} +
+ )} +({ethers.utils.formatEther((options.unaboomerPrice * options.unaboomerAmount).toString())} ETH)
+ > + ) || options.unaboomersMinted == options.unaboomerMaxMintPerWallet && options.unaboomersKilled + options.unaboomersRadicalized < options.unaboomerMaxSupply && ( +
+ Don't let the Boomers win, fight back! Assemble a mailbomb and blow 'em to smithereens.
+ (Warning: Some bombs are duds!)
+
(.01 ETH)
+
+ 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)
+
- 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)
-
- Don't let the Boomers win, fight back! Assemble a mailbomb and blow 'em to smithereens.
- (Warning: Some bombs are duds!)
-
(.01 ETH)
-
- 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)
-