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 { CompilerAbstract } from '@remix-project/remix-solidity'
import { ContractVerificationPluginClient } from './ContractVerificationPluginClient'
import { ContractDropdownSelection } from './components/ContractDropdown'
// Define the type for the context
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 { AppContext } from './AppContext'
import { VerifyFormContext } from './VerifyFormContext'
import DisplayRoutes from './routes'
import type { ContractVerificationSettings, ThemeType, Chain, SubmittedContracts, VerificationReceipt, VerificationResponse } from './types'
import { mergeChainSettingsWithDefaults } from './utils'
@ -11,6 +12,7 @@ import './App.css'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { useLocalStorage } from './hooks/useLocalStorage'
import { getVerifier } from './Verifiers'
import { ContractDropdownSelection } from './components/ContractDropdown'
const plugin = new ContractVerificationPluginClient()
@ -20,6 +22,16 @@ const App = () => {
const [submittedContracts, setSubmittedContracts] = useLocalStorage<SubmittedContracts>('contract-verification:submitted-contracts', {})
const [chains, setChains] = useState<Chain[]>([]) // State to hold the chains data
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)
useEffect(() => {
@ -131,7 +143,9 @@ const App = () => {
return (
<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>
)
}

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

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react'
import { useContext, useEffect, useRef, useState } from 'react'
import { ethers } from 'ethers'
import { AppContext } from '../AppContext'
@ -22,15 +22,34 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
const abi = compiledContract?.abi
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(() => {
setConstructorArgsValues([])
if (constructorArgsInInitialState.current) {
constructorArgsInInitialState.current = false
return
}
setAbiEncodedConstructorArgs('')
setAbiEncodingError('')
if (constructorArgs) {
setConstructorArgsValues(Array(constructorArgs.length).fill(''))
}
setConstructorArgsValues(Array(constructorArgs?.length ?? 0).fill(''))
}, [constructorArgs])
const handleConstructorArgs = (value: string, index: number) => {
@ -67,17 +86,9 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
const handleRawConstructorArgs = (value: string) => {
setAbiEncodedConstructorArgs(value)
try {
const decoded = ethers.utils.defaultAbiCoder.decode(
constructorArgs.map((inp) => inp.type),
value
)
setConstructorArgsValues(decoded.map((val) => JSON.stringify(val)))
setAbiEncodingError('')
} catch (e) {
console.error(e)
setAbiEncodingError('Decoding error: ' + e.message)
}
const { decoded, errorMessage } = decodeConstructorArgs(value)
setConstructorArgsValues(decoded)
setAbiEncodingError(errorMessage)
}
if (!selectedContract) return null
@ -105,7 +116,7 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
{constructorArgs.map((inp, i) => (
<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>
<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>
))}
{abiEncodedConstructorArgs && (

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

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

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

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

Loading…
Cancel
Save