Skip to content

Commit cd9d306

Browse files
committed
Add retry button for failed verifications
1 parent 7512af5 commit cd9d306

File tree

1 file changed

+126
-7
lines changed

1 file changed

+126
-7
lines changed

apps/contract-verification/src/app/components/AccordionReceipt.tsx

Lines changed: 126 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import React, { useMemo } from 'react'
2-
import { SubmittedContract, VerificationReceipt } from '../types'
2+
import { SubmittedContract, VerificationReceipt, VerificationStatus } from '../types'
33
import { shortenAddress, CustomTooltip } from '@remix-ui/helper'
44
import { AppContext } from '../AppContext'
55
import { CopyToClipboard } from '@remix-ui/clipboard'
6+
import { getVerifier } from '../Verifiers'
7+
import { CompilerAbstract } from '@remix-project/remix-solidity'
8+
import { mergeChainSettingsWithDefaults } from '../utils'
69

710
interface AccordionReceiptProps {
811
contract: SubmittedContract
912
index: number
1013
}
1114

1215
export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, index }) => {
13-
const { chains } = React.useContext(AppContext)
16+
const { chains, settings, compilationOutput, setSubmittedContracts } = React.useContext(AppContext)
1417

1518
const [expanded, setExpanded] = React.useState(false)
1619

@@ -25,6 +28,107 @@ export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, in
2528
setExpanded(!expanded)
2629
}
2730

31+
const isRetryAvailable = useMemo(() => {
32+
if (!compilationOutput) return false
33+
34+
const compilerAbstract = Object.values(compilationOutput || {}).find(
35+
(abstract: CompilerAbstract) =>
36+
abstract.data.contracts[contract.filePath] &&
37+
abstract.data.contracts[contract.filePath][contract.contractName]
38+
)
39+
return !!compilerAbstract
40+
}, [compilationOutput, contract.filePath, contract.contractName])
41+
42+
const handleRetryVerification = async (receipt: VerificationReceipt) => {
43+
setSubmittedContracts(prev => {
44+
const currentContract = prev[contract.id]
45+
if (!currentContract) return prev
46+
47+
return {
48+
...prev,
49+
[contract.id]: {
50+
...currentContract,
51+
receipts: (currentContract.receipts || []).map(r =>
52+
r === receipt ? { ...r, status: 'pending' as VerificationStatus, message: 'Retrying...' } : r
53+
),
54+
proxyReceipts: (currentContract.proxyReceipts || []).map(r =>
55+
r === receipt ? { ...r, status: 'pending' as VerificationStatus, message: 'Retrying...' } : r
56+
)
57+
}
58+
}
59+
})
60+
61+
try {
62+
const chainSettings = mergeChainSettingsWithDefaults(contract.chainId, settings)
63+
const verifierSettings = chainSettings.verifiers[receipt.verifierInfo.name]
64+
65+
if (!verifierSettings || !verifierSettings.apiUrl) {
66+
throw new Error('Verifier settings or apiUrl not found.')
67+
}
68+
69+
const verifier = getVerifier(receipt.verifierInfo.name, verifierSettings)
70+
71+
let response
72+
73+
if (receipt.isProxyReceipt) {
74+
if (!verifier.verifyProxy) {
75+
throw new Error(`Proxy verification not supported by ${receipt.verifierInfo.name}`)
76+
}
77+
response = await verifier.verifyProxy(contract)
78+
} else {
79+
const compilerAbstract = Object.values(compilationOutput || {}).find(
80+
(abstract: CompilerAbstract) =>
81+
abstract.data.contracts[contract.filePath] &&
82+
abstract.data.contracts[contract.filePath][contract.contractName]
83+
)
84+
85+
if (!compilerAbstract) {
86+
const userMessage = `Compilation output not found. Please re-compile the contract file ('${contract.filePath}') and try again.`
87+
console.error(`[Retry] ${userMessage}`)
88+
throw new Error(userMessage)
89+
}
90+
91+
response = await verifier.verify(contract, compilerAbstract)
92+
}
93+
94+
setSubmittedContracts(prev => {
95+
const currentContract = prev[contract.id]
96+
if (!currentContract) return prev
97+
return {
98+
...prev,
99+
[contract.id]: {
100+
...currentContract,
101+
receipts: (currentContract.receipts || []).map(r =>
102+
r === receipt ? { ...r, ...response, receiptId: response.receiptId || undefined, status: response.status, message: response.message } : r
103+
),
104+
proxyReceipts: (currentContract.proxyReceipts || []).map(r =>
105+
r === receipt ? { ...r, ...response, receiptId: response.receiptId || undefined, status: response.status, message: response.message } : r
106+
)
107+
}
108+
}
109+
})
110+
111+
} catch (e) {
112+
console.error(e)
113+
setSubmittedContracts(prev => {
114+
const currentContract = prev[contract.id]
115+
if (!currentContract) return prev
116+
return {
117+
...prev,
118+
[contract.id]: {
119+
...currentContract,
120+
receipts: (currentContract.receipts || []).map(r =>
121+
r === receipt ? { ...r, status: 'failed' as VerificationStatus, message: e.message } : r
122+
),
123+
proxyReceipts: (currentContract.proxyReceipts || []).map(r =>
124+
r === receipt ? { ...r, status: 'failed' as VerificationStatus, message: e.message } : r
125+
)
126+
}
127+
}
128+
})
129+
}
130+
}
131+
28132
return (
29133
<div className={`${expanded ? 'bg-light' : 'border-bottom '}`}>
30134
<div className="d-flex flex-row align-items-center">
@@ -65,7 +169,7 @@ export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, in
65169

66170
<div>
67171
<span className="fw-bold">Verified at: </span>
68-
<ReceiptsBody receipts={contract.receipts} />
172+
<ReceiptsBody receipts={contract.receipts} handleRetry={handleRetryVerification} isRetryAvailable={isRetryAvailable} />
69173
</div>
70174

71175
{hasProxy && (
@@ -79,7 +183,7 @@ export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, in
79183
</div>
80184
<div>
81185
<span className="fw-bold">Proxy verified at: </span>
82-
<ReceiptsBody receipts={contract.proxyReceipts} />
186+
<ReceiptsBody receipts={contract.proxyReceipts} handleRetry={handleRetryVerification} isRetryAvailable={isRetryAvailable} />
83187
</div>
84188
</>
85189
)}
@@ -88,13 +192,17 @@ export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, in
88192
)
89193
}
90194

91-
const ReceiptsBody = ({ receipts }: { receipts: VerificationReceipt[] }) => {
195+
const ReceiptsBody = ({ receipts, handleRetry, isRetryAvailable }: {
196+
receipts: VerificationReceipt[],
197+
handleRetry: (receipt: VerificationReceipt) => void,
198+
isRetryAvailable: boolean
199+
}) => {
92200
return (
93201
<ul className="list-group">
94202
{receipts.map((receipt) => (
95203
<li
96204
key={`${receipt.contractId}-${receipt.verifierInfo.name}${receipt.isProxyReceipt ? '-proxy' : ''}-${receipt.receiptId}`}
97-
className="list-group-item d-flex flex-row align-items-center"
205+
className="list-group-item d-flex flex-row align-items-baseline"
98206
>
99207
<CustomTooltip
100208
placement="top"
@@ -109,12 +217,23 @@ const ReceiptsBody = ({ receipts }: { receipts: VerificationReceipt[] }) => {
109217
<i className="fas fa-check-double text-success px-1"></i> :
110218
receipt.status === 'failed' ?
111219
<i className="fas fa-xmark text-warning px-1"></i> :
112-
['pending', 'awaiting implementation verification'].includes(receipt.status) ?
220+
['pending', 'awaiting implementation verification', 'Retrying...'].includes(receipt.status) ?
113221
<i className="fas fa-spinner fa-spin px-1"></i> :
114222
<i className="fas fa-question px-1"></i>
115223
}
116224
</span>
117225
</CustomTooltip>
226+
{receipt.status === 'failed' && isRetryAvailable && (
227+
<CustomTooltip placement="top" tooltipText="Retry Verification">
228+
<button
229+
className="btn btn-sm p-0 me-2"
230+
style={{ border: 'none', background: 'none', color: 'var(--primary)' }}
231+
onClick={() => handleRetry(receipt)}
232+
>
233+
<i className="fas fa-redo" style={{ fontSize: '0.6rem' }}></i>
234+
</button>
235+
</CustomTooltip>
236+
)}
118237
<div className="d-flex flex-row w-100 justify-content-between">
119238
<div>
120239
<CustomTooltip placement="top" tooltipClasses=" text-break" tooltipText={`API: ${receipt.verifierInfo.apiUrl}`}>

0 commit comments

Comments
 (0)