Skip to content
Merged
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22, 24]
node-version: [24]
steps:
- uses: actions/checkout@v5
- name: Install pnpm
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
on:
push:
branches: main

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # required to use OIDC
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: "24"
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- uses: JS-DevTools/npm-publish@v4
with:
registry: "https://registry.npmjs.org/"
1 change: 1 addition & 0 deletions packages/example-project/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
# Hardhat files
/artifacts
/cache
/ignition
21 changes: 18 additions & 3 deletions packages/example-project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ pnpm install
pnpm build
```

Start the node:
## Testing

### 1. Start the node

```sh
pnpm hardhat node
Expand All @@ -23,14 +25,27 @@ This will:
- Automatically launch the OpenScan Explorer on <http://localhost:3030>
- Open your browser to the explorer interface

Then, you can run scripts in a separate terminal:
### 2. Deploy contracts with Ignition

In a separate terminal:

```sh
pnpm hardhat ignition deploy ignition/modules/Counter.ts --network localhost
```

### 3. Deploy contracts with script

```sh
pnpm run deploy
```

### 4. Send transactions with script

```sh
pnpm run send-tx
```

All transactions will be logged with clickable OpenScan links in the console
All transactions will be logged with clickable OpenScan links in the console. Check the code is verified

## What's inside the project?

Expand Down
5 changes: 3 additions & 2 deletions packages/example-project/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { defineConfig } from "hardhat/config";
import openScanPlugin from "openscan-hardhat-links";
import openScanPlugin from "@openscan/hardhat-plugin";
import hardhatIgnitionViemPlugin from "@nomicfoundation/hardhat-ignition-viem";

export default defineConfig({
plugins: [openScanPlugin],
plugins: [hardhatIgnitionViemPlugin, openScanPlugin],
solidity: "0.8.29",
networks: {
localhost: {
Expand Down
11 changes: 7 additions & 4 deletions packages/example-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
"send-tx": "hardhat run scripts/send-transactions.ts"
},
"devDependencies": {
"@nomicfoundation/hardhat-ignition-viem": "^3.0.7",
"@nomicfoundation/hardhat-ignition": "^3.0.5",
"@nomicfoundation/ignition-core": "^3.0.7",
"@openscan/hardhat-plugin": "workspace:*",
"@tsconfig/node22": "^22.0.2",
"@types/node": "^22.11.0",
"hardhat": "^3.0.11",
"openscan-hardhat-links": "workspace:*",
"typescript": "~5.8.0",
"forge-std": "github:foundry-rs/forge-std#v1.9.4"
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
"hardhat": "^3.1.5",
"typescript": "~5.8.0"
}
}
2 changes: 1 addition & 1 deletion packages/example-project/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"isolatedModules": true,
"typeRoots": ["${configDir}/node_modules/@types"]
},
"exclude": ["${configDir}/dist", "${configDir}/node_modules"],
"exclude": ["${configDir}/dist", "${configDir}/node_modules", "${configDir}/ignition"],
"references": [{ "path": "../plugin" }]
}
1 change: 1 addition & 0 deletions packages/plugin/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
CHANGELOG.md
/test/fixture-projects/**/artifacts
/test/fixture-projects/**/cache
/src/explorer
26 changes: 24 additions & 2 deletions packages/plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,35 @@ This plugin provides two main features:

## Usage

Simply start your Hardhat node:
### 1. Start the node

```bash
npx hardhat node
```

The OpenScan Explorer will automatically launch and your browser will open to the explorer interface. All subsequent transactions will include OpenScan links in the console output
The OpenScan Explorer will automatically launch and your browser will open to the explorer interface.

### 2. Deploy contracts with Ignition

In a separate terminal:

```bash
npx hardhat ignition deploy ignition/modules/Counter.ts --network localhost
```

### 3. Deploy contracts with script

```bash
npx hardhat run scripts/deploy.ts --network localhost
```

### 4. Send transactions with script

```bash
npx hardhat run scripts/send-tx.ts --network localhost
```

All transactions will be logged with clickable OpenScan links in the console. Check that the code is verified

## How It Works

Expand Down
11 changes: 6 additions & 5 deletions packages/plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openscan/hardhat-plugin",
"version": "0.0.7",
"version": "1.0.0",
"description": "Hardhat 3 plugin to use openscan explorer",
"license": "MIT",
"type": "module",
Expand All @@ -22,20 +22,21 @@
"eslint": "eslint \"src/**/*.ts\"",
"prettier": "prettier \"**/*.{ts,js,md,json}\"",
"build": "tsc --build .",
"postbuild": "node -e \"require('fs').cpSync('src/explorer/dist', 'dist/explorer', {recursive: true})\"",
"prepublishOnly": "pnpm build",
"prebuild": "pnpm clean",
"clean": "rimraf dist",
"watch": "tsc --build . --watch"
},
"files": [
"dist/src/",
"dist/explorer/",
"src/",
"CHANGELOG.md",
"LICENSE",
"README.md"
],
"dependencies": {
"@openscan/explorer": "1.1.1-alpha"
},
"devDependencies": {
"@eslint/js": "^9.35.0",
"@nomicfoundation/hardhat-node-test-reporter": "^3.0.0",
Expand All @@ -45,15 +46,15 @@
"eslint": "^9.35.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"hardhat": "^3.0.11",
"hardhat": "^3.1.5",
"prettier": "3.6.2",
"rimraf": "^5.0.5",
"tsx": "^4.19.3",
"typescript": "~5.8.0",
"typescript-eslint": "^8.43.0"
},
"peerDependencies": {
"hardhat": "^3.0.11"
"hardhat": "^3.1.5"
},
"publishConfig": {
"access": "public"
Expand Down
176 changes: 176 additions & 0 deletions packages/plugin/src/artifacts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { existsSync, readdirSync, readFileSync } from "node:fs";
import path from "node:path";

export interface ArtifactData {
abi: unknown[];
contractName: string;
sourceName?: string;
buildInfoId?: string;
sourceCode?: string;
buildInfo?: unknown;
deployments: string[];
}

interface DeployedAddresses {
[key: string]: string;
}

export type AddressMap = Record<string, ArtifactData>;

const CHAIN_ID = 31337;

let hasLoggedArtifacts = false;

export function findIgnitionDeployment(projectRoot: string): string | null {
const deploymentPath = path.join(
projectRoot,
"ignition",
"deployments",
`chain-${CHAIN_ID}`,
);
const deployedAddressesPath = path.join(
deploymentPath,
"deployed_addresses.json",
);

if (existsSync(deployedAddressesPath)) {
return deploymentPath;
}

return null;
}

export function loadArtifacts(
deploymentPath: string,
projectRoot: string,
): AddressMap {
const addressMap: AddressMap = {};

// Read deployed_addresses.json
const deployedAddressesPath = path.join(
deploymentPath,
"deployed_addresses.json",
);
if (!existsSync(deployedAddressesPath)) {
console.warn("[openscan] deployed_addresses.json not found");
return addressMap;
}

const deployedAddresses: DeployedAddresses = JSON.parse(
readFileSync(deployedAddressesPath, "utf-8"),
);

// Build contract name to address mapping
const contractDeployments: Record<string, string> = {};
for (const [moduleContract, address] of Object.entries(deployedAddresses)) {
const contractName = moduleContract.split("#")[1];
if (contractName) {
contractDeployments[contractName] = address;
}
}

// Read artifacts directory
const artifactsDir = path.join(deploymentPath, "artifacts");
if (!existsSync(artifactsDir)) {
console.warn("[openscan] artifacts directory not found");
return addressMap;
}

const artifactFiles = readdirSync(artifactsDir).filter((f) =>
f.endsWith(".json"),
);

// Build-info directory
const buildInfoDir = path.join(deploymentPath, "build-info");

// Contracts source directory
const contractsDir = path.join(projectRoot, "contracts");

for (const artifactFile of artifactFiles) {
const artifactPath = path.join(artifactsDir, artifactFile);

let artifact: Record<string, unknown>;
try {
artifact = JSON.parse(readFileSync(artifactPath, "utf-8")) as Record<
string,
unknown
>;
} catch {
continue;
}

const contractName = artifact.contractName as string | undefined;
if (!contractName) continue;

const deployedAddress = contractDeployments[contractName];
if (!deployedAddress) continue;

const artifactData: ArtifactData = {
abi: (artifact.abi as unknown[]) || [],
contractName,
sourceName: artifact.sourceName as string | undefined,
buildInfoId: artifact.buildInfoId as string | undefined,
deployments: [deployedAddress],
};

// Try to load build info
if (artifactData.buildInfoId && existsSync(buildInfoDir)) {
const buildInfoPath = path.join(
buildInfoDir,
`${artifactData.buildInfoId}.json`,
);
if (existsSync(buildInfoPath)) {
try {
artifactData.buildInfo = JSON.parse(
readFileSync(buildInfoPath, "utf-8"),
);
} catch {
// Ignore build info errors
}
}
}

// Try to load source code
if (artifactData.sourceName && existsSync(contractsDir)) {
const sourceFileName = artifactData.sourceName.split("/").pop();
if (sourceFileName) {
const sourcePath = path.join(contractsDir, sourceFileName);
if (existsSync(sourcePath)) {
try {
artifactData.sourceCode = readFileSync(sourcePath, "utf-8");
} catch {
// Ignore source code errors
}
}
}
}

// Store by lowercase address
addressMap[deployedAddress.toLowerCase()] = artifactData;
}

return addressMap;
}

export function loadIgnitionArtifacts(projectRoot: string): AddressMap | null {
const deploymentPath = findIgnitionDeployment(projectRoot);
if (!deploymentPath) {
return null;
}

const shouldLog = !hasLoggedArtifacts;
if (shouldLog) {
hasLoggedArtifacts = true;
console.log(`[openscan] Found Ignition deployment at: ${deploymentPath}`);
}

const result = loadArtifacts(deploymentPath, projectRoot);

if (shouldLog) {
console.log(
`[openscan] Loaded ${Object.keys(result).length} contract artifacts`,
);
}

return result;
}
Loading