web3-testing — Claude Code skill: tests and install
This skill guides Solidity contract testing with Hardhat and Foundry, from unit checks to mainnet forks. Use it for test suites and DeFi protocol validation.
/plugin marketplace add wshobson/agents
/plugin install blockchain-web3@claude-code-workflowsWhat web3-testing does
web3-testing provides patterns for testing Solidity contracts with Hardhat and Foundry. The examples cover deployment, transfers, events, reverted transactions, and time-dependent behavior. It also includes integration tests, fuzzing for edge cases, gas checks, coverage reporting, and contract verification on Etherscan.
The Hardhat setup uses @nomicfoundation/hardhat-toolbox, @nomiclabs/hardhat-etherscan, hardhat-gas-reporter, and solidity-coverage. Foundry examples use forge-std/Test.sol plus cheatcodes such as vm.prank, vm.deal, and vm.createSelectFork. Mainnet fork tests need an RPC value in MAINNET_RPC_URL; the shown configuration also uses GOERLI_RPC_URL, PRIVATE_KEY, COINMARKETCAP_API_KEY, and ETHERSCAN_API_KEY.
Install it when your project contains Solidity contracts and you need a testing structure for Hardhat or Foundry. It is unnecessary for projects that do not test smart contracts.
What it helps with
- check contract deployment and initial state
- cover transfers, events, and reverted transactions
- exercise edge cases with fuzzing
- measure gas used by contract operations
- run DeFi checks against a mainnet fork
Example prompts for the agent
- “Write Hardhat unit tests for `Token` covering owner, total supply, transfers, and the Transfer event.”
- “Create a Foundry fuzz test for token transfers and cover insufficient balance.”
- “Set up a mainnet fork with `MAINNET_RPC_URL` and test interaction with DAI.”
How to install web3-testing
1Plugin marketplace
/plugin marketplace add wshobson/agents/plugin install blockchain-web3@claude-code-workflowsThis installs the whole blockchain-web3 plugin with all its skills
2Skills CLI (Vercel)
npx skills add wshobson/agents --skill web3-testing -a claude-code3Manually: put the files in place
~/.claude/skills/web3-testing/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/web3-testing/
1Skills CLI (Vercel)
npx skills add wshobson/agents --skill web3-testing -a codex2Manually: put the files in place
.agents/skills/web3-testing/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add wshobson/agents --skill web3-testing -a cursor2Manually: put the files in place
.cursor/skills/web3-testing/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install wshobson/agents web3-testing2Manually: put the files in place
.github/skills/web3-testing/SKILL.mdPlatform CLI
gemini skills install https://github.com/wshobson/agents --path plugins/blockchain-web3/skills/web3-testingManually: put the files in place
.devin/skills/web3-testing/SKILL.mdThe legacy .windsurf/skills/ path still works
Claude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set213
The whole set →Similar
Questions about web3-testing
How do I install web3-testing in Claude Code?
Run npx skills add wshobson/agents --skill web3-testing -a claude-code. The skill file is placed at ~/.claude/skills/web3-testing/SKILL.md.
What does web3-testing do?
It guides unit and integration testing for Solidity contracts with Hardhat and Foundry. It also covers fuzzing, gas checks, coverage, and mainnet fork tests.
What is required for mainnet fork testing?
You need an RPC value in MAINNET_RPC_URL. The examples use a selected mainnet block for the fork.
Source text
SKILL.md · MIT
---
name: web3-testing
description: Test smart contracts comprehensively using Hardhat and Foundry with unit tests, integration tests, and mainnet forking. Use when testing Solidity contracts, setting up blockchain test suites, or validating DeFi protocols.
---
# Web3 Smart Contract Testing
Master comprehensive testing strategies for smart contracts using Hardhat, Foundry, and advanced testing patterns.
## When to Use This Skill
- Writing unit tests for smart contracts
- Setting up integration test suites
- Performing gas optimization testing
- Fuzzing for edge cases
- Forking mainnet for realistic testing
- Automating test coverage reporting
- Verifying contracts on Etherscan
## Hardhat Testing Setup
```javascript
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
require("hardhat-gas-reporter");
require("solidity-coverage");
module.exports = {
solidity: {
version: "0.8.19",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
forking: {
url: process.env.MAINNET_RPC_URL,
blockNumber: 15000000,
},
},
goerli: {
url: process.env.GOERLI_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
},
},
gasReporter: {
enabled: true,
currency: "USD",
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
```
## Unit Testing Patterns
```javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
const {
loadFixture,
time,
} = require("@nomicfoundation/hardhat-network-helpers");
describe("Token Contract", function () {
// Fixture for test setup
async function deployTokenFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
return { token, owner, addr1, addr2 };
}
describe("Deployment", function () {
it("Should set the right owner", async function () {
const { token, owner } = await loadFixture(deployTokenFixture);
expect(await token.owner()).to.equal(owner.address);
});
it("Should assign total supply to owner", async function () {
const { token, owner } = await loadFixture(deployTokenFixture);
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
await expect(token.transfer(addr1.address, 50)).to.changeTokenBalances(
token,
[owner, addr1],
[-50, 50],
);
});
it("Should fail if sender doesn't have enough tokens", async function () {
const { token, addr1 } = await loadFixture(deployTokenFixture);
const initialBalance = await token.balanceOf(addr1.address);
await expect(
token.connect(addr1).transfer(owner.address, 1),
).to.be.revertedWith("Insufficient balance");
});
it("Should emit Transfer event", async function () {
const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
await expect(token.transfer(addr1.address, 50))
.to.emit(token, "Transfer")
.withArgs(owner.address, addr1.address, 50);
});
});
describe("Time-based tests", function () {
it("Should handle time-locked operations", async function () {
const { token } = await loadFixture(deployTokenFixture);
// Increase time by 1 day
await time.increase(86400);
// Test time-dependent functionality
});
});
describe("Gas optimization", function () {
it("Should use gas efficiently", async function () {
const { token } = await loadFixture(deployTokenFixture);
const tx = await token.transfer(addr1.address, 100);
const receipt = await tx.wait();
expect(receipt.gasUsed).to.be.lessThan(50000);
});
});
});
```
## Foundry Testing (Forge)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenTest is Test {
Token token;
address owner = address(1);
address user1 = address(2);
address user2 = address(3);
function setUp() public {
vm.prank(owner);
token = new Token();
}
function testInitialSupply() public {
assertEq(token.totalSupply(), 1000000 * 10**18);
}
function testTransfer() public {
vm.prank(owner);
token.transfer(user1, 100);
assertEq(token.balanceOf(user1), 100);
assertEq(token.balanceOf(owner), token.totalSupply() - 100);
}
function testFailTransferInsufficientBalance() public {
vm.prank(user1);
token.transfer(user2, 100); // Should fail
}
function testCannotTransferToZeroAddress() public {
vm.prank(owner);
vm.expectRevert("Invalid recipient");
token.transfer(address(0), 100);
}
// Fuzzing test
function testFuzzTransfer(uint256 amount) public {
vm.assume(amount > 0 && amount <= token.totalSupply());
vm.prank(owner);
token.transfer(user1, amount);
assertEq(token.balanceOf(user1), amount);
}
// Test with cheatcodes
function testDealAndPrank() public {
// Give ETH to address
vm.deal(user1, 10 ether);
// Impersonate address
vm.prank(user1);
// Test functionality
assertEq(user1.balance, 10 ether);
}
// Mainnet fork test
function testForkMainnet() public {
vm.createSelectFork("https://eth-mainnet.alchemyapi.io/v2/...");
// Interact with mainnet contracts
address dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
assertEq(IERC20(dai).symbol(), "DAI");
}
}
```
## Advanced Testing Patterns
### Snapshot and Revert
```javascript
describe("Complex State Changes", function () {
let snapshotId;
beforeEach(async function () {
snapshotId = await network.provider.send("evm_snapshot");
});
afterEach(async function () {
await network.provider.send("evm_revert", [snapshotId]);
});
it("Test 1", async function () {
// Make state changes
});
it("Test 2", async function () {
// State reverted, clean slate
});
});
```
### Mainnet Forking
```javascript
describe("Mainnet Fork Tests", function () {
let uniswapRouter, dai, usdc;
before(async function () {
await network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: process.env.MAINNET_RPC_URL,
blockNumber: 15000000,
},
},
],
});
// Connect to existing mainnet contracts
uniswapRouter = await ethers.getContractAt(
"IUniswapV2Router",
"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
);
dai = await ethers.getContractAt(
"IERC20",
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
);
});
it("Should swap on Uniswap", async function () {
// Test with real Uniswap contracts
});
});
```
### Impersonating Accounts
```javascript
it("Should impersonate whale account", async function () {
const whaleAddress = "0x...";
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [whaleAddress],
});
const whale = await ethers.getSigner(whaleAddress);
// Use whale's tokens
await dai
.connect(whale)
.transfer(addr1.address, ethers.utils.parseEther("1000"));
});
```
## Additional patterns and templates
More detailed templates and worked examples live in `references/details.md`. Read that file for the full pattern library.
© Seth Hobson · MIT · wshobson/agents