Add Lookup view

pull/5285/head
Manuel Wedler 5 months ago committed by Aniket
parent 1f0a5c46d0
commit 2a42111e42
  1. 33
      apps/contract-verification/src/app/components/ContractAddressInput.tsx
  2. 35
      apps/contract-verification/src/app/components/SearchableChainDropdown.tsx
  3. 3
      apps/contract-verification/src/app/components/index.tsx
  4. 10
      apps/contract-verification/src/app/routes.tsx
  5. 22
      apps/contract-verification/src/app/views/LookupView.tsx
  6. 43
      apps/contract-verification/src/app/views/VerifyView.tsx

@ -0,0 +1,33 @@
import React, { useEffect, useState, useContext } from 'react'
import { ethers } from 'ethers/'
interface ContractAddressInputProps {
label: string
id: string
setContractAddress: (address: string) => void
contractAddress: string
}
// Chooses one contract from the compilation output.
export const ContractAddressInput: React.FC<ContractAddressInputProps> = ({ label, id, setContractAddress, contractAddress }) => {
const [contractAddressError, setContractAddressError] = useState('')
const handleAddressChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const isValidAddress = ethers.utils.isAddress(event.target.value)
setContractAddress(event.target.value)
if (!isValidAddress) {
setContractAddressError('Invalid contract address')
console.error('Invalid contract address')
return
}
setContractAddressError('')
}
return (
<div className="form-group">
<label htmlFor={id}>{label}</label>
<div>{contractAddressError && <div className="text-danger">{contractAddressError}</div>}</div>
<input type="text" className="form-control" id={id} placeholder="0x2738d13E81e..." value={contractAddress} onChange={handleAddressChange} />
</div>
)
}

@ -1,34 +1,53 @@
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect, useRef, useMemo } from 'react'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { Chain } from '../types/VerificationTypes' import { Chain } from '../types/VerificationTypes'
import { AppContext } from '../AppContext'
interface DropdownProps { interface DropdownProps {
label: string label: string
chains: Chain[]
id: string id: string
setSelectedChain: (chain: Chain) => void setSelectedChain: (chain: Chain) => void
selectedChain: Chain selectedChain: Chain
} }
export const SearchableDropdown: React.FC<DropdownProps> = ({ chains, label, id, setSelectedChain, selectedChain }) => { export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, setSelectedChain, selectedChain }) => {
const { chains } = React.useContext(AppContext)
const ethereumChainIds = [1, 3, 4, 5, 11155111, 17000]
// Add Ethereum chains to the head of the chains list. Sort the rest alphabetically
const dropdownChains = useMemo(
() =>
chains.sort((a, b) => {
const isAInEthereum = ethereumChainIds.includes(a.chainId)
const isBInEthereum = ethereumChainIds.includes(b.chainId)
if (isAInEthereum && !isBInEthereum) return -1
if (!isAInEthereum && isBInEthereum) return 1
if (isAInEthereum && isBInEthereum) return ethereumChainIds.indexOf(a.chainId) - ethereumChainIds.indexOf(b.chainId)
return (a.title || a.name).localeCompare(b.title || b.name)
}),
[chains]
)
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [filteredOptions, setFilteredOptions] = useState<Chain[]>(chains) const [filteredOptions, setFilteredOptions] = useState<Chain[]>(dropdownChains)
const dropdownRef = useRef<HTMLDivElement>(null) const dropdownRef = useRef<HTMLDivElement>(null)
const fuse = new Fuse(chains, { const fuse = new Fuse(dropdownChains, {
keys: ['name'], keys: ['name'],
threshold: 0.3, threshold: 0.3,
}) })
useEffect(() => { useEffect(() => {
if (searchTerm === '') { if (searchTerm === '') {
setFilteredOptions(chains) setFilteredOptions(dropdownChains)
} else { } else {
const result = fuse.search(searchTerm) const result = fuse.search(searchTerm)
setFilteredOptions(result.map(({ item }) => item)) setFilteredOptions(result.map(({ item }) => item))
} }
}, [searchTerm, chains]) }, [searchTerm, dropdownChains])
// Close dropdown when user clicks outside // Close dropdown when user clicks outside
useEffect(() => { useEffect(() => {
@ -59,7 +78,7 @@ export const SearchableDropdown: React.FC<DropdownProps> = ({ chains, label, id,
setSearchTerm('') setSearchTerm('')
} }
if (!chains || chains.length === 0) { if (!dropdownChains || dropdownChains.length === 0) {
return ( return (
<div className="dropdown"> <div className="dropdown">
<label htmlFor={id}>{label}</label> <label htmlFor={id}>{label}</label>

@ -1,3 +1,4 @@
export { NavMenu } from './NavMenu' export { NavMenu } from './NavMenu'
export { ContractDropdown } from './ContractDropdown' export { ContractDropdown } from './ContractDropdown'
export { SearchableDropdown } from './SearchableDropdown' export { SearchableChainDropdown } from './SearchableChainDropdown'
export { ContractAddressInput } from './ContractAddressInput'

@ -4,6 +4,7 @@ import { HashRouter as Router, Route, Routes } from 'react-router-dom'
import { VerifyView } from './views' import { VerifyView } from './views'
import { DefaultLayout } from './layouts' import { DefaultLayout } from './layouts'
import { ReceiptsView } from './views/ReceiptsView' import { ReceiptsView } from './views/ReceiptsView'
import { LookupView } from './views/LookupView'
const DisplayRoutes = () => ( const DisplayRoutes = () => (
<Router> <Router>
@ -25,6 +26,15 @@ const DisplayRoutes = () => (
</DefaultLayout> </DefaultLayout>
} }
/> />
<Route
path="/lookup"
element={
<DefaultLayout from="/" title="Lookup" description="Search for verified contracts and download them to Remix">
<LookupView />
</DefaultLayout>
}
/>
</Routes> </Routes>
</Router> </Router>
) )

@ -0,0 +1,22 @@
import { useState } from 'react'
import { SearchableChainDropdown, ContractAddressInput } from '../components'
import { Chain } from '../types/VerificationTypes'
export const LookupView = () => {
const [selectedChain, setSelectedChain] = useState<Chain | undefined>()
const [contractAddress, setContractAddress] = useState('')
const handleLookup = () => {}
return (
<form onSubmit={handleLookup}>
<SearchableChainDropdown label="Chain" id="network-dropdown" setSelectedChain={setSelectedChain} selectedChain={selectedChain} />
<ContractAddressInput label="Contract Address" id="contract-address" setContractAddress={setContractAddress} contractAddress={contractAddress} />
<button type="submit" className="btn btn-primary">
Lookup
</button>
</form>
)
}

@ -1,9 +1,7 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { AppContext } from '../AppContext' import { AppContext } from '../AppContext'
import { SearchableDropdown } from '../components' import { SearchableChainDropdown, ContractDropdown, ContractAddressInput } from '../components'
import { ContractDropdown } from '../components/ContractDropdown'
import { ethers } from 'ethers/'
import { Chain, SubmittedContract, VerificationReceipt } from '../types/VerificationTypes' import { Chain, SubmittedContract, VerificationReceipt } from '../types/VerificationTypes'
import { SourcifyVerifier } from '../Verifiers/SourcifyVerifier' import { SourcifyVerifier } from '../Verifiers/SourcifyVerifier'
import { EtherscanVerifier } from '../Verifiers/EtherscanVerifier' import { EtherscanVerifier } from '../Verifiers/EtherscanVerifier'
@ -11,9 +9,8 @@ import { useNavigate } from 'react-router-dom'
import { ConstructorArguments } from '../components/ConstructorArguments' import { ConstructorArguments } from '../components/ConstructorArguments'
export const VerifyView = () => { export const VerifyView = () => {
const { chains, compilationOutput, verifiers, setVerifiers, selectedContractFileAndName, setSubmittedContracts } = React.useContext(AppContext) const { compilationOutput, verifiers, setVerifiers, selectedContractFileAndName, setSubmittedContracts } = React.useContext(AppContext)
const [contractAddress, setContractAddress] = useState('') const [contractAddress, setContractAddress] = useState('')
const [contractAddressError, setContractAddressError] = useState('')
const [selectedChain, setSelectedChain] = useState<Chain | undefined>() const [selectedChain, setSelectedChain] = useState<Chain | undefined>()
const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = React.useState<string>('') const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = React.useState<string>('')
const navigate = useNavigate() const navigate = useNavigate()
@ -22,20 +19,6 @@ export const VerifyView = () => {
console.log('Selected chain changed', selectedChain) console.log('Selected chain changed', selectedChain)
}, [selectedChain]) }, [selectedChain])
const ethereumChainIds = [1, 3, 4, 5, 11155111, 17000]
// Add Ethereum chains to the head of the chains list. Sort the rest alphabetically
const dropdownChains = chains.sort((a, b) => {
const isAInEthereum = ethereumChainIds.includes(a.chainId)
const isBInEthereum = ethereumChainIds.includes(b.chainId)
if (isAInEthereum && !isBInEthereum) return -1
if (!isAInEthereum && isBInEthereum) return 1
if (isAInEthereum && isBInEthereum) return ethereumChainIds.indexOf(a.chainId) - ethereumChainIds.indexOf(b.chainId)
return (a.title || a.name).localeCompare(b.title || b.name)
})
const handleVerify = async (e) => { const handleVerify = async (e) => {
e.preventDefault() // Don't change the page e.preventDefault() // Don't change the page
@ -96,34 +79,18 @@ export const VerifyView = () => {
}) })
} }
const handleAddressChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const isValidAddress = ethers.utils.isAddress(event.target.value)
setContractAddress(event.target.value)
if (!isValidAddress) {
setContractAddressError('Invalid contract address')
console.error('Invalid contract address')
return
}
setContractAddressError('')
}
console.log('sourcifyVerifiers:', verifiers) console.log('sourcifyVerifiers:', verifiers)
return ( return (
<form onSubmit={handleVerify}> <form onSubmit={handleVerify}>
<SearchableDropdown label="Contract Chain" chains={dropdownChains} id="network-dropdown" setSelectedChain={setSelectedChain} selectedChain={selectedChain} /> <SearchableChainDropdown label="Chain" id="network-dropdown" setSelectedChain={setSelectedChain} selectedChain={selectedChain} />
<div className="form-group"> <ContractAddressInput label="Contract Address" id="contract-address" setContractAddress={setContractAddress} contractAddress={contractAddress} />
<label htmlFor="contract-address">Contract Address</label>
<div>{contractAddressError && <div className="text-danger">{contractAddressError}</div>}</div>
<input type="text" className="form-control" id="contract-address" placeholder="0x2738d13E81e..." value={contractAddress} onChange={handleAddressChange} />
</div>
<ContractDropdown label="Contract Name" id="contract-dropdown-1" /> <ContractDropdown label="Contract Name" id="contract-dropdown-1" />
<button type="submit" className="btn btn-primary"> <button type="submit" className="btn btn-primary">
{' '} Verify
Verify{' '}
</button> </button>
<div> <div>

Loading…
Cancel
Save