Skip to content

Commit 9a848bb

Browse files
authored
Merge pull request #3 from sourcifyeth/staging
Release: Add Hardhat/Foundry build-info file support
2 parents a0c6030 + d80c5b1 commit 9a848bb

14 files changed

+535
-125
lines changed

CLAUDE.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Development Commands
6+
7+
- `npm run dev` - Start development server with hot reload (runs on http://localhost:5173)
8+
- `npm run build` - Build for production (outputs to `build/` directory)
9+
- `npm start` - Start production server
10+
- `npm run typecheck` - Run TypeScript type checking
11+
12+
## Architecture Overview
13+
14+
This is a React Router v7 SPA application for smart contract verification using the Sourcify service. The app operates in SPA mode (SSR disabled in react-router.config.ts).
15+
16+
### Key Directories
17+
18+
- `app/` - Main application code
19+
- `components/` - Reusable UI components, with verification-specific components in `verification/`
20+
- `contexts/` - React contexts for global state (ServerConfig, CompilerVersions, Chains)
21+
- `hooks/` - Custom React hooks for form validation and verification state
22+
- `routes/` - Route components (home page, job status)
23+
- `types/` - TypeScript type definitions
24+
- `utils/` - Utility functions for API calls, storage, and business logic
25+
26+
### Core Architecture
27+
28+
**Verification Flow**: The app supports multiple verification methods (single-file, multiple-files, std-json, metadata-json, build-info) for both Solidity and Vyper contracts. Framework helpers (hardhat, foundry) provide setup instructions and can optionally use build-info file uploads. All methods eventually convert to standard JSON format before submission to Sourcify's v2 API.
29+
30+
**API Integration**:
31+
- `sourcifyApi.ts` - Main Sourcify API client with custom headers for client identification
32+
- `etherscanApi.ts` - Etherscan integration for importing contract data
33+
- All API calls use custom `sourcifyFetch()` with client identification headers
34+
35+
**State Management**: Uses React Context for global state:
36+
- `ServerConfigContext` - Manages Sourcify server URLs (default, custom, localStorage sync)
37+
- `CompilerVersionsContext` - Fetches and caches compiler versions
38+
- `ChainsContext` - Manages blockchain network data
39+
40+
**Job Tracking**: Verification jobs are tracked via localStorage and polling the `/v2/verify/{verificationId}` endpoint.
41+
42+
### Environment Variables
43+
44+
- `VITE_SOURCIFY_SERVER_URL` - Default Sourcify server URL
45+
- `VITE_SOURCIFY_REPO_URL` - Sourcify repository URL
46+
- `VITE_GIT_COMMIT` - Git commit hash for client version tracking
47+
- `VITE_ENV` - Environment (development/production)
48+
49+
### Build Configuration
50+
51+
- React Router v7 with Vite
52+
- TailwindCSS v4 for styling
53+
- TypeScript with strict mode
54+
- Path alias: `~/*` maps to `./app/*`
55+
- Web Workers used for bytecode diff calculations (`public/diffWorker.js`)
56+
57+
### Key Features Implemented
58+
59+
**Build-Info File Support**: Framework helpers (Hardhat/Foundry) can toggle between showing setup commands or uploading build-info files. The build-info feature:
60+
- Parses Hardhat and Foundry build-info JSON files
61+
- Extracts standard JSON compilation input from the "input" field
62+
- Auto-populates compiler version from build-info metadata
63+
- Validates build-info structure and provides helpful error messages
64+
- Integrates seamlessly with existing std-json submission flow
65+
66+
**Type Safety**: Strong TypeScript typing throughout:
67+
- `SelectedMethod` union type combines verification methods and framework methods
68+
- Proper typing for all form validation and state management
69+
- Type-safe API interfaces and response handling

app/components/verification/CompilerSelector.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import React, { useState } from "react";
22
import { useCompilerVersions } from "../../contexts/CompilerVersionsContext";
33
import type { SolidityVersion, VyperVersion } from "../../contexts/CompilerVersionsContext";
4-
import type { Language, VerificationMethodObject, FrameworkMethodObject } from "../../types/verification";
4+
import type { Language, SelectedMethod } from "../../types/verification";
55

66
interface CompilerSelectorProps {
77
language: Language | null;
8-
verificationMethod: VerificationMethodObject | FrameworkMethodObject | null;
8+
selectedMethod: SelectedMethod | "";
99
selectedVersion?: string;
1010
onVersionSelect: (version: string) => void;
1111
}
1212

1313
export default function CompilerSelector({
1414
language,
15-
verificationMethod,
15+
selectedMethod,
1616
selectedVersion,
1717
onVersionSelect,
1818
}: CompilerSelectorProps) {
@@ -31,14 +31,14 @@ export default function CompilerSelector({
3131
const [showPrereleases, setShowPrereleases] = useState(false);
3232

3333
// Don't show if language is null or if using metadata/framework methods
34-
if (!language || !verificationMethod) {
34+
if (!language || !selectedMethod) {
3535
return null;
3636
}
3737

3838
if (
39-
verificationMethod.id === "metadata-json" ||
40-
verificationMethod.id === "hardhat" ||
41-
verificationMethod.id === "foundry"
39+
selectedMethod === "metadata-json" ||
40+
selectedMethod === "hardhat" ||
41+
selectedMethod === "foundry"
4242
) {
4343
return null;
4444
}

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: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,40 @@
11
import { Tooltip as ReactTooltip } from "react-tooltip";
2+
import { useState, useEffect } from "react";
3+
import { MdInfo } from "react-icons/md";
24
import { verificationMethods, frameworkMethods, frameworkMessages } from "../../data/verificationMethods";
3-
import type { Language } from "../../types/verification";
5+
import type { Language, SelectedMethod } from "../../types/verification";
46
import VerificationWarning from "./VerificationWarning";
57

68
interface VerificationMethodSelectorProps {
79
selectedLanguage: Language | null;
8-
selectedMethod: string;
9-
onMethodSelect: (method: string) => void;
10+
selectedMethod: SelectedMethod | "";
11+
onMethodSelect: (method: SelectedMethod) => void;
1012
}
1113

1214
export default function VerificationMethodSelector({
1315
selectedLanguage,
1416
selectedMethod,
1517
onMethodSelect,
1618
}: VerificationMethodSelectorProps) {
19+
// Keep track of the last selected framework method so we can return to it
20+
const [lastFrameworkMethod, setLastFrameworkMethod] = useState<"hardhat" | "foundry">("hardhat");
21+
1722
if (!selectedLanguage) return null;
1823

1924
const methods = verificationMethods[selectedLanguage as keyof typeof verificationMethods];
25+
26+
// Update lastFrameworkMethod when a framework method is selected
27+
useEffect(() => {
28+
if (frameworkMethods.some(method => method.id === selectedMethod)) {
29+
setLastFrameworkMethod(selectedMethod as "hardhat" | "foundry");
30+
}
31+
}, [selectedMethod]);
32+
33+
// Check if selected method is a framework method
34+
const isFrameworkMethod = frameworkMethods.some((method) => method.id === selectedMethod);
35+
36+
// Check if we're in build-info mode (selected method is build-info)
37+
const isBuildInfoMode = selectedMethod === "build-info";
2038

2139
// Get the warning for the selected method
2240
const selectedMethodWarning =
@@ -25,8 +43,12 @@ export default function VerificationMethodSelector({
2543
?.warning
2644
: null;
2745

28-
// Get the framework message for the selected framework
29-
const selectedFrameworkMessage = selectedMethod && frameworkMessages[selectedMethod];
46+
// Get the framework message for the selected framework (or the active framework in build-info mode)
47+
const selectedFrameworkMessage = (isFrameworkMethod && frameworkMessages[selectedMethod]) ||
48+
(isBuildInfoMode && frameworkMessages[lastFrameworkMethod]);
49+
50+
// Get the currently active framework method for visual selection purposes
51+
const activeFrameworkMethod = isBuildInfoMode ? lastFrameworkMethod : (isFrameworkMethod ? selectedMethod : null);
3052

3153
return (
3254
<div>
@@ -85,15 +107,15 @@ export default function VerificationMethodSelector({
85107
type="button"
86108
onClick={() => onMethodSelect(method.id)}
87109
className={`relative flex items-center justify-center gap-2 p-3 border-2 rounded-lg text-center transition-all duration-200 w-36 ${
88-
selectedMethod === method.id
110+
activeFrameworkMethod === method.id
89111
? "border-cerulean-blue-500 bg-cerulean-blue-50"
90112
: "border-gray-300 hover:border-cerulean-blue-300 hover:bg-gray-50"
91113
}`}
92114
>
93115
<img src={method.icon} alt={method.title} className="w-6 h-6" />
94116
<h3
95117
className={`text-base font-medium ${
96-
selectedMethod === method.id ? "text-cerulean-blue-600" : "text-gray-700"
118+
activeFrameworkMethod === method.id ? "text-cerulean-blue-600" : "text-gray-700"
97119
}`}
98120
>
99121
{method.title}
@@ -102,16 +124,57 @@ export default function VerificationMethodSelector({
102124
))}
103125
</div>
104126

127+
{/* Framework Build-Info Toggle */}
128+
{(isFrameworkMethod || isBuildInfoMode) && (
129+
<div className="mt-4 mb-4 flex items-center gap-2">
130+
<span className="text-sm text-gray-700">Show Commands</span>
131+
<label className="relative inline-flex items-center">
132+
<input
133+
type="checkbox"
134+
checked={isBuildInfoMode}
135+
onChange={(e) => {
136+
if (e.target.checked) {
137+
onMethodSelect("build-info");
138+
} else {
139+
onMethodSelect(lastFrameworkMethod);
140+
}
141+
}}
142+
className="sr-only"
143+
/>
144+
<div className={`w-11 h-6 rounded-full relative transition-colors ${
145+
isBuildInfoMode ? "bg-cerulean-blue-600" : "bg-gray-200"
146+
}`}>
147+
<div className={`absolute top-[2px] left-[2px] bg-white border border-gray-300 rounded-full h-5 w-5 transition-transform ${
148+
isBuildInfoMode ? "translate-x-full" : ""
149+
}`}></div>
150+
</div>
151+
</label>
152+
<span className="text-sm text-gray-700 flex items-center gap-1">
153+
Upload build-info file
154+
<button
155+
type="button"
156+
data-tooltip-id="build-info-tooltip"
157+
data-tooltip-content="Build-info files are saved compilation outputs from Hardhat and Foundry. While it's recommended to use the framework commands directly for verification, you can also upload these files. Find them in artifacts/build-info (Hardhat) or out/build-info (Foundry)."
158+
className="text-gray-500 hover:text-gray-700 transition-colors duration-200"
159+
>
160+
<MdInfo size={16} />
161+
</button>
162+
<ReactTooltip id="build-info-tooltip" place="top" style={{ maxWidth: '300px' }} />
163+
</span>
164+
</div>
165+
)}
166+
167+
105168
{/* Verification Warnings */}
106169
{selectedMethodWarning && (
107170
<div className="mt-4">
108171
<VerificationWarning type="warning">{selectedMethodWarning}</VerificationWarning>
109172
</div>
110173
)}
111174

112-
{selectedFrameworkMessage && (
175+
{selectedFrameworkMessage && !isBuildInfoMode && (
113176
<div className="mt-4">
114-
<VerificationWarning type="info">{selectedFrameworkMessage}</VerificationWarning>
177+
<VerificationWarning type="info">{selectedFrameworkMessage()}</VerificationWarning>
115178
</div>
116179
)}
117180
</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
};

0 commit comments

Comments
 (0)