diff --git a/minter/src/App.js b/minter/src/App.js
index 0e0da1e..f99dd6e 100644
--- a/minter/src/App.js
+++ b/minter/src/App.js
@@ -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 } ) =>
-
-
-
- { children }
-
-
-
-
-
-
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
{ error && { error }
}
@@ -116,9 +80,9 @@ function App() {
}
{ !address && ( !error && !loading ) && <>
- Rocketeer Minter
- 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.
-
+ Rocketeer { action == 'mint' ? 'Minter' : 'Verifier' }
+ { action == 'mint' && 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.
}
+ { action == 'verify' && This interface is used to veriy that you are the owner of a Rocketeer
}
Connect wallet
@@ -127,32 +91,9 @@ function App() {
> }
- if( mintedTokenId ) return
-
- Minting Successful!
- View on Opensea
-
-
-
- // Render main interface
- return (
-
-
- Rocketeer Minter
- We are ready to mint! There are currently { totalSupply } minted Rocketeers.
-
- Minting to:
-
- { contract &&
-
- Mint new Rocketeer
- }
-
-
-
-
-
- )
+ if( action === 'mint' ) return
+ if( action === 'verify' ) return
+ else return <>>
}
export default App;
diff --git a/minter/src/components/generic.js b/minter/src/components/generic.js
new file mode 100644
index 0000000..8e37597
--- /dev/null
+++ b/minter/src/components/generic.js
@@ -0,0 +1,13 @@
+import LaunchBackground from '../assets/undraw_launch_day_4e04.svg'
+
+export const Container = ( { children } ) =>
+
+
+
+ { children }
+
+
+
+
+
+
\ No newline at end of file
diff --git a/minter/src/components/minter.js b/minter/src/components/minter.js
new file mode 100644
index 0000000..5c93306
--- /dev/null
+++ b/minter/src/components/minter.js
@@ -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
+ { error && { error }
}
+ { !error && loading && }
+
+
+ if( mintedTokenId ) return
+
+ Minting Successful!
+ View on Opensea
+
+
+
+ // Render main interface
+ return (
+
+
+ Rocketeer Minter
+ We are ready to mint! There are currently { totalSupply } minted Rocketeers.
+
+ Minting to:
+
+ { contract &&
+
+ Mint new Rocketeer
+ }
+
+
+
+ )
+}
+
diff --git a/minter/src/components/verifier.js b/minter/src/components/verifier.js
new file mode 100644
index 0000000..edbb50a
--- /dev/null
+++ b/minter/src/components/verifier.js
@@ -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
+ { message.balance > 0 && ✅ { message.username } has { message.balance } Rocketeers
}
+ { message.balance < 1 && 🛑 Computer says no
}
+
+
+ if( verifyUrl ) return
+
+ Verification URL
+ Post this in the Discord channel #get-verified:
+ { verifyUrl }
+
+
+
+ return
+
+ Verify your hodlr status
+ Verify your Rocketeer status by signing a message with your wallet. This does NOT trigger a transaction. Therefore it is free.
+ setUsername( e.target.value ) } type="text" placeholder="Your Discord username" />
+ Verify
+
+
+}
\ No newline at end of file
diff --git a/minter/src/index.css b/minter/src/index.css
index bf087cf..c5785ad 100644
--- a/minter/src/index.css
+++ b/minter/src/index.css
@@ -13,6 +13,8 @@ body {
body * {
box-sizing: border-box;
+ max-width: 100%;
+ overflow-wrap: break-word;
}
code {
diff --git a/minter/src/modules/web3.js b/minter/src/modules/web3.js
index 5bc644a..07afe2b 100644
--- a/minter/src/modules/web3.js
+++ b/minter/src/modules/web3.js
@@ -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": [