Minting works on testnet

This commit is contained in:
Mentor Palokaj 2021-10-15 14:37:07 +02:00
parent b19a47b001
commit 701b7e3be6
5 changed files with 2405 additions and 26 deletions

2105
minter/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,12 @@
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"ethers": "^5.4.7",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.2"
"web-vitals": "^1.1.2",
"web3": "^1.6.0"
},
"scripts": {
"start": "react-scripts start",

View File

@ -2,22 +2,34 @@ import './App.css'
import { useState, useEffect } from 'react'
import { getAddress, useAddress, useTotalSupply, useContract, signer } from './modules/web3'
import { log } from './modules/helpers'
function App() {
// ///////////////////////////////
// States
// ///////////////////////////////
const [ loading, setLoading ] = useState( 'Detecting metamask...' )
const [ error, setError ] = useState( undefined )
const [ address, setAddress ] = useState( undefined )
const totalSupply = useTotalSupply( )
const address = useAddress()
// Handle contract interactions
const contract = useContract()
// ///////////////////////////////
// Functions
// ///////////////////////////////
// Handle user login interaction
async function metamasklogin() {
try {
setLoading( 'Connecting to Metamask' )
const [ address ] = await window.ethereum.request({ method: 'eth_requestAccounts' })
setAddress( address )
const address = await getAddress()
log( 'Received: ', address )
} catch( e ) {
setError( `Metamask error: ${ e.message || JSON.stringify( e ) }. Please reload the page.` )
@ -27,41 +39,49 @@ function App() {
}
async function mintRocketeer() {
try {
if( !address ) setError( 'No destination address selected' )
setLoading( 'Confirm transaction in metamask' )
const response = await contract.spawnRocketeer( address )
log( 'Successful mint with: ', response )
} catch( e ) {
log( 'Minting error: ', e )
} finally {
setLoading( false )
}
}
// ///////////////////////////////
// Lifecycle
// ///////////////////////////////
// Check for metamask on load
useEffect( f => {
useEffect( f => window.ethereum ? setLoading( false ) : setError( 'No web3 provider detected, please install metamask' ), [] )
// if no web3, stop
if( !window.ethereum ) return setError( 'No web3 provider detected, please install metamask' )
setLoading( false )
}, [] )
// Render error if it exists
if( error ) return <main>
<p>{ error }</p>
// ///////////////////////////////
// Rendering
// ///////////////////////////////
log( error, loading, address )
// Initialisation interface
if( error || loading || !address ) return <main>
{ error && <p>{ error }</p> }
{ loading && <p>{ loading }</p> }
{ !address && <button onClick={ metamasklogin }>Connect Metamask</button> }
</main>
// Render loading indicator
if( loading ) return <main>
<p>{ loading }</p>
</main>
// No address known? Show connect button
if( !address ) return <main>
<button onClick={ metamasklogin }>Connect Metamask</button>
</main>
// Render main interface
return (
<main>
<p>Logged in as ${ address }</p>
<p>Logged in as { address }</p>
<p>Total minted: { totalSupply }</p>
{ contract && <button onClick={ mintRocketeer }>Mint new Rocketeer</button> }
</main>
);

View File

@ -0,0 +1,7 @@
const { location } = window
export const dev = process.env.NODE_ENV === 'development' || ( typeof location !== 'undefined' && location.href.includes( 'debug=true' ) )
export const log = ( ...messages ) => {
if( dev ) console.log( ...messages )
}

245
minter/src/modules/web3.js Normal file
View File

@ -0,0 +1,245 @@
import { useState, useEffect } from "react"
import { log } from './helpers'
// Ethers and web3 sdk
import { ethers } from "ethers"
// Convenience objects
const { providers: { Web3Provider }, Contract } = ethers
const { ethereum } = window
export const provider = ethereum && new Web3Provider(ethereum)
export const signer = provider && provider.getSigner()
// ///////////////////////////////
// Chain interactors
// ///////////////////////////////
// Get address through metamask
export async function getAddress() {
// Check if web3 is exposed
if( !ethereum ) throw new Error( 'No web3 provider detected, please install metamask' )
// Get the first address ( which is the selected address )
const [ address ] = await window.ethereum.request({ method: 'eth_requestAccounts' })
return address
}
// Address hook
export function useAddress() {
const [ address, setAddress ] = useState( undefined )
// Set initial value if known
useEffect( f => {
if( ethereum.selectedAddress ) setAddress( ethereum.selectedAddress )
}, [] )
// Create listener to accounts change
useEffect( f => {
if( !ethereum ) return
log( 'Starting account listener with ', ethereum )
const listener = ethereum.on( 'accountsChanged', addresses => {
log( 'Addresses changed to ', addresses )
setAddress( addresses[0] )
} )
return () => ethereum.removeListener( 'accountsChanged', listener )
}, [] )
return address
}
export function useTotalSupply() {
const [ supply, setSupply ] = useState( 'loading' )
const contract = useContract( )
// Create listener to minting
useEffect( f => {
// Do nothing if there is not contract object
if( !contract ) return
// Load initial supply value
( async ( ) => {
try {
const supply = await contract.totalSupply()
log( 'Initial supply detected: ', supply )
setSupply( supply.toString() )
} catch( e ) {
log( 'Error getting initial supply: ', e )
}
} )( )
// Listen to token transfers andor mints
const listener = contract.on( 'Transfer', async ( from, to, amount, event ) => {
try {
log( `Transfer ${ from } sent to ${ to } `, amount, event )
const supply = await contract.totalSupply()
log( 'Got supply from contract: ', supply )
setSupply( supply.toString() )
} catch( e ) {
log( 'Error getting supply from contract: ', e )
}
} )
return () => contract.removeListener( 'Transfer', listener )
}, [ contract ] )
return supply
}
// Chain ID hook
export function useChainId() {
const [ chain, setChain ] = useState( undefined )
// Create listener to chain change
useEffect( f => {
if( !ethereum ) return
const listener = ethereum.on('chainChanged', chainId => {
log( 'Chain changed to ', chainId )
setChain( chainId )
} )
return ( ) => ethereum.removeListener( 'chainChanged', listener )
}, [] )
// Initial chain detection
useEffect( f => {
// Check for initial chain and set to state
( async () => {
if( !ethereum ) return
const initialChain = await ethereum.request( { method: 'eth_chainId' } )
log( 'Initial chain detected as ', initialChain )
setChain( initialChain )
} )( )
}, [] )
return chain
}
// ///////////////////////////////
// Contract interactors
// ///////////////////////////////
const contractAddressByChainId = {
'0x1': '',
'0x4': '0x2829ba9d76e675b8867E1707A9aB49B280D916c6'
}
const ABI = [
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
}
],
"name": "spawnRocketeer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
export function useContract() {
const chainId = useChainId()
const [ contract, setContract ] = useState( undefined )
useEffect( f => {
try {
// If no chain id is known, stop
if( !chainId ) return
// Generate contract interface for this chain ID
log( `Generating new contract with chain ${ chainId } and address `, contractAddressByChainId[ chainId ] )
const newContract = new Contract( contractAddressByChainId[ chainId ], ABI, signer )
// Set new contract to state
setContract( newContract )
log( 'New contract interface initialised: ', newContract )
} catch( e ) {
alert( `Blockchain error: ${ e.message || JSON.stringify( e ) }` )
log( 'Error generating contract: ', e )
setContract( undefined )
}
}, [ chainId ] )
return contract
}