-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from ethereum-optimism/11-26-feat_add_contract…
…_verification_flow feat: add contract verification flow
- Loading branch information
Showing
14 changed files
with
699 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import {fromFoundryArtifactPath} from '@/forge/foundryProject'; | ||
import {queryMappingChainListItemByIdentifier} from '@/queries/chainRegistryItemByIdentifier'; | ||
import {useContractVerificationStore} from '@/stores/contractVerification'; | ||
import {zodSupportedNetwork} from '@/superchain-registry/fetchChainList'; | ||
import {zodAddress} from '@/validators/schemas'; | ||
import {verifyContractOnBlockscout} from '@/verify/blockscout'; | ||
import {createStandardJsonInput} from '@/verify/createStandardJsonInput'; | ||
import {identifyExplorer} from '@/verify/identifyExplorerType'; | ||
import {option} from 'pastel'; | ||
import {z} from 'zod'; | ||
|
||
export const zodVerifyContractParams = z.object({ | ||
forgeArtifactPath: z | ||
.string() | ||
.describe( | ||
option({ | ||
description: 'Path to the Forge artifact', | ||
alias: 'f', | ||
}), | ||
) | ||
.min(1), | ||
contractAddress: zodAddress.describe( | ||
option({description: 'Contract address', alias: 'a'}), | ||
), | ||
network: zodSupportedNetwork.describe( | ||
option({ | ||
description: 'Network to verify on', | ||
alias: 'n', | ||
}), | ||
), | ||
chains: z.array(z.string()).describe( | ||
option({ | ||
description: 'Chains to verify on', | ||
alias: 'c', | ||
}), | ||
), | ||
}); | ||
|
||
const getVerifyContractContext = async ( | ||
params: z.infer<typeof zodVerifyContractParams>, | ||
) => { | ||
const {forgeArtifactPath, network, chains} = params; | ||
const chainIdentifiers = chains.map(chain => `${network}/${chain}`); | ||
|
||
const chainListItemByIdentifier = | ||
await queryMappingChainListItemByIdentifier(); | ||
const {foundryProject, contractFileName} = await fromFoundryArtifactPath( | ||
forgeArtifactPath, | ||
); | ||
|
||
return { | ||
selectedChainList: chainIdentifiers.map( | ||
identifier => chainListItemByIdentifier[identifier], | ||
), | ||
foundryProject, | ||
contractFileName, | ||
}; | ||
}; | ||
|
||
export const verifyContract = async ( | ||
params: z.infer<typeof zodVerifyContractParams>, | ||
) => { | ||
const store = useContractVerificationStore.getState(); | ||
|
||
const {contractAddress, chains} = params; | ||
|
||
let context: Awaited<ReturnType<typeof getVerifyContractContext>>; | ||
try { | ||
context = await getVerifyContractContext(params); | ||
|
||
store.setPrepareSuccess( | ||
context.selectedChainList.map(chain => chain!.chainId), | ||
); | ||
} catch (e) { | ||
store.setPrepareError(e as Error); | ||
return; | ||
} | ||
|
||
const {foundryProject, contractFileName} = context; | ||
|
||
// TODO: Type this | ||
let standardJsonInput: any; | ||
try { | ||
standardJsonInput = await createStandardJsonInput( | ||
foundryProject.baseDir, | ||
contractFileName, | ||
); | ||
store.setGenerateSuccess(); | ||
} catch (e) { | ||
store.setGenerateError(e as Error); | ||
return; | ||
} | ||
|
||
await Promise.all( | ||
context.selectedChainList.map(async chain => { | ||
const chainId = chain!.chainId; | ||
const explorer = chain!.explorers[0]!; | ||
|
||
store.setVerifyPending(chainId); | ||
|
||
const explorerType = await identifyExplorer(explorer).catch(() => null); | ||
|
||
if (explorerType !== 'blockscout') { | ||
throw new Error('Unsupported explorer'); | ||
} | ||
|
||
try { | ||
await verifyContractOnBlockscout( | ||
chain!.explorers[0]!, | ||
contractAddress, | ||
contractFileName.replace('.sol', ''), | ||
standardJsonInput, | ||
); | ||
store.setVerifySuccess(chainId); | ||
} catch (e) { | ||
store.setVerifyError(chainId, e as Error); | ||
return; | ||
} | ||
}), | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import {Box, Text} from 'ink'; | ||
import {useEffect} from 'react'; | ||
import { | ||
verifyContract, | ||
zodVerifyContractParams, | ||
} from '@/actions/verifyContract'; | ||
import {z} from 'zod'; | ||
import {useContractVerificationStore} from '@/stores/contractVerification'; | ||
import {Spinner} from '@inkjs/ui'; | ||
import {useMappingChainById} from '@/queries/chainById'; | ||
|
||
const zodVerifyContractCommandParams = zodVerifyContractParams; | ||
|
||
const VerifyCommand = ({ | ||
options, | ||
}: { | ||
options: z.infer<typeof zodVerifyContractCommandParams>; | ||
}) => { | ||
useEffect(() => { | ||
verifyContract(options); | ||
}, []); | ||
|
||
const {currentStep, stateByChainId} = useContractVerificationStore(); | ||
const {data: chainById} = useMappingChainById(); | ||
|
||
if (currentStep === 'prepare' || !chainById) { | ||
return ( | ||
<Box> | ||
<Spinner label="Preparing..." /> | ||
</Box> | ||
); | ||
} | ||
|
||
if (currentStep === 'generate-standard-json-input') { | ||
return ( | ||
<Box> | ||
<Spinner label="Generating standard JSON input..." /> | ||
</Box> | ||
); | ||
} | ||
|
||
const chains = Object.values(stateByChainId).sort( | ||
(a, b) => a.chainId - b.chainId, | ||
); | ||
|
||
return ( | ||
<Box flexDirection="column"> | ||
<Box marginBottom={1}> | ||
<Text bold>Superchain Contract Verification</Text> | ||
</Box> | ||
{chains.map(chain => ( | ||
<Box key={chain.chainId} flexDirection="column"> | ||
<Box gap={1}> | ||
<Box width={24}> | ||
<Text color="blue">{`${options.network}/${ | ||
chainById[chain.chainId]!.name | ||
}`}</Text> | ||
</Box> | ||
{chain.verificationStatus === 'pending' && ( | ||
<Box gap={1}> | ||
<Spinner /> | ||
<Text dimColor>Verification in progress...</Text> | ||
</Box> | ||
)} | ||
{chain.verificationStatus === 'success' && ( | ||
<Text color="green"> | ||
✓ Verification successful{' '} | ||
{`${ | ||
chainById[chain.chainId]?.blockExplorers?.default.url | ||
}/address/${options.contractAddress}`} | ||
</Text> | ||
)} | ||
{chain.verificationStatus === 'failure' && ( | ||
<Text color="red">✗ Verification failed</Text> | ||
)} | ||
</Box> | ||
{chain.error && ( | ||
<Box marginLeft={2}> | ||
<Text color="red" dimColor> | ||
Error: {chain.error.message || JSON.stringify(chain.error)} | ||
</Text> | ||
</Box> | ||
)} | ||
</Box> | ||
))} | ||
</Box> | ||
); | ||
}; | ||
|
||
export default VerifyCommand; | ||
export const options = zodVerifyContractCommandParams; |
Oops, something went wrong.