Persist form data when switching tabs

pull/5285/head
Manuel Wedler 4 months ago committed by Aniket
parent 456448d7d6
commit ed1ce19c3f
  1. 1
      apps/contract-verification/src/app/AppContext.tsx
  2. 46
      apps/contract-verification/src/app/VerifyFormContext.tsx
  3. 16
      apps/contract-verification/src/app/app.tsx
  4. 1
      apps/contract-verification/src/app/components/ConfigInput.tsx
  5. 47
      apps/contract-verification/src/app/components/ConstructorArguments.tsx
  6. 32
      apps/contract-verification/src/app/components/ContractDropdown.tsx
  7. 2
      apps/contract-verification/src/app/components/SearchableChainDropdown.tsx
  8. 3
      apps/contract-verification/src/app/views/LookupView.tsx
  9. 3
      apps/contract-verification/src/app/views/SettingsView.tsx
  10. 19
      apps/contract-verification/src/app/views/VerifyView.tsx

@ -2,6 +2,7 @@ import React from 'react'
import type { ThemeType, Chain, SubmittedContracts, ContractVerificationSettings } from './types' import type { ThemeType, Chain, SubmittedContracts, ContractVerificationSettings } from './types'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractVerificationPluginClient } from './ContractVerificationPluginClient' import { ContractVerificationPluginClient } from './ContractVerificationPluginClient'
import { ContractDropdownSelection } from './components/ContractDropdown'
// Define the type for the context // Define the type for the context
type AppContextType = { type AppContextType = {

@ -0,0 +1,46 @@
import React from 'react'
import type { Chain } from './types'
import { ContractDropdownSelection } from './components/ContractDropdown'
// Define the type for the context
type VerifyFormContextType = {
selectedChain: Chain | undefined
setSelectedChain: React.Dispatch<React.SetStateAction<Chain>>
contractAddress: string
setContractAddress: React.Dispatch<React.SetStateAction<string>>
contractAddressError: string
setContractAddressError: React.Dispatch<React.SetStateAction<string>>
selectedContract: ContractDropdownSelection | undefined
setSelectedContract: React.Dispatch<React.SetStateAction<ContractDropdownSelection>>
proxyAddress: string
setProxyAddress: React.Dispatch<React.SetStateAction<string>>
proxyAddressError: string
setProxyAddressError: React.Dispatch<React.SetStateAction<string>>
abiEncodedConstructorArgs: string
setAbiEncodedConstructorArgs: React.Dispatch<React.SetStateAction<string>>
abiEncodingError: string
setAbiEncodingError: React.Dispatch<React.SetStateAction<string>>
}
// Provide a default value with the appropriate types
const defaultContextValue: VerifyFormContextType = {
selectedChain: undefined,
setSelectedChain: (selectedChain: Chain) => {},
contractAddress: '',
setContractAddress: (contractAddress: string) => {},
contractAddressError: '',
setContractAddressError: (contractAddressError: string) => {},
selectedContract: undefined,
setSelectedContract: (selectedContract: ContractDropdownSelection) => {},
proxyAddress: '',
setProxyAddress: (proxyAddress: string) => {},
proxyAddressError: '',
setProxyAddressError: (contractAddressError: string) => {},
abiEncodedConstructorArgs: '',
setAbiEncodedConstructorArgs: (contractAddproxyAddressress: string) => {},
abiEncodingError: '',
setAbiEncodingError: (contractAddressError: string) => {},
}
// Create the context with the type
export const VerifyFormContext = React.createContext<VerifyFormContextType>(defaultContextValue)

@ -3,6 +3,7 @@ import { useState, useEffect, useRef } from 'react'
import { ContractVerificationPluginClient } from './ContractVerificationPluginClient' import { ContractVerificationPluginClient } from './ContractVerificationPluginClient'
import { AppContext } from './AppContext' import { AppContext } from './AppContext'
import { VerifyFormContext } from './VerifyFormContext'
import DisplayRoutes from './routes' import DisplayRoutes from './routes'
import type { ContractVerificationSettings, ThemeType, Chain, SubmittedContracts, VerificationReceipt, VerificationResponse } from './types' import type { ContractVerificationSettings, ThemeType, Chain, SubmittedContracts, VerificationReceipt, VerificationResponse } from './types'
import { mergeChainSettingsWithDefaults } from './utils' import { mergeChainSettingsWithDefaults } from './utils'
@ -11,6 +12,7 @@ import './App.css'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import { useLocalStorage } from './hooks/useLocalStorage' import { useLocalStorage } from './hooks/useLocalStorage'
import { getVerifier } from './Verifiers' import { getVerifier } from './Verifiers'
import { ContractDropdownSelection } from './components/ContractDropdown'
const plugin = new ContractVerificationPluginClient() const plugin = new ContractVerificationPluginClient()
@ -20,6 +22,16 @@ const App = () => {
const [submittedContracts, setSubmittedContracts] = useLocalStorage<SubmittedContracts>('contract-verification:submitted-contracts', {}) const [submittedContracts, setSubmittedContracts] = useLocalStorage<SubmittedContracts>('contract-verification:submitted-contracts', {})
const [chains, setChains] = useState<Chain[]>([]) // State to hold the chains data const [chains, setChains] = useState<Chain[]>([]) // State to hold the chains data
const [compilationOutput, setCompilationOutput] = useState<{ [key: string]: CompilerAbstract } | undefined>() const [compilationOutput, setCompilationOutput] = useState<{ [key: string]: CompilerAbstract } | undefined>()
// Form values:
const [selectedChain, setSelectedChain] = useState<Chain | undefined>()
const [contractAddress, setContractAddress] = useState('')
const [contractAddressError, setContractAddressError] = useState('')
const [selectedContract, setSelectedContract] = useState<ContractDropdownSelection | undefined>()
const [proxyAddress, setProxyAddress] = useState('')
const [proxyAddressError, setProxyAddressError] = useState('')
const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = useState<string>('')
const [abiEncodingError, setAbiEncodingError] = useState<string>('')
const timer = useRef(null) const timer = useRef(null)
useEffect(() => { useEffect(() => {
@ -131,7 +143,9 @@ const App = () => {
return ( return (
<AppContext.Provider value={{ themeType, setThemeType, clientInstance: plugin, settings, setSettings, chains, compilationOutput, submittedContracts, setSubmittedContracts }}> <AppContext.Provider value={{ themeType, setThemeType, clientInstance: plugin, settings, setSettings, chains, compilationOutput, submittedContracts, setSubmittedContracts }}>
<DisplayRoutes /> <VerifyFormContext.Provider value={{ selectedChain, setSelectedChain, contractAddress, setContractAddress, contractAddressError, setContractAddressError, selectedContract, setSelectedContract, proxyAddress, setProxyAddress, proxyAddressError, setProxyAddressError, abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, abiEncodingError, setAbiEncodingError }}>
<DisplayRoutes />
</VerifyFormContext.Provider>
</AppContext.Provider> </AppContext.Provider>
) )
} }

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { ethers } from 'ethers/'
interface ConfigInputProps { interface ConfigInputProps {
label: string label: string

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react' import { useContext, useEffect, useRef, useState } from 'react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { AppContext } from '../AppContext' import { AppContext } from '../AppContext'
@ -22,15 +22,34 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
const abi = compiledContract?.abi const abi = compiledContract?.abi
const constructorArgs = abi && abi.find((a) => a.type === 'constructor')?.inputs const constructorArgs = abi && abi.find((a) => a.type === 'constructor')?.inputs
const [constructorArgsValues, setConstructorArgsValues] = useState<string[]>(Array(constructorArgs?.length ?? 0).fill(''))
const decodeConstructorArgs = (value: string) => {
try {
const decodedObj = ethers.utils.defaultAbiCoder.decode(
constructorArgs.map((inp) => inp.type),
value
)
const decoded = decodedObj.map((val) => JSON.stringify(val))
return { decoded, errorMessage: '' }
} catch (e) {
console.error(e)
const errorMessage = 'Decoding error: ' + e.message
const decoded = Array(constructorArgs?.length ?? 0).fill('')
return { decoded, errorMessage }
}
}
const [constructorArgsValues, setConstructorArgsValues] = useState<string[]>(abiEncodedConstructorArgs ? decodeConstructorArgs(abiEncodedConstructorArgs).decoded : Array(constructorArgs?.length ?? 0).fill(''))
const constructorArgsInInitialState = useRef(true)
useEffect(() => { useEffect(() => {
setConstructorArgsValues([]) if (constructorArgsInInitialState.current) {
constructorArgsInInitialState.current = false
return
}
setAbiEncodedConstructorArgs('') setAbiEncodedConstructorArgs('')
setAbiEncodingError('') setAbiEncodingError('')
if (constructorArgs) { setConstructorArgsValues(Array(constructorArgs?.length ?? 0).fill(''))
setConstructorArgsValues(Array(constructorArgs.length).fill(''))
}
}, [constructorArgs]) }, [constructorArgs])
const handleConstructorArgs = (value: string, index: number) => { const handleConstructorArgs = (value: string, index: number) => {
@ -67,17 +86,9 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
const handleRawConstructorArgs = (value: string) => { const handleRawConstructorArgs = (value: string) => {
setAbiEncodedConstructorArgs(value) setAbiEncodedConstructorArgs(value)
try { const { decoded, errorMessage } = decodeConstructorArgs(value)
const decoded = ethers.utils.defaultAbiCoder.decode( setConstructorArgsValues(decoded)
constructorArgs.map((inp) => inp.type), setAbiEncodingError(errorMessage)
value
)
setConstructorArgsValues(decoded.map((val) => JSON.stringify(val)))
setAbiEncodingError('')
} catch (e) {
console.error(e)
setAbiEncodingError('Decoding error: ' + e.message)
}
} }
if (!selectedContract) return null if (!selectedContract) return null
@ -105,7 +116,7 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
{constructorArgs.map((inp, i) => ( {constructorArgs.map((inp, i) => (
<div key={`constructor-arg-${inp.name}`} className="d-flex flex-row align-items-center justify-content-between mb-2"> <div key={`constructor-arg-${inp.name}`} className="d-flex flex-row align-items-center justify-content-between mb-2">
<div className="mr-2 small">{inp.name}</div> <div className="mr-2 small">{inp.name}</div>
<input className="form-control w-50" placeholder={inp.type} value={constructorArgsValues[i]} onChange={(e) => handleConstructorArgs(e.target.value, i)} /> <input className="form-control w-50" placeholder={inp.type} value={constructorArgsValues[i] ?? ''} onChange={(e) => handleConstructorArgs(e.target.value, i)} />
</div> </div>
))} ))}
{abiEncodedConstructorArgs && ( {abiEncodedConstructorArgs && (

@ -1,4 +1,4 @@
import React, { useEffect, useState, useContext } from 'react' import React, { useEffect, useState, useContext, Fragment } from 'react'
import './ContractDropdown.css' import './ContractDropdown.css'
import { AppContext } from '../AppContext' import { AppContext } from '../AppContext'
@ -11,15 +11,16 @@ export interface ContractDropdownSelection {
interface ContractDropdownProps { interface ContractDropdownProps {
label: string label: string
id: string id: string
selectedContract: ContractDropdownSelection
setSelectedContract: (selection: ContractDropdownSelection) => void setSelectedContract: (selection: ContractDropdownSelection) => void
} }
// Chooses one contract from the compilation output. // Chooses one contract from the compilation output.
export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id, setSelectedContract }) => { export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id, selectedContract, setSelectedContract }) => {
const { compilationOutput } = useContext(AppContext) const { compilationOutput } = useContext(AppContext)
useEffect(() => { useEffect(() => {
if (!compilationOutput) return if (!compilationOutput || !!selectedContract) return
// Otherwise select the first by default // Otherwise select the first by default
const triggerFilePath = Object.keys(compilationOutput)[0] const triggerFilePath = Object.keys(compilationOutput)[0]
const contracts = compilationOutput[triggerFilePath]?.data?.contracts const contracts = compilationOutput[triggerFilePath]?.data?.contracts
@ -42,21 +43,24 @@ export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id, s
return ( return (
<div className="form-group"> <div className="form-group">
<label htmlFor={id}>{label}</label> <label htmlFor={id}>{label}</label>
<select className={`form-control custom-select pr-4 ${!hasContracts ? 'disabled-cursor' : ''}`} id={id} disabled={!hasContracts} onChange={handleSelectContract}> <select value={selectedContract ? JSON.stringify(selectedContract) : ''} className={`form-control custom-select pr-4 ${!hasContracts ? 'disabled-cursor' : ''}`} id={id} disabled={!hasContracts} onChange={handleSelectContract}>
{hasContracts ? ( {hasContracts ? (
Object.keys(compilationOutput).map((compilationTriggerFileName) => ( Object.keys(compilationOutput).map((compilationTriggerFileName) => (
<optgroup key={compilationTriggerFileName} label={`[Compilation Trigger File]: ${compilationTriggerFileName}`}> <optgroup key={compilationTriggerFileName} label={`[Compilation Trigger File]: ${compilationTriggerFileName}`}>
{Object.keys(compilationOutput[compilationTriggerFileName].data.contracts).map((fileName2) => ( {Object.keys(compilationOutput[compilationTriggerFileName].data.contracts).map((fileName) => (
<> <Fragment key={`${compilationTriggerFileName}:${fileName}`}>
<option disabled style={{ fontWeight: 'bold' }}> <option disabled value={`${compilationTriggerFileName}:${fileName}`} style={{ fontWeight: 'bold' }}>
[File]: {fileName2} [File]: {fileName}
</option> </option>
{Object.keys(compilationOutput[compilationTriggerFileName].data.contracts[fileName2]).map((contractName) => ( {Object.keys(compilationOutput[compilationTriggerFileName].data.contracts[fileName]).map((contractName) => {
<option key={fileName2 + ':' + contractName} value={JSON.stringify({ triggerFilePath: compilationTriggerFileName, filePath: fileName2, contractName: contractName })}> const value = JSON.stringify({ triggerFilePath: compilationTriggerFileName, filePath: fileName, contractName: contractName })
{'\u00A0\u00A0\u00A0' + contractName} {/* Indentation for contract names */} return (
</option> <option key={`${compilationTriggerFileName}:${fileName}:${contractName}`} value={value}>
))} {'\u00A0\u00A0\u00A0' + contractName} {/* Indentation for contract names */}
</> </option>
)
})}
</Fragment>
))} ))}
</optgroup> </optgroup>
)) ))

@ -34,7 +34,7 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
[chains] [chains]
) )
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState(selectedChain ? getChainDescriptor(selectedChain) : '')
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [filteredOptions, setFilteredOptions] = useState<Chain[]>(dropdownChains) const [filteredOptions, setFilteredOptions] = useState<Chain[]>(dropdownChains)
const dropdownRef = useRef<HTMLDivElement>(null) const dropdownRef = useRef<HTMLDivElement>(null)

@ -7,10 +7,11 @@ import { AppContext } from '../AppContext'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import { getVerifier } from '../Verifiers' import { getVerifier } from '../Verifiers'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { VerifyFormContext } from '../VerifyFormContext'
export const LookupView = () => { export const LookupView = () => {
const { settings, clientInstance } = useContext(AppContext) const { settings, clientInstance } = useContext(AppContext)
const [selectedChain, setSelectedChain] = useState<Chain | undefined>() const { selectedChain, setSelectedChain } = useContext(VerifyFormContext)
const [contractAddress, setContractAddress] = useState('') const [contractAddress, setContractAddress] = useState('')
const [contractAddressError, setContractAddressError] = useState('') const [contractAddressError, setContractAddressError] = useState('')
const [loadingVerifiers, setLoadingVerifiers] = useState<Partial<Record<VerifierIdentifier, boolean>>>({}) const [loadingVerifiers, setLoadingVerifiers] = useState<Partial<Record<VerifierIdentifier, boolean>>>({})

@ -3,10 +3,11 @@ import { SearchableChainDropdown, ConfigInput } from '../components'
import type { VerifierIdentifier, Chain, VerifierSettings, ContractVerificationSettings } from '../types' import type { VerifierIdentifier, Chain, VerifierSettings, ContractVerificationSettings } from '../types'
import { mergeChainSettingsWithDefaults } from '../utils' import { mergeChainSettingsWithDefaults } from '../utils'
import { AppContext } from '../AppContext' import { AppContext } from '../AppContext'
import { VerifyFormContext } from '../VerifyFormContext'
export const SettingsView = () => { export const SettingsView = () => {
const { settings, setSettings } = useContext(AppContext) const { settings, setSettings } = useContext(AppContext)
const [selectedChain, setSelectedChain] = useState<Chain | undefined>() const { selectedChain, setSelectedChain } = useContext(VerifyFormContext)
const chainSettings = selectedChain ? mergeChainSettingsWithDefaults(selectedChain.chainId.toString(), settings) : undefined const chainSettings = selectedChain ? mergeChainSettingsWithDefaults(selectedChain.chainId.toString(), settings) : undefined

@ -2,27 +2,20 @@ import { useContext, useEffect, useState } from 'react'
import { AppContext } from '../AppContext' import { AppContext } from '../AppContext'
import { SearchableChainDropdown, ContractDropdown, ContractAddressInput } from '../components' import { SearchableChainDropdown, ContractDropdown, ContractAddressInput } from '../components'
import type { VerifierIdentifier, Chain, SubmittedContract, VerificationReceipt, VerifierInfo, VerificationResponse } from '../types' import type { VerifierIdentifier, SubmittedContract, VerificationReceipt, VerifierInfo, VerificationResponse } from '../types'
import { VERIFIERS } from '../types' import { VERIFIERS } from '../types'
import { mergeChainSettingsWithDefaults, validConfiguration } from '../utils' import { mergeChainSettingsWithDefaults, validConfiguration } from '../utils'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { ConstructorArguments } from '../components/ConstructorArguments' import { ConstructorArguments } from '../components/ConstructorArguments'
import { ContractDropdownSelection } from '../components/ContractDropdown'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import { AbstractVerifier, getVerifier } from '../Verifiers' import { AbstractVerifier, getVerifier } from '../Verifiers'
import { VerifyFormContext } from '../VerifyFormContext'
export const VerifyView = () => { export const VerifyView = () => {
const { compilationOutput, setSubmittedContracts, settings } = useContext(AppContext) const { compilationOutput, setSubmittedContracts, settings } = useContext(AppContext)
const [selectedChain, setSelectedChain] = useState<Chain | undefined>() const { selectedChain, setSelectedChain, contractAddress, setContractAddress, contractAddressError, setContractAddressError, selectedContract, setSelectedContract, proxyAddress, setProxyAddress, proxyAddressError, setProxyAddressError, abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, abiEncodingError, setAbiEncodingError } = useContext(VerifyFormContext)
const [contractAddress, setContractAddress] = useState('')
const [contractAddressError, setContractAddressError] = useState('')
const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = useState<string>('')
const [abiEncodingError, setAbiEncodingError] = useState<string>('')
const [selectedContract, setSelectedContract] = useState<ContractDropdownSelection | undefined>()
const [enabledVerifiers, setEnabledVerifiers] = useState<Partial<Record<VerifierIdentifier, boolean>>>({}) const [enabledVerifiers, setEnabledVerifiers] = useState<Partial<Record<VerifierIdentifier, boolean>>>({})
const [hasProxy, setHasProxy] = useState(false) const [hasProxy, setHasProxy] = useState(!!proxyAddress)
const [proxyAddress, setProxyAddress] = useState('')
const [proxyAddressError, setProxyAddressError] = useState('')
const navigate = useNavigate() const navigate = useNavigate()
const chainSettings = selectedChain ? mergeChainSettingsWithDefaults(selectedChain.chainId.toString(), settings) : undefined const chainSettings = selectedChain ? mergeChainSettingsWithDefaults(selectedChain.chainId.toString(), settings) : undefined
@ -116,6 +109,8 @@ export const VerifyView = () => {
setSubmittedContracts((prev) => ({ ...prev, [newSubmittedContract.id]: newSubmittedContract })) setSubmittedContracts((prev) => ({ ...prev, [newSubmittedContract.id]: newSubmittedContract }))
setContractAddress('')
// Take user to receipt view // Take user to receipt view
navigate('/receipts') navigate('/receipts')
@ -164,7 +159,7 @@ export const VerifyView = () => {
<ContractAddressInput label="Contract Address" id="contract-address" contractAddress={contractAddress} setContractAddress={setContractAddress} contractAddressError={contractAddressError} setContractAddressError={setContractAddressError} /> <ContractAddressInput label="Contract Address" id="contract-address" contractAddress={contractAddress} setContractAddress={setContractAddress} contractAddressError={contractAddressError} setContractAddressError={setContractAddressError} />
<ContractDropdown label="Contract Name" id="contract-dropdown-1" setSelectedContract={setSelectedContract} /> <ContractDropdown label="Contract Name" id="contract-dropdown-1" selectedContract={selectedContract} setSelectedContract={setSelectedContract} />
{selectedContract && <ConstructorArguments abiEncodedConstructorArgs={abiEncodedConstructorArgs} setAbiEncodedConstructorArgs={setAbiEncodedConstructorArgs} selectedContract={selectedContract} abiEncodingError={abiEncodingError} setAbiEncodingError={setAbiEncodingError} />} {selectedContract && <ConstructorArguments abiEncodedConstructorArgs={abiEncodedConstructorArgs} setAbiEncodedConstructorArgs={setAbiEncodedConstructorArgs} selectedContract={selectedContract} abiEncodingError={abiEncodingError} setAbiEncodingError={setAbiEncodingError} />}

Loading…
Cancel
Save