mirror of
https://github.com/stronk-dev/RandomChad.git
synced 2025-07-05 10:35:08 +02:00
Minting works on testnet
This commit is contained in:
parent
b19a47b001
commit
701b7e3be6
2105
minter/package-lock.json
generated
2105
minter/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,12 @@
|
|||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.2.7",
|
"@testing-library/react": "^11.2.7",
|
||||||
"@testing-library/user-event": "^12.8.3",
|
"@testing-library/user-event": "^12.8.3",
|
||||||
|
"ethers": "^5.4.7",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"web-vitals": "^1.1.2"
|
"web-vitals": "^1.1.2",
|
||||||
|
"web3": "^1.6.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
@ -2,22 +2,34 @@ import './App.css'
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
import { getAddress, useAddress, useTotalSupply, useContract, signer } from './modules/web3'
|
||||||
|
import { log } from './modules/helpers'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// States
|
||||||
|
// ///////////////////////////////
|
||||||
const [ loading, setLoading ] = useState( 'Detecting metamask...' )
|
const [ loading, setLoading ] = useState( 'Detecting metamask...' )
|
||||||
const [ error, setError ] = useState( undefined )
|
const [ error, setError ] = useState( undefined )
|
||||||
const [ address, setAddress ] = useState( undefined )
|
const totalSupply = useTotalSupply( )
|
||||||
|
const address = useAddress()
|
||||||
|
|
||||||
|
// Handle contract interactions
|
||||||
|
const contract = useContract()
|
||||||
|
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
// Functions
|
// Functions
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
|
|
||||||
|
// Handle user login interaction
|
||||||
async function metamasklogin() {
|
async function metamasklogin() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
setLoading( 'Connecting to Metamask' )
|
setLoading( 'Connecting to Metamask' )
|
||||||
const [ address ] = await window.ethereum.request({ method: 'eth_requestAccounts' })
|
const address = await getAddress()
|
||||||
setAddress( address )
|
log( 'Received: ', address )
|
||||||
|
|
||||||
} catch( e ) {
|
} catch( e ) {
|
||||||
setError( `Metamask error: ${ e.message || JSON.stringify( e ) }. Please reload the page.` )
|
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
|
// Lifecycle
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
// Check for metamask on load
|
// 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' )
|
// Rendering
|
||||||
setLoading( false )
|
// ///////////////////////////////
|
||||||
|
log( error, loading, address )
|
||||||
}, [] )
|
// Initialisation interface
|
||||||
|
if( error || loading || !address ) return <main>
|
||||||
// Render error if it exists
|
{ error && <p>{ error }</p> }
|
||||||
if( error ) return <main>
|
{ loading && <p>{ loading }</p> }
|
||||||
<p>{ error }</p>
|
{ !address && <button onClick={ metamasklogin }>Connect Metamask</button> }
|
||||||
</main>
|
</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
|
// Render main interface
|
||||||
return (
|
return (
|
||||||
<main>
|
<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>
|
</main>
|
||||||
);
|
);
|
||||||
|
7
minter/src/modules/helpers.js
Normal file
7
minter/src/modules/helpers.js
Normal 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
245
minter/src/modules/web3.js
Normal 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
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user