Skip to content

Commit 7599470

Browse files
committed
Add Hardhat/Foundry build-info file support
1 parent 80de245 commit 7599470

File tree

9 files changed

+384
-68
lines changed

9 files changed

+384
-68
lines changed

app/components/verification/ContractIdentifier.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default function ContractIdentifier({
6868
try {
6969
const contracts: ParsedContract[] = [];
7070

71-
if (selectedMethod === "std-json") {
71+
if (selectedMethod === "std-json" || selectedMethod === "build-info") {
7272
// For std-json, use the first uploaded file
7373
const jsonFile = uploadedFiles[0];
7474
if (jsonFile) {

app/components/verification/FileUpload.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ const getFileRequirements = (method: VerificationMethod, language: Language | nu
5757
maxFiles: 1,
5858
description: `Solidity Metadata JSON file`,
5959
};
60+
case "build-info":
61+
return {
62+
allowedExtensions: [".json"],
63+
maxFiles: 1,
64+
description: "Build-info JSON file from Hardhat/Foundry artifacts directory",
65+
};
6066
default:
6167
return {
6268
allowedExtensions: [".sol"],

app/components/verification/VerificationMethodSelector.tsx

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export default function VerificationMethodSelector({
2828
// Get the framework message for the selected framework
2929
const selectedFrameworkMessage = selectedMethod && frameworkMessages[selectedMethod];
3030

31+
// Check if selected method is a framework method
32+
const isFrameworkMethod = frameworkMethods.some((method) => method.id === selectedMethod);
33+
34+
// Check if we're in build-info mode (selected method is build-info)
35+
const isBuildInfoMode = selectedMethod === "build-info";
36+
3137
return (
3238
<div>
3339
<label className="block text-base font-semibold text-gray-900 mb-2">Verification Method</label>
@@ -102,16 +108,63 @@ export default function VerificationMethodSelector({
102108
))}
103109
</div>
104110

111+
{/* Framework Build-Info Toggle */}
112+
{isFrameworkMethod && (
113+
<div className="mt-4 mb-4 flex items-center gap-2">
114+
<span className="text-sm text-gray-700">Show Commands</span>
115+
<label className="relative inline-flex items-center">
116+
<input
117+
type="checkbox"
118+
checked={false}
119+
onChange={(e) => {
120+
if (e.target.checked) {
121+
onMethodSelect("build-info");
122+
}
123+
}}
124+
className="sr-only"
125+
/>
126+
<div className="w-11 h-6 rounded-full relative transition-colors bg-gray-200">
127+
<div className="absolute top-[2px] left-[2px] bg-white border border-gray-300 rounded-full h-5 w-5 transition-transform"></div>
128+
</div>
129+
</label>
130+
<span className="text-sm text-gray-700">Upload build-info file</span>
131+
</div>
132+
)}
133+
134+
{/* Build-info back to framework toggle */}
135+
{isBuildInfoMode && (
136+
<div className="mt-4 mb-4 flex items-center gap-2">
137+
<span className="text-sm text-gray-700">Show Commands</span>
138+
<label className="relative inline-flex items-center">
139+
<input
140+
type="checkbox"
141+
checked={true}
142+
onChange={(e) => {
143+
if (!e.target.checked) {
144+
// Go back to hardhat as default - we could make this smarter by remembering the previous framework
145+
onMethodSelect("hardhat");
146+
}
147+
}}
148+
className="sr-only"
149+
/>
150+
<div className="w-11 h-6 rounded-full relative transition-colors bg-cerulean-blue-600">
151+
<div className="absolute top-[2px] left-[2px] bg-white border border-gray-300 rounded-full h-5 w-5 transition-transform translate-x-full"></div>
152+
</div>
153+
</label>
154+
<span className="text-sm text-gray-700">Upload build-info file</span>
155+
</div>
156+
)}
157+
105158
{/* Verification Warnings */}
106159
{selectedMethodWarning && (
107160
<div className="mt-4">
108161
<VerificationWarning type="warning">{selectedMethodWarning}</VerificationWarning>
109162
</div>
110163
)}
111164

112-
{selectedFrameworkMessage && (
165+
{selectedFrameworkMessage && !isBuildInfoMode && (
113166
<div className="mt-4">
114-
<VerificationWarning type="info">{selectedFrameworkMessage}</VerificationWarning>
167+
<VerificationWarning type="info">{selectedFrameworkMessage()}</VerificationWarning>
115168
</div>
116169
)}
117170
</div>

app/data/verificationMethods.tsx

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -70,68 +70,72 @@ export const frameworkMethods: FrameworkMethodObject[] = [
7070
},
7171
];
7272

73-
export const frameworkMessages: FrameworkMessages = {
74-
hardhat: (
73+
export const createFrameworkMessage = (framework: 'hardhat' | 'foundry') => () => {
74+
const isHardhat = framework === 'hardhat';
75+
76+
return (
7577
<div>
7678
<p className="mb-3 text-sm">
7779
<a
78-
href="https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#verifying-on-sourcify"
80+
href={isHardhat
81+
? "https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#verifying-on-sourcify"
82+
: "https://book.getfoundry.sh/reference/forge/forge-verify-contract"
83+
}
7984
target="_blank"
8085
rel="noopener noreferrer"
8186
className="text-cerulean-blue-600 hover:text-cerulean-blue-700 underline"
8287
>
83-
Hardhat Documentation →
88+
{isHardhat ? 'Hardhat' : 'Foundry'} Documentation →
8489
</a>
8590
</p>
86-
<p className="mb-3">Enable Sourcify in your hardhat.config.js:</p>
87-
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg mb-3 overflow-x-auto">
88-
<pre className="text-sm">
89-
{`module.exports = {
91+
92+
{isHardhat ? (
93+
<>
94+
<p className="mb-3">Enable Sourcify in your hardhat.config.js:</p>
95+
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg mb-3 overflow-x-auto">
96+
<pre className="text-sm">
97+
{`module.exports = {
9098
sourcify: {
9199
// Doesn't need an API key
92100
enabled: true
93101
}
94102
};`}
95-
</pre>
96-
</div>
97-
<p className="mb-2">Then verify a contract:</p>
98-
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
99-
<pre className="text-sm">
100-
{`npx hardhat verify --network mainnet 0x1F98431c8aD98523631AE4a59f267346ea31F984`}
101-
</pre>
102-
</div>
103-
</div>
104-
),
105-
foundry: (
106-
<div>
107-
<p className="mb-3 text-sm">
108-
<a
109-
href="https://book.getfoundry.sh/reference/forge/forge-verify-contract"
110-
target="_blank"
111-
rel="noopener noreferrer"
112-
className="text-cerulean-blue-600 hover:text-cerulean-blue-700 underline"
113-
>
114-
Foundry Documentation →
115-
</a>
116-
</p>
117-
<p className="mb-2">Deploy and verify with Foundry:</p>
118-
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg mb-3 overflow-x-auto">
119-
<pre className="text-sm">
120-
{`forge create --rpc-url <rpc-url> --private-key <private-key> src/MyContract.sol:MyContract --verify --verifier sourcify`}
121-
</pre>
122-
</div>
123-
<p className="mb-2">Or verify an already deployed contract:</p>
124-
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg mb-3 overflow-x-auto">
125-
<pre className="text-sm">
126-
{`forge verify-contract --verifier sourcify --chain <chain-id> 0xB4239c86440d6C39d518D6457038cB404451529b MyContract`}
127-
</pre>
128-
</div>
129-
<p className="mb-2">Check if a contract is verified:</p>
130-
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
131-
<pre className="text-sm">
132-
{`forge verify-check 0x1F98431c8aD98523631AE4a59f267346ea31F984 --verifier sourcify`}
133-
</pre>
134-
</div>
103+
</pre>
104+
</div>
105+
<p className="mb-2">Then verify a contract:</p>
106+
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
107+
<pre className="text-sm">
108+
{`npx hardhat verify --network mainnet 0x1F98431c8aD98523631AE4a59f267346ea31F984`}
109+
</pre>
110+
</div>
111+
</>
112+
) : (
113+
<>
114+
<p className="mb-2">Deploy and verify with Foundry:</p>
115+
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg mb-3 overflow-x-auto">
116+
<pre className="text-sm">
117+
{`forge create --rpc-url <rpc-url> --private-key <private-key> src/MyContract.sol:MyContract --verify --verifier sourcify`}
118+
</pre>
119+
</div>
120+
<p className="mb-2">Or verify an already deployed contract:</p>
121+
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg mb-3 overflow-x-auto">
122+
<pre className="text-sm">
123+
{`forge verify-contract --verifier sourcify --chain <chain-id> 0xB4239c86440d6C39d518D6457038cB404451529b MyContract`}
124+
</pre>
125+
</div>
126+
<p className="mb-2">Check if a contract is verified:</p>
127+
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
128+
<pre className="text-sm">
129+
{`forge verify-check 0x1F98431c8aD98523631AE4a59f267346ea31F984 --verifier sourcify`}
130+
</pre>
131+
</div>
132+
</>
133+
)}
135134
</div>
136-
),
135+
);
136+
};
137+
138+
export const frameworkMessages: FrameworkMessages = {
139+
hardhat: createFrameworkMessage('hardhat'),
140+
foundry: createFrameworkMessage('foundry'),
137141
};

app/hooks/useFormValidation.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ export function useFormValidation({
4242
// JSON validation state for std-json method
4343
const [isJsonValid, setIsJsonValid] = useState(true);
4444

45-
// Validate JSON files when uploaded for std-json method
45+
// Validate JSON files when uploaded for std-json, build-info, or metadata-json methods
4646
useEffect(() => {
4747
const validateJsonFile = async () => {
48-
if ((selectedMethod === "std-json" || selectedMethod === "metadata-json") && uploadedFiles.length > 0) {
48+
if ((selectedMethod === "std-json" || selectedMethod === "metadata-json" || selectedMethod === "build-info") && uploadedFiles.length > 0) {
4949
try {
5050
const file = uploadedFiles[0];
5151
const content = await file.text();
@@ -67,17 +67,18 @@ export function useFormValidation({
6767

6868
// Check if selected method is a framework method (not a verification method)
6969
const isFrameworkMethod = FRAMEWORK_METHODS.includes(selectedMethod);
70-
71-
// Check if compiler version is required (not for metadata, hardhat, or foundry methods)
70+
71+
// Check if compiler version is required (not for metadata-json or framework methods)
7272
const isCompilerVersionRequired =
7373
languageString && selectedMethod && !["metadata-json", "hardhat", "foundry"].includes(selectedMethod);
7474

75-
// Check if contract identifier is required (not for metadata-json, hardhat, or foundry methods)
75+
// Check if contract identifier is required (not for metadata-json or framework methods)
7676
const isContractIdentifierRequired =
7777
languageString && selectedMethod && !["metadata-json", "hardhat", "foundry"].includes(selectedMethod);
7878

7979
// Check if files are required based on the selected method
80-
const areFilesRequired = languageString && selectedMethod && !["hardhat", "foundry"].includes(selectedMethod);
80+
const areFilesRequired = languageString && selectedMethod &&
81+
["single-file", "multiple-files", "std-json", "metadata-json", "build-info"].includes(selectedMethod);
8182

8283
// Check if EVM version is required (for all languages, not for metadata-json, hardhat, or foundry methods)
8384
const isEvmVersionRequired =
@@ -89,8 +90,8 @@ export function useFormValidation({
8990
if (selectedMethod === "metadata-json") {
9091
// metadata-json requires both metadata file and source files
9192
return metadataFile !== null && uploadedFiles.length > 0;
92-
} else if (selectedMethod === "std-json") {
93-
// std-json requires uploaded files and valid JSON
93+
} else if (selectedMethod === "std-json" || selectedMethod === "build-info") {
94+
// std-json and build-info require uploaded files and valid JSON
9495
return uploadedFiles.length > 0 && isJsonValid;
9596
} else if (["single-file", "multiple-files"].includes(selectedMethod)) {
9697
// Other methods require uploaded files
@@ -148,6 +149,12 @@ export function useFormValidation({
148149
} else if (!isJsonValid) {
149150
newErrors.files = "Uploaded file contains invalid JSON format";
150151
}
152+
} else if (selectedMethod === "build-info") {
153+
if (uploadedFiles.length === 0) {
154+
newErrors.files = "Please upload build-info file";
155+
} else if (!isJsonValid) {
156+
newErrors.files = "Uploaded file contains invalid JSON format";
157+
}
151158
} else if (selectedMethod === "single-file") {
152159
newErrors.files = "Please upload contract file";
153160
} else if (selectedMethod === "multiple-files") {

0 commit comments

Comments
 (0)