mirror of
https://github.com/stronk-dev/RandomChad.git
synced 2025-07-05 10:35:08 +02:00
Verifier
This commit is contained in:
parent
6f2a438ff7
commit
82d2b6da08
@ -1,44 +1,36 @@
|
||||
import Minter from './components/minter'
|
||||
import Verifier from './components/verifier'
|
||||
import Fox from './assets/metamask-fox.svg'
|
||||
// import LaunchBackground from './assets/undraw_To_the_stars_qhyy.svg'
|
||||
// import LaunchBackground from './assets/undraw_relaunch_day_902d-fixed.svg'
|
||||
import LaunchBackground from './assets/undraw_launch_day_4e04.svg'
|
||||
import './App.css'
|
||||
|
||||
import { Container } from './components/generic'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { log } from './modules/helpers'
|
||||
import { useAddress, getAddress } from './modules/web3'
|
||||
|
||||
import { getAddress, useAddress, useTotalSupply, useContract, useChainId, rocketeerUriOnOpensea } from './modules/web3'
|
||||
import { log, setListenerAndReturnUnlistener } from './modules/helpers'
|
||||
|
||||
const Container = ( { children } ) => <main>
|
||||
|
||||
<div className="container">
|
||||
|
||||
{ children }
|
||||
|
||||
</div>
|
||||
|
||||
<img className="stretchBackground" src={ LaunchBackground } alt="Launching rocket" />
|
||||
|
||||
</main>
|
||||
|
||||
function App() {
|
||||
|
||||
// ///////////////////////////////
|
||||
// States
|
||||
// ///////////////////////////////
|
||||
const [ action, setAction ] = useState( undefined )
|
||||
const [ loading, setLoading ] = useState( 'Detecting metamask...' )
|
||||
const [ error, setError ] = useState( undefined )
|
||||
const [ mintedTokenId, setMintedTokenId ] = useState( undefined )
|
||||
const totalSupply = useTotalSupply( )
|
||||
const address = useAddress()
|
||||
const chainId = useChainId()
|
||||
|
||||
// Handle contract interactions
|
||||
const contract = useContract()
|
||||
|
||||
// ///////////////////////////////
|
||||
// Functions
|
||||
// ///////////////////////////////
|
||||
function checkAction() {
|
||||
|
||||
const verify = window.location.href.includes( 'mode=verify' )
|
||||
log( `Location is ${window.location.href}, ${ !verify ? 'not opening' : 'opening' } verifier` )
|
||||
|
||||
if( verify ) return setAction( 'verify' )
|
||||
return setAction( 'mint' )
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Handle user login interaction
|
||||
async function metamasklogin( e ) {
|
||||
@ -58,53 +50,25 @@ function App() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function mintRocketeer( e ) {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
|
||||
if( !address ) setError( 'No destination address selected' )
|
||||
setLoading( 'Confirm transaction in metamask' )
|
||||
const response = await contract.spawnRocketeer( address )
|
||||
log( 'Successful mint with: ', response )
|
||||
|
||||
setLoading( 'Waiting for confirmations...' )
|
||||
|
||||
} catch( e ) {
|
||||
log( 'Minting error: ', e )
|
||||
alert( `Minting error: ${ e.message }` )
|
||||
setLoading( false )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ///////////////////////////////
|
||||
// Lifecycle
|
||||
// ///////////////////////////////
|
||||
useEffect( f => setListenerAndReturnUnlistener( contract, 'Transfer', async ( from, to, amount, event ) => {
|
||||
|
||||
try {
|
||||
|
||||
log( `useEffect: Transfer ${ from } sent to ${ to } `, amount, event )
|
||||
const [ transFrom, transTo, tokenId ] = event.args
|
||||
setMintedTokenId( tokenId.toString() )
|
||||
setLoading( false )
|
||||
|
||||
} catch( e ) {
|
||||
log( 'Error getting Transfer event from contract: ', e )
|
||||
}
|
||||
|
||||
} ), [ contract, loading ] )
|
||||
|
||||
// Check for metamask on load
|
||||
useEffect( f => window.ethereum ? setLoading( false ) : setError( 'No web3 provider detected, please install metamask' ), [] )
|
||||
|
||||
|
||||
// Check for action on load
|
||||
useEffect( f => {
|
||||
checkAction()
|
||||
window.addEventListener( 'popstate', checkAction )
|
||||
}, [] )
|
||||
|
||||
// ///////////////////////////////
|
||||
// Rendering
|
||||
// ///////////////////////////////
|
||||
|
||||
log( action, error, loading, address )
|
||||
// Initialisation interface
|
||||
if( error || loading || !address ) return <Container>
|
||||
{ error && <p>{ error }</p> }
|
||||
@ -116,9 +80,9 @@ function App() {
|
||||
</div> }
|
||||
{ !address && ( !error && !loading ) && <>
|
||||
|
||||
<h1>Rocketeer Minter</h1>
|
||||
<p>This interface is used to mint new Rocketeer NFTs. Minting is free, except for the gas fees. After minting you can view your new Rocketeer and its attributes on Opensea.</p>
|
||||
|
||||
<h1>Rocketeer { action == 'mint' ? 'Minter' : 'Verifier' }</h1>
|
||||
{ action == 'mint' && <p>This interface is used to mint new Rocketeer NFTs. Minting is free, except for the gas fees. After minting you can view your new Rocketeer and its attributes on Opensea.</p> }
|
||||
{ action == 'verify' && <p>This interface is used to veriy that you are the owner of a Rocketeer</p> }
|
||||
<a className="button" href="/#" onClick={ metamasklogin }>
|
||||
<img alt="metamask fox" src={ Fox } />
|
||||
Connect wallet
|
||||
@ -127,32 +91,9 @@ function App() {
|
||||
</> }
|
||||
</Container>
|
||||
|
||||
if( mintedTokenId ) return <Container>
|
||||
|
||||
<h1>Minting Successful!</h1>
|
||||
<a className="button" rel="noreferrer" target="_blank" alt="Link to opensea details of Rocketeer" href={ rocketeerUriOnOpensea( chainId, mintedTokenId ) }>View on Opensea</a>
|
||||
|
||||
</Container>
|
||||
|
||||
// Render main interface
|
||||
return (
|
||||
<Container>
|
||||
|
||||
<h1>Rocketeer Minter</h1>
|
||||
<p>We are ready to mint! There are currently { totalSupply } minted Rocketeers.</p>
|
||||
|
||||
<label htmlFor='address'>Minting to:</label>
|
||||
<input id='address' value={ address } disabled />
|
||||
{ contract && <a className="button" href="/#" onClick={ mintRocketeer }>
|
||||
<img alt="metamask fox" src={ Fox } />
|
||||
Mint new Rocketeer
|
||||
</a> }
|
||||
|
||||
|
||||
<img className="stretchBackground" src={ LaunchBackground } alt="Launching rocket" />
|
||||
|
||||
</Container>
|
||||
)
|
||||
if( action === 'mint' ) return <Minter />
|
||||
if( action === 'verify' ) return <Verifier />
|
||||
else return <></>
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
13
minter/src/components/generic.js
Normal file
13
minter/src/components/generic.js
Normal file
@ -0,0 +1,13 @@
|
||||
import LaunchBackground from '../assets/undraw_launch_day_4e04.svg'
|
||||
|
||||
export const Container = ( { children } ) => <main>
|
||||
|
||||
<div className="container">
|
||||
|
||||
{ children }
|
||||
|
||||
</div>
|
||||
|
||||
<img className="stretchBackground" src={ LaunchBackground } alt="Launching rocket" />
|
||||
|
||||
</main>
|
107
minter/src/components/minter.js
Normal file
107
minter/src/components/minter.js
Normal file
@ -0,0 +1,107 @@
|
||||
import Fox from '../assets/metamask-fox.svg'
|
||||
import { Container } from './generic'
|
||||
import '../App.css'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { useAddress, useTotalSupply, useContract, useChainId, rocketeerUriOnOpensea } from '../modules/web3'
|
||||
import { log, setListenerAndReturnUnlistener } from '../modules/helpers'
|
||||
|
||||
export default function Minter() {
|
||||
|
||||
// ///////////////////////////////
|
||||
// States
|
||||
// ///////////////////////////////
|
||||
const [ loading, setLoading ] = useState( false )
|
||||
const [ error, setError ] = useState( undefined )
|
||||
const [ mintedTokenId, setMintedTokenId ] = useState( undefined )
|
||||
const totalSupply = useTotalSupply( )
|
||||
const address = useAddress()
|
||||
const chainId = useChainId()
|
||||
|
||||
// Handle contract interactions
|
||||
const contract = useContract()
|
||||
|
||||
// ///////////////////////////////
|
||||
// Functions
|
||||
// ///////////////////////////////
|
||||
|
||||
async function mintRocketeer( e ) {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
|
||||
if( !address ) setError( 'No destination address selected' )
|
||||
setLoading( 'Confirm transaction in metamask' )
|
||||
const response = await contract.spawnRocketeer( address )
|
||||
log( 'Successful mint with: ', response )
|
||||
|
||||
setLoading( 'Waiting for confirmations...' )
|
||||
|
||||
} catch( e ) {
|
||||
log( 'Minting error: ', e )
|
||||
alert( `Minting error: ${ e.message }` )
|
||||
setLoading( false )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ///////////////////////////////
|
||||
// Lifecycle
|
||||
// ///////////////////////////////
|
||||
useEffect( f => setListenerAndReturnUnlistener( contract, 'Transfer', async ( from, to, amount, event ) => {
|
||||
|
||||
try {
|
||||
|
||||
log( `useEffect: Transfer ${ from } sent to ${ to } `, amount, event )
|
||||
const [ transFrom, transTo, tokenId ] = event.args
|
||||
setMintedTokenId( tokenId.toString() )
|
||||
setLoading( false )
|
||||
|
||||
} catch( e ) {
|
||||
log( 'Error getting Transfer event from contract: ', e )
|
||||
}
|
||||
|
||||
} ), [ contract, loading ] )
|
||||
|
||||
// ///////////////////////////////
|
||||
// Rendering
|
||||
// ///////////////////////////////
|
||||
|
||||
if( error || loading ) return <Container>
|
||||
{ error && <p>{ error }</p> }
|
||||
{ !error && loading && <div className="loading">
|
||||
|
||||
<div className="lds-dual-ring"></div>
|
||||
<p>{ loading }</p>
|
||||
|
||||
</div> }
|
||||
</Container>
|
||||
|
||||
if( mintedTokenId ) return <Container>
|
||||
|
||||
<h1>Minting Successful!</h1>
|
||||
<a className="button" rel="noreferrer" target="_blank" alt="Link to opensea details of Rocketeer" href={ rocketeerUriOnOpensea( chainId, mintedTokenId ) }>View on Opensea</a>
|
||||
|
||||
</Container>
|
||||
|
||||
// Render main interface
|
||||
return (
|
||||
<Container>
|
||||
|
||||
<h1>Rocketeer Minter</h1>
|
||||
<p>We are ready to mint! There are currently { totalSupply } minted Rocketeers.</p>
|
||||
|
||||
<label htmlFor='address'>Minting to:</label>
|
||||
<input id='address' value={ address } disabled />
|
||||
{ contract && <a className="button" href="/#" onClick={ mintRocketeer }>
|
||||
<img alt="metamask fox" src={ Fox } />
|
||||
Mint new Rocketeer
|
||||
</a> }
|
||||
|
||||
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
93
minter/src/components/verifier.js
Normal file
93
minter/src/components/verifier.js
Normal file
@ -0,0 +1,93 @@
|
||||
import { Container } from './generic'
|
||||
import '../App.css'
|
||||
|
||||
import { useState, useEffect, useReducer } from 'react'
|
||||
import { log, setListenerAndReturnUnlistener } from '../modules/helpers'
|
||||
import { useAddress, useChainId, useBalanceOf } from '../modules/web3'
|
||||
|
||||
export default function Verifier() {
|
||||
|
||||
// ///////////////////////////////
|
||||
// State management
|
||||
// ///////////////////////////////
|
||||
const balance = useBalanceOf()
|
||||
const chainId = useChainId()
|
||||
const address = useAddress()
|
||||
const [ username, setUsername ] = useState( )
|
||||
const [ verifyUrl, setVerifyUrl ] = useState()
|
||||
const [ message, setMessage ] = useState()
|
||||
|
||||
// ///////////////////////////////
|
||||
// Functions
|
||||
// ///////////////////////////////
|
||||
function showVerificationUrl( e ) {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if( !username ) return alert( 'Please fill in your Discord username to get verified' )
|
||||
if( balance < 1 ) return alert( `The address ${ address } does not own Rocketeers, did you select the right address?` )
|
||||
|
||||
const baseUrl = `https://mint.rocketeer.fans/?mode=verify`
|
||||
const message = btoa( `{ "username": "${ username }", "address": "${ address }", "balance": "${ balance }" }` )
|
||||
|
||||
setVerifyUrl( baseUrl + `&message=${ message }` )
|
||||
|
||||
}
|
||||
|
||||
function verifyIfNeeded() {
|
||||
|
||||
if( !window.location.href.includes( 'message' ) ) return
|
||||
|
||||
try {
|
||||
|
||||
const { search } = window.location
|
||||
const query = new URLSearchParams( search )
|
||||
const message = query.get( 'message' )
|
||||
const verification = atob( message )
|
||||
log( 'Received message: ', verification )
|
||||
const json = JSON.parse( verification )
|
||||
|
||||
return setMessage( json )
|
||||
|
||||
} catch( e ) {
|
||||
|
||||
log( e )
|
||||
return alert( 'Verification error, contact the team on Discord' )
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ///////////////////////////////
|
||||
// Lifecycle
|
||||
// ///////////////////////////////
|
||||
useEffect( f => {
|
||||
verifyIfNeeded()
|
||||
window.addEventListener( 'popstate', verifyIfNeeded )
|
||||
}, [] )
|
||||
|
||||
// ///////////////////////////////
|
||||
// Rendering
|
||||
// ///////////////////////////////
|
||||
|
||||
if( message ) return <Container>
|
||||
{ message.balance > 0 && <p>✅ { message.username } has { message.balance } Rocketeers</p> }
|
||||
{ message.balance < 1 && <p>🛑 Computer says no</p> }
|
||||
</Container>
|
||||
|
||||
if( verifyUrl ) return <Container>
|
||||
|
||||
<h1>Verification URL</h1>
|
||||
<p>Post this in the Discord channel #get-verified:</p>
|
||||
<p>{ verifyUrl }</p>
|
||||
|
||||
</Container>
|
||||
|
||||
return <Container>
|
||||
|
||||
<h1>Verify your hodlr status</h1>
|
||||
<p>Verify your Rocketeer status by signing a message with your wallet. This does NOT trigger a transaction. Therefore it is free.</p>
|
||||
<input onChange={ e => setUsername( e.target.value ) } type="text" placeholder="Your Discord username" />
|
||||
<a onClick={ showVerificationUrl } href="/#" className="button">Verify</a>
|
||||
|
||||
</Container>
|
||||
}
|
@ -13,6 +13,8 @@ body {
|
||||
|
||||
body * {
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
code {
|
||||
|
@ -99,6 +99,42 @@ export function useTotalSupply() {
|
||||
|
||||
}
|
||||
|
||||
export function useBalanceOf() {
|
||||
|
||||
const [ balance, setBalance ] = useState( 'loading' )
|
||||
const contract = useContract( )
|
||||
const address = useAddress()
|
||||
|
||||
// Create listener to minting
|
||||
useEffect( f => {
|
||||
|
||||
// Do nothing if there is not contract object
|
||||
if( !contract || !address ) return
|
||||
|
||||
// Load initial supply value
|
||||
( async ( ) => {
|
||||
|
||||
try {
|
||||
|
||||
const balance = await contract.balanceOf( address )
|
||||
log( 'Balance detected: ', balance, `(${ balance.toString() })` )
|
||||
setBalance( balance.toString() )
|
||||
|
||||
} catch( e ) {
|
||||
|
||||
log( 'Error getting initial supply: ', e )
|
||||
|
||||
}
|
||||
|
||||
} )( )
|
||||
|
||||
|
||||
}, [ contract, address ] )
|
||||
|
||||
return balance
|
||||
|
||||
}
|
||||
|
||||
// Chain ID hook
|
||||
export function useChainId() {
|
||||
|
||||
@ -138,7 +174,7 @@ const contractAddressByChainId = {
|
||||
'0x4': '0x89D9f02D2877A35E8323DC1b578FD1f6014B04d0'
|
||||
}
|
||||
|
||||
// Contract ABI with only totalSupply and Transfer
|
||||
// Contract ABI with only totalSupply, balanceOf and Transfer
|
||||
const ABI = [
|
||||
{
|
||||
"inputs": [],
|
||||
@ -154,6 +190,26 @@ const ABI = [
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
|
Loading…
x
Reference in New Issue
Block a user