feat: add Fe language support for contract verification#29
Conversation
Support verifying Fe smart contracts following the Fe language support added to the Sourcify server. Changes include: - Language selector button with Fe icon - Fe compiler versions fetched from GitHub releases (2025+ only, prereleases shown by default) - .fe file extension support in file upload with Fe icon - Contract identifier parsing for Fe (filename-based like Vyper, format: src/file.fe:Name) - Compiler settings panel hidden for Fe (no EVM version or optimizer) - EVM version not required in form validation for Fe - API request sends language as "Fe" to Sourcify server Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
✅ Deploy Preview for verify-sourcify-ui ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Instead of deriving the contract name from the filename, scan the source content for `pub contract <Name>` declarations, consistent with how the Sourcify server compilers package identifies Fe contracts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
||
| const [showNightlyBuilds, setShowNightlyBuilds] = useState(false); | ||
| const [showPrereleases, setShowPrereleases] = useState(false); | ||
| const [showPrereleases, setShowPrereleases] = useState(language === "fe"); |
There was a problem hiding this comment.
The PR description says:
prereleases shown by default since all current versions are alpha
This is not the case for me when I opened the Netlify preview.
I suggest to disable toggling the Show alpha/prereleases for Fe and enable it by default. It doesn't make sense to toggle it if there are no releases shown afterwards.
| .then((response) => response.json()) | ||
| .then((data: { tag_name: string; published_at: string; assets: { name: string }[] }[]) => { | ||
| const allVersionsList: FeVersion[] = data | ||
| .filter((release) => release.assets.length > 0 && new Date(release.published_at).getFullYear() >= 2025) |
There was a problem hiding this comment.
Just a question: Do all releases on GitHub produce bytecode? I thought Fe had releases without bytecode production previously.
There was a problem hiding this comment.
I assumed releases after 2025 would produce bytecode but good question
There was a problem hiding this comment.
Let's just use the version with ABI support as the minimum supported one.
- Always show all Fe versions (including prereleases) and hide the toggle since all current Fe releases are alpha/prereleases - Filter Fe versions to only show releases after March 20, 2026 - Display server URL in the environment prefix for non-production envs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughThis pull request adds comprehensive Fe language support to the smart contract verification system. The changes extend language type definitions to include "fe", implement Fe compiler version fetching and context management, update UI components to display Fe options alongside Solidity and Vyper, add Fe-specific contract discovery via regex-based parsing, adjust form validation to exclude EVM version requirements for Fe, and integrate Fe into the verification methods configuration and standard JSON input generation. Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/components/verification/FileUpload.tsx (1)
334-337:⚠️ Potential issue | 🟡 MinorMissing
.feextension check in paste mode toggle cleanup.When toggling paste mode off, the code checks if the uploaded file ends with
.solor.vyto clear it, but doesn't include.fe. This could leave Fe pasted content in an inconsistent state.🐛 Proposed fix to include .fe extension
if ( uploadedFiles.length > 0 && (uploadedFiles[0].name.startsWith("pasted-content") || uploadedFiles[0].name.endsWith(".sol") || - uploadedFiles[0].name.endsWith(".vy")) + uploadedFiles[0].name.endsWith(".vy") || + uploadedFiles[0].name.endsWith(".fe")) ) { onFilesChange([]); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/verification/FileUpload.tsx` around lines 334 - 337, The cleanup that runs when toggling paste mode off currently checks uploadedFiles[0].name for "pasted-content", ".sol", and ".vy" but misses the Fe extension; update the conditional that references uploadedFiles[0].name (the paste-mode toggle cleanup branch) to also check for uploadedFiles[0].name.endsWith(".fe") so Fe pasted/uploaded files are cleared the same way as .sol and .vy files.
🧹 Nitpick comments (2)
app/components/verification/CompilerSelector.tsx (2)
2-4: Use the~/*alias for this app import.This new import keeps the file on relative
../../paths, which drifts from the repo convention and makes later moves noisier.As per coding guidelines, "Use path alias '~/*' to map to './app/*' in imports."♻️ Suggested import update
-import { useCompilerVersions } from "../../contexts/CompilerVersionsContext"; -import type { SolidityVersion, VyperVersion, FeVersion } from "../../contexts/CompilerVersionsContext"; -import type { Language, SelectedMethod } from "../../types/verification"; +import { useCompilerVersions } from "~/contexts/CompilerVersionsContext"; +import type { SolidityVersion, VyperVersion, FeVersion } from "~/contexts/CompilerVersionsContext"; +import type { Language, SelectedMethod } from "~/types/verification";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/verification/CompilerSelector.tsx` around lines 2 - 4, The imports in CompilerSelector.tsx use relative paths; update them to the app path alias by replacing "../../contexts/CompilerVersionsContext" with "~/contexts/CompilerVersionsContext" (for useCompilerVersions and the SolidityVersion/VyperVersion/FeVersion types) and "../../types/verification" with "~/types/verification" (for Language and SelectedMethod) so the file follows the "~/*" => "./app/*" import convention.
35-35: Keep Fe's default separate from Vyper's prerelease state.
showPrereleasesis seeded fromlanguage, but that state is only used by the Vyper branch later on. If this component mounts withlanguage === "fe"and then switches to Vyper without a remount, the Vyper checkbox starts checked unexpectedly because React does not rerun theuseStateinitializer when props change. Please verify the Fe → Vyper switch path in the preview.♻️ Minimal fix
- const [showPrereleases, setShowPrereleases] = useState(language === "fe"); + const [showPrereleases, setShowPrereleases] = useState(false);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/verification/CompilerSelector.tsx` at line 35, showPrereleases is incorrectly seeded from language (const [showPrereleases, setShowPrereleases] = useState(language === "fe")), causing the Vyper prerelease checkbox to inherit Fe's default when switching languages; change the initializer to a stable default (e.g. useState(false)) and add a useEffect that resets or sets showPrereleases when language becomes "vyper" (e.g. useEffect(() => { if (language === "vyper") setShowPrereleases(false); }, [language])) so the Vyper branch uses its own state independent of the Fe default, referencing showPrereleases, setShowPrereleases, useState, useEffect and the language prop to locate the edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/PageLayout.tsx`:
- Around line 40-43: The envPrefix construction currently injects the raw
serverUrl into the header (see envPrefix and serverUrl in PageLayout.tsx);
change it to render a sanitized form of serverUrl (e.g., parse serverUrl with
the URL constructor or a helper sanitizeServerUrl function and use only
origin/host, removing username/password, query, and path) before embedding in
envPrefix so the header shows a safe, minimal endpoint (host/origin) instead of
the full URL with credentials or tokens.
In `@app/components/verification/ContractIdentifier.tsx`:
- Around line 168-170: In parseFeFileContent, replace the current regex that
looks for "pub contract" with a multiline-anchored pattern: anchor to the start
of the line (respecting leading whitespace), make the "pub" keyword optional,
and capture a valid Fe identifier (start with letter/underscore, then word
chars); ensure the regex uses multiline mode so it won't match inside
comments/strings. This change will be applied inside the parseFeFileContent
function where matches = content.matchAll(...) is constructed.
In `@app/contexts/CompilerVersionsContext.tsx`:
- Line 48: The fetch of FE releases must be made type-safe, handle HTTP errors
and pagination, and use the API's prerelease flag: define a TypeScript interface
(e.g., GitHubRelease with id, tag_name, name, prerelease, published_at, assets,
etc.), update the FE_VERSIONS_LIST_URL usage in the release-fetching function
(the code around FE_VERSIONS_LIST_URL and the fetcher function that processes
releases) to check response.ok before calling response.json() and throw or
handle errors when not ok, iterate pages by appending &per_page=100&page=N and
loop until an empty array is returned (accumulating releases across pages), and
replace any regex-based prerelease detection with release.prerelease when
mapping to your internal version objects; ensure all fetch results are annotated
with the GitHubRelease type and surfaced as typed data.
---
Outside diff comments:
In `@app/components/verification/FileUpload.tsx`:
- Around line 334-337: The cleanup that runs when toggling paste mode off
currently checks uploadedFiles[0].name for "pasted-content", ".sol", and ".vy"
but misses the Fe extension; update the conditional that references
uploadedFiles[0].name (the paste-mode toggle cleanup branch) to also check for
uploadedFiles[0].name.endsWith(".fe") so Fe pasted/uploaded files are cleared
the same way as .sol and .vy files.
---
Nitpick comments:
In `@app/components/verification/CompilerSelector.tsx`:
- Around line 2-4: The imports in CompilerSelector.tsx use relative paths;
update them to the app path alias by replacing
"../../contexts/CompilerVersionsContext" with
"~/contexts/CompilerVersionsContext" (for useCompilerVersions and the
SolidityVersion/VyperVersion/FeVersion types) and "../../types/verification"
with "~/types/verification" (for Language and SelectedMethod) so the file
follows the "~/*" => "./app/*" import convention.
- Line 35: showPrereleases is incorrectly seeded from language (const
[showPrereleases, setShowPrereleases] = useState(language === "fe")), causing
the Vyper prerelease checkbox to inherit Fe's default when switching languages;
change the initializer to a stable default (e.g. useState(false)) and add a
useEffect that resets or sets showPrereleases when language becomes "vyper"
(e.g. useEffect(() => { if (language === "vyper") setShowPrereleases(false); },
[language])) so the Vyper branch uses its own state independent of the Fe
default, referencing showPrereleases, setShowPrereleases, useState, useEffect
and the language prop to locate the edits.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b7d82fde-f6d3-47ec-bf01-73422339f66e
⛔ Files ignored due to path filters (1)
public/fe.svgis excluded by!**/*.svg
📒 Files selected for processing (12)
app/components/PageLayout.tsxapp/components/verification/CompilerSelector.tsxapp/components/verification/CompilerSettings.tsxapp/components/verification/ContractIdentifier.tsxapp/components/verification/EvmVersionSelector.tsxapp/components/verification/FileUpload.tsxapp/components/verification/LanguageSelector.tsxapp/contexts/CompilerVersionsContext.tsxapp/data/verificationMethods.tsxapp/hooks/useFormValidation.tsapp/types/verification.tsapp/utils/sourcifyApi.ts
| const parseFeFileContent = (fileName: string, content: string): ParsedContract[] => { | ||
| const contracts: ParsedContract[] = []; | ||
| const matches = content.matchAll(/pub\s+contract\s+(\w+)/g); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n app/components/verification/ContractIdentifier.tsx | sed -n '165,180p'Repository: sourcifyeth/verify.sourcify.dev
Length of output: 617
🏁 Script executed:
# Check if Fe language has any documentation or specs about contract declarations
web_search
Fe programming language contract declaration syntaxRepository: sourcifyeth/verify.sourcify.dev
Length of output: 166
🌐 Web query:
Fe programming language contract declaration syntax Ethereum
💡 Result:
In the Fe programming language for Ethereum smart contracts, contracts are declared using the contract keyword followed by the contract name and a body containing fields, functions, structs, etc. Basic syntax from official Fe language specification: contract ContractName { field: Type, pub fn function_name(mut self, arg: Type) { // body } } Example from official docs (GuestBook contract): contract GuestBook { messages: Map<address, String<100>> pub fn sign(mut self, ctx: Context, book_msg: String<100>) { self.messages[ctx.msg_sender] = book_msg } pub fn get_msg(self, addr: address) -> String<100> { return self.messages[addr].to_mem } } Key elements: - contract IDENTIFIER { ContractMembers } - ContractMembers include optional pub visibility fields (IDENTIFIER: Type), functions (pub fn name(...)), structs, enums. - Constructor: pub fn init(mut self, ...) { ... } - Files use .fe extension, compiled with fe build. Note: The homepage shows a more advanced "msg/recv" syntax which appears to be a newer or example feature, but official spec and tutorials use the function-based syntax consistently. Fe syntax has evolved (e.g., older Python-like "def", now "fn"), but current docs confirm contract { ... } declaration.
Citations:
- 1: https://fe-lang.org/docs/spec/items/contracts.html
- 2: https://fe-lang.org/
- 3: https://fe-lang.org/docs/print.html
- 4: https://fe-lang.org/docs/quickstart/first_contract.html
- 5: https://blog.fe-lang.org/posts/development-update-5/
🏁 Script executed:
# Check how parseFeFileContent is called and used
rg "parseFeFileContent" app/components/verification/ContractIdentifier.tsx -B 2 -A 2Repository: sourcifyeth/verify.sourcify.dev
Length of output: 821
🏁 Script executed:
# Look for test fixtures or examples that might show Fe syntax
fd -t f -e fe -e txt | head -20Repository: sourcifyeth/verify.sourcify.dev
Length of output: 57
🏁 Script executed:
# Check if there are any other parsing functions for reference
rg "matchAll|parse.*FileContent" app/components/verification/ContractIdentifier.tsx -B 1 -A 3Repository: sourcifyeth/verify.sourcify.dev
Length of output: 1675
🏁 Script executed:
# Search for .fe files or Fe test examples
find . -type f -name "*.fe" 2>/dev/null | head -20Repository: sourcifyeth/verify.sourcify.dev
Length of output: 57
🏁 Script executed:
# Check test files or fixtures that might use Fe contracts
rg "contract\s+\w+" --type typescript -A 2 | grep -i "fe\|test" | head -20Repository: sourcifyeth/verify.sourcify.dev
Length of output: 109
🏁 Script executed:
# Search for test data or fixtures
find . -type f \( -name "*.test.*" -o -name "*.spec.*" -o -name "*test*" \) | grep -i fe | head -10Repository: sourcifyeth/verify.sourcify.dev
Length of output: 57
🏁 Script executed:
# Look for any Fe-related examples or documentation in the codebase
rg "fe" app/components/verification/ -l | head -10Repository: sourcifyeth/verify.sourcify.dev
Length of output: 573
🏁 Script executed:
# Check what the Solidity and Vyper parsing functions look like for comparison
rg "const parseFileContent|const parseVyper" app/components/verification/ContractIdentifier.tsx -A 5Repository: sourcifyeth/verify.sourcify.dev
Length of output: 279
🏁 Script executed:
# Check the surrounding code context for parseFileContent to understand the pattern better
cat -n app/components/verification/ContractIdentifier.tsx | sed -n '100,130p'Repository: sourcifyeth/verify.sourcify.dev
Length of output: 1814
🏁 Script executed:
# Look for how Solidity/Vyper handle their contract parsing to see if there's a pattern
cat -n app/components/verification/ContractIdentifier.tsx | sed -n '180,220p'Repository: sourcifyeth/verify.sourcify.dev
Length of output: 1513
Anchor the Fe contract regex and make the pub keyword optional.
The current regex /pub\s+contract\s+(\w+)/g has two issues:
- It matches
pub contractinside comments or strings, causing false positives - It requires the
pubkeyword, but Fe allows contracts declared without it (e.g.,contract GuestBook { ... }per the official Fe specification)
Anchoring with multiline mode prevents comment false positives, but the pub keyword should also be optional:
♻️ Improved regex
- const matches = content.matchAll(/pub\s+contract\s+(\w+)/g);
+ const matches = content.matchAll(/^\s*pub?\s*contract\s+([A-Za-z_]\w*)\b/gm);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const parseFeFileContent = (fileName: string, content: string): ParsedContract[] => { | |
| const contracts: ParsedContract[] = []; | |
| const matches = content.matchAll(/pub\s+contract\s+(\w+)/g); | |
| const parseFeFileContent = (fileName: string, content: string): ParsedContract[] => { | |
| const contracts: ParsedContract[] = []; | |
| const matches = content.matchAll(/^\s*pub?\s*contract\s+([A-Za-z_]\w*)\b/gm); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/components/verification/ContractIdentifier.tsx` around lines 168 - 170,
In parseFeFileContent, replace the current regex that looks for "pub contract"
with a multiline-anchored pattern: anchor to the start of the line (respecting
leading whitespace), make the "pub" keyword optional, and capture a valid Fe
identifier (start with letter/underscore, then word chars); ensure the regex
uses multiline mode so it won't match inside comments/strings. This change will
be applied inside the parseFeFileContent function where matches =
content.matchAll(...) is constructed.
|
|
||
| const SOLC_VERSIONS_LIST_URL = "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.txt"; | ||
| const VYPER_VERSIONS_LIST_URL = "https://vyper-releases-mirror.hardhat.org/list.json"; | ||
| const FE_VERSIONS_LIST_URL = "https://api.github.com/repos/argotorg/fe/releases"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "CompilerVersionsContext.tsx" -type fRepository: sourcifyeth/verify.sourcify.dev
Length of output: 117
🏁 Script executed:
cat -n app/contexts/CompilerVersionsContext.tsx | sed -n '40,60p'Repository: sourcifyeth/verify.sourcify.dev
Length of output: 983
🏁 Script executed:
cat -n app/contexts/CompilerVersionsContext.tsx | sed -n '135,175p'Repository: sourcifyeth/verify.sourcify.dev
Length of output: 1771
🌐 Web query:
GitHub API releases endpoint pagination response schema v3 REST API
💡 Result:
The GitHub REST API v3 releases endpoint (GET /repos/{owner}/{repo}/releases) returns a paginated array of Release objects. The response body is always an array of Release objects, regardless of pagination. Response body schema (Status: 200): Array of Release objects with fields including: - url: required, string, format: uri - html_url: required, string, format: uri - assets_url: required, string, format: uri - upload_url: required, string - tarball_url: required, string or null, format: uri - zipball_url: required, string or null, format: uri - id: required, integer - node_id: required, string - tag_name: required, string - target_commitish: required, string - name: string or null - draft: required, boolean - author: required, simple user object - prerelease: required, boolean - created_at: required, string, format: date-time - published_at: required, string or null, format: date-time - assets: array of release asset objects - body: string or null - reactions: reaction rollup object - body_html: string or null - body_text: string or null Pagination is provided via the Link header in the HTTP response, not in the response body. The Link header contains rel="next", rel="prev", rel="first", rel="last" with URLs for navigating pages. Use ?page and ?per_page (max 100, default 30) query parameters. Example Link header: link: https://api.github.com/repos/OWNER/REPO/releases?page=2; rel="next", https://api.github.com/repos/OWNER/REPO/releases?page=1; rel="first" Official documentation confirms the releases endpoint supports standard pagination parameters and returns Link headers for navigation.
Citations:
- 1: https://docs.github.com/en/rest/releases/releases
- 2: https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api
- 3: https://docs.github.com/rest/releases/releases
- 4: https://github.com/github/rest-api-description
- 5: https://docs.github.com/en/rest/about-the-rest-api/about-the-openapi-description-for-the-rest-api
Implement proper GitHub API error handling and use the native prerelease field instead of inferring from tag text.
The code fetches https://api.github.com/repos/argotorg/fe/releases without checking response.ok, meaning HTTP errors (4xx, 5xx) or malformed responses will silently fail. Additionally, the endpoint is paginated (30 per page by default) but the code only processes the first page; once the Fe repository has enough releases, supported older versions will disappear from the selector. Finally, the GitHub Releases API already provides a prerelease boolean field, so deriving it from /alpha|beta|rc/ in the tag name is unreliable and inconsistent with the API contract.
Define a proper TypeScript interface for the GitHub release response, check response.ok before calling .json(), handle pagination explicitly with query parameters, and use the native release.prerelease field directly.
As per coding guidelines, "Type-safe API interfaces and response handling must be used throughout the codebase."
Applies to: lines 48, 143–165
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/contexts/CompilerVersionsContext.tsx` at line 48, The fetch of FE
releases must be made type-safe, handle HTTP errors and pagination, and use the
API's prerelease flag: define a TypeScript interface (e.g., GitHubRelease with
id, tag_name, name, prerelease, published_at, assets, etc.), update the
FE_VERSIONS_LIST_URL usage in the release-fetching function (the code around
FE_VERSIONS_LIST_URL and the fetcher function that processes releases) to check
response.ok before calling response.json() and throw or handle errors when not
ok, iterate pages by appending &per_page=100&page=N and loop until an empty
array is returned (accumulating releases across pages), and replace any
regex-based prerelease detection with release.prerelease when mapping to your
internal version objects; ensure all fetch results are annotated with the
GitHubRelease type and surfaced as typed data.
Summary
.fefile extension support in file upload with Fe iconsrc/counter.fe:Counter), consistent with how Vyper handles it"Fe"as the language string in the Sourcify API requestFollows the Fe language support being added to the Sourcify server in argotorg/sourcify#2692.
🤖 Generated with Claude Code
Summary by CodeRabbit