Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
);
);
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"onCommand:ethcode.contract.call",
"onCommand:ethcode.transaction.gas.set",
"onCommand:ethcode.transaction.gas.prices",
"onCommand:ethcode.rental.create"
"onCommand:ethcode.rental.create",
"onCommand:ethcode.foundry.load",
"onCommand:ethcode.hardhat.load"
],
"main": "./extension/build/extension.js",
"extensionDependencies": [
Expand Down Expand Up @@ -157,6 +159,14 @@
{
"command": "ethcode.createERC4907Contract",
"title": "Ethcode: Create ERC4907 Contract"
},
{
"command": "ethcode.foundry.load",
"title": "Ethcode: Load Foundry Contracts"
},
{
"command": "ethcode.hardhat.load",
"title": "Ethcode: Load Hardhat Contracts"
}
],
"keybindings": [
Expand Down
73 changes: 66 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ import {
import {
createERC4907Contract,
parseBatchCompiledJSON,
parseFoundryCompiledJSON,
parseHardhatCompiledJSON,
parseCompiledJSONPayload,
selectContract
selectContract,
parseFoundryConfig
} from './utils'
import { isFoundryProject, isHardhatProject } from './utils/functions'
import { provider, status, wallet, contract } from './api'
import { events } from './api/events'
import { event } from './api/api'
import { type API } from './types'
import * as toml from 'toml'

export async function activate (context: ExtensionContext): Promise<API | undefined> {
const disposables = [
Expand Down Expand Up @@ -154,6 +159,24 @@ export async function activate (context: ExtensionContext): Promise<API | undefi
// Activate
commands.registerCommand('ethcode.activate', async () => {
logger.success('Welcome to Ethcode!')
}),

// Load Foundry contracts
commands.registerCommand('ethcode.foundry.load', async () => {
try {
await parseFoundryCompiledJSON(context)
} catch (error) {
logger.error(`Error loading Foundry contracts: ${error}`)
}
}),

// Load Hardhat contracts
commands.registerCommand('ethcode.hardhat.load', async () => {
try {
await parseHardhatCompiledJSON(context)
} catch (error) {
logger.error(`Error loading Hardhat contracts: ${error}`)
}
})
]

Expand Down Expand Up @@ -235,25 +258,61 @@ export async function activate (context: ExtensionContext): Promise<API | undefi
await window.showErrorMessage('No folder selected please open one.')
return
}
const watcher = workspace.createFileSystemWatcher(
new RelativePattern(path_[0].uri.fsPath, '{artifacts, build, out, cache}/**/*.json')
)

watcher.onDidCreate(async (uri) => {
// Create dynamic file watcher based on project type
const createDynamicWatcher = async () => {
const watchPatterns: string[] = []

try {
// Check for Foundry project using centralized parser
const foundryConfig = await parseFoundryConfig()
if (foundryConfig) {
const { outDir } = foundryConfig
watchPatterns.push(`${outDir}/**/*.json`)
logger.log(`Foundry project detected, watching: ${outDir}/**/*.json`)
}

// Check for Hardhat project
if (isHardhatProject(path_[0].uri.fsPath)) {
watchPatterns.push('artifacts/**/*.json')
logger.log('Hardhat project detected, watching: artifacts/**/*.json')
}

// Fallback patterns for other frameworks
if (watchPatterns.length === 0) {
watchPatterns.push('{artifacts, build, out, cache, out-*/**/*.json}')
logger.log('No specific framework detected, using fallback patterns')
}

} catch (error) {
logger.error(`Error setting up file watcher: ${error}`)
// Fallback to original pattern
watchPatterns.push('{artifacts, build, out, cache, out-*/**/*.json}')
Comment on lines +283 to +290
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid glob pattern syntax. The pattern '{artifacts, build, out, cache, out-/**/.json}' mixes directory names with a nested path pattern. This should be '{artifacts,build,out,cache,out-}/**/.json' to correctly match JSON files in any of these directories.

Suggested change
watchPatterns.push('{artifacts, build, out, cache, out-*/**/*.json}')
logger.log('No specific framework detected, using fallback patterns')
}
} catch (error) {
logger.error(`Error setting up file watcher: ${error}`)
// Fallback to original pattern
watchPatterns.push('{artifacts, build, out, cache, out-*/**/*.json}')
watchPatterns.push('{artifacts,build,out,cache,out-*}/**/*.json')
logger.log('No specific framework detected, using fallback patterns')
}
} catch (error) {
logger.error(`Error setting up file watcher: ${error}`)
// Fallback to original pattern
watchPatterns.push('{artifacts,build,out,cache,out-*}/**/*.json}')

Copilot uses AI. Check for mistakes.
Comment on lines +283 to +290
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid glob pattern syntax. The pattern '{artifacts, build, out, cache, out-/**/.json}' mixes directory names with a nested path pattern. This should be '{artifacts,build,out,cache,out-}/**/.json' to correctly match JSON files in any of these directories.

Suggested change
watchPatterns.push('{artifacts, build, out, cache, out-*/**/*.json}')
logger.log('No specific framework detected, using fallback patterns')
}
} catch (error) {
logger.error(`Error setting up file watcher: ${error}`)
// Fallback to original pattern
watchPatterns.push('{artifacts, build, out, cache, out-*/**/*.json}')
watchPatterns.push('{artifacts,build,out,cache,out-*}/**/*.json')
logger.log('No specific framework detected, using fallback patterns')
}
} catch (error) {
logger.error(`Error setting up file watcher: ${error}`)
// Fallback to original pattern
watchPatterns.push('{artifacts,build,out,cache,out-*}/**/*.json')

Copilot uses AI. Check for mistakes.
}

// Create watcher with dynamic patterns
return workspace.createFileSystemWatcher(
new RelativePattern(path_[0].uri.fsPath, watchPatterns.join(','))
)
}

const watcher = await createDynamicWatcher()

watcher.onDidCreate(async (uri: any) => {
await parseBatchCompiledJSON(context)
const contracts = context.workspaceState.get('contracts') as string[]
if (contracts === undefined || contracts.length === 0) return []
event.contracts.fire(Object.keys(contracts))
})

watcher.onDidChange(async (uri) => {
watcher.onDidChange(async (uri: any) => {
Copy link

Copilot AI Jul 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Avoid using any for the uri parameter in onDidChange. Use the VS Code Uri type for stronger type safety.

Suggested change
watcher.onDidChange(async (uri: any) => {
watcher.onDidChange(async (uri: Uri) => {

Copilot uses AI. Check for mistakes.
await parseBatchCompiledJSON(context)
const contracts = context.workspaceState.get('contracts') as string[]
if (contracts === undefined || contracts.length === 0) return []
event.contracts.fire(Object.keys(contracts))
})

watcher.onDidDelete(async (uri) => {
watcher.onDidDelete(async (uri: any) => {
Copy link

Copilot AI Jul 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Avoid using any for the uri parameter in onDidDelete. Use the VS Code Uri type for stronger type safety.

Suggested change
watcher.onDidDelete(async (uri: any) => {
watcher.onDidDelete(async (uri: Uri) => {

Copilot uses AI. Check for mistakes.
const contracts = context.workspaceState.get('contracts') as string[]
if (contracts === undefined || contracts.length === 0) return []
event.contracts.fire(Object.keys(contracts))
Expand Down
94 changes: 78 additions & 16 deletions src/types/output.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type AbiItem } from './types'
import { logger } from '../lib'
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing logger in a types file violates separation of concerns. The logger should only be used in the getByteCode function implementation, not imported at the module level in a types file.

Suggested change
import { logger } from '../lib'

Copilot uses AI. Check for mistakes.

export interface HardHatCompiledOutput {
contractName: string
Expand All @@ -18,6 +19,40 @@ export interface RemixCompiledOutput {
abi: readonly AbiItem[]
}

export interface FoundryCompiledOutput {
abi: readonly AbiItem[]
bytecode: {
object: string
sourceMap: string
linkReferences: Record<string, Record<string, Array<{ start: number, length: number }>>>
}
deployedBytecode: {
object: string
sourceMap: string
linkReferences: Record<string, Record<string, Array<{ start: number, length: number }>>>
}
metadata: string
ir: string
irOptimized: string
storageLayout: any
evm: {
assembly: string
bytecode: {
object: string
sourceMap: string
linkReferences: Record<string, Record<string, Array<{ start: number, length: number }>>>
}
deployedBytecode: {
object: string
sourceMap: string
linkReferences: Record<string, Record<string, Array<{ start: number, length: number }>>>
}
methodIdentifiers: Record<string, string>
gasEstimates: any
}
ewasm: any
}

interface GasEstimate {
confidence: number
maxFeePerGas: number
Expand All @@ -34,17 +69,22 @@ export interface GasEstimateOutput {
export interface CompiledJSONOutput {
name?: string // contract name
path?: string // local path of the contract
contractType: number // 0: null, 1: hardhat output, 2: remix output
contractType: number // 0: null, 1: hardhat output, 2: remix output, 3: foundry output
hardhatOutput?: HardHatCompiledOutput
remixOutput?: RemixCompiledOutput
foundryOutput?: FoundryCompiledOutput
}

export const getAbi = (output: CompiledJSONOutput): any => {
if (output.contractType === 0) return []

if (output.contractType === 1) return output.hardhatOutput?.abi

return output.remixOutput?.abi
if (output.contractType === 2) return output.remixOutput?.abi

if (output.contractType === 3) return output.foundryOutput?.abi

return []
}

export const getByteCode = (
Expand All @@ -59,21 +99,43 @@ export const getByteCode = (
return bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`
}

// Remix format
const bytecode = output.remixOutput?.data.bytecode.object
if (!bytecode) {
console.log('Remix bytecode is undefined or null')
return undefined
if (output.contractType === 2) {
// Remix format
const bytecode = output.remixOutput?.data.bytecode.object
if (!bytecode) {
logger.log('Remix bytecode is undefined or null')
return undefined
}

logger.log(`Original Remix bytecode: ${bytecode.substring(0, 20)}...`)
logger.log(`Bytecode starts with 0x: ${bytecode.startsWith('0x')}`)

// Ensure 0x prefix for Remix format
const result = bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`
logger.log(`Final bytecode: ${result.substring(0, 20)}...`)

return result
}

console.log(`Original Remix bytecode: ${bytecode.substring(0, 20)}...`)
console.log(`Bytecode starts with 0x: ${bytecode.startsWith('0x')}`)

// Ensure 0x prefix for Remix format
const result = bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`
console.log(`Final bytecode: ${result.substring(0, 20)}...`)

return result

if (output.contractType === 3) {
// Foundry format
const bytecode = output.foundryOutput?.bytecode.object
if (!bytecode) {
logger.log('Foundry bytecode is undefined or null')
return undefined
}

logger.log(`Original Foundry bytecode: ${bytecode.substring(0, 20)}...`)
logger.log(`Bytecode starts with 0x: ${bytecode.startsWith('0x')}`)

// Ensure 0x prefix for Foundry format
const result = bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`
logger.log(`Final Foundry bytecode: ${result.substring(0, 20)}...`)

return result
}

return undefined
}

export interface BytecodeObject {
Expand Down
Loading