Your First Stylus Contract¶
Build, deploy, and test a Stylus counter contract using only the plugin's arb:* tasks.
Time: ~30 minutes (first Docker compile may take longer)
What You'll Build¶
A Counter contract with count(), increment(), and a Hardhat test that deploys it via stylusViem.
Step 1: Create the Project¶
The initializer creates:
hardhat.config.tswith the plugin configuredcontracts/stylus-counter/a ready-to-use Stylus countercontracts/SolidityCounter.sola Solidity counter for cross-VM teststest/cross-vm.test.tsexample tests
Docker file sharing on Linux
If arb:compile fails with "path is not shared from the host", create your project outside /tmp. Use your home directory or workspace Docker Desktop must have that path in Settings → Resources → File Sharing.
Step 2: Understand the Contract¶
The scaffolded counter lives at contracts/stylus-counter/src/lib.rs:
#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
#![cfg_attr(not(any(test, feature = "export-abi")), no_std)]
#[macro_use]
extern crate alloc;
use alloc::vec::Vec;
use stylus_sdk::{alloy_primitives::U256, prelude::*};
sol_storage! {
#[entrypoint]
pub struct Counter {
uint256 count;
}
}
#[public]
impl Counter {
pub fn count(&self) -> U256 {
self.count.get()
}
pub fn increment(&mut self) {
let count = self.count.get();
self.count.set(count + U256::from(1));
}
}
Each Stylus contract is a directory under contracts/ with:
| File | Purpose |
|---|---|
Cargo.toml |
Must depend on stylus-sdk |
rust-toolchain.toml |
Pins the Rust toolchain version |
Stylus.toml |
Stylus SDK configuration |
src/lib.rs |
Contract logic |
Step 3: Compile¶
This compiles Solidity and all Stylus contracts. Artifacts are written to artifacts/contracts/<name>/<name>.json.
To compile only Stylus contracts:
Step 4: Start a Local Node (optional)¶
For manual deploys against a persistent node:
Tests and deploys without --network start their own temporary node automatically you can skip this step if you only plan to test.
Step 5: Deploy¶
Expected output:
The plugin discovers the contract by folder name (stylus-counter), compiles if needed, and runs cargo stylus deploy inside Docker (or on the host with --host).
Step 6: Write a Test¶
Create test/counter.test.ts:
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { network } from 'hardhat';
describe('Counter', async function () {
const { stylusViem } = await network.create();
it('starts at 0 and increments', async function () {
const counter = await stylusViem.deployContract('stylus-counter');
assert.equal(await counter.read.count(), 0n);
await counter.write.increment();
assert.equal(await counter.read.count(), 1n);
});
});
Step 7: Run Tests¶
Or run a single file:
arb:test compiles Solidity, starts a temporary Nitro node, compiles Stylus contracts, deploys them in each test, and runs the Node.js test runner.
Step 8: Clean Up¶
What You Learned¶
- Project layout for Stylus contracts (
contracts/<name>/) arb:compilefor Rust/WASM compilationarb:deployfor on-chain deploymentarb:testwithstylusViem.deployContract()- Cross-VM interaction (Solidity + Stylus on the same chain)
Next Steps¶
- Deploy Plugin constructor args, networks,
stylusViemAPI - Test Plugin host mode, assertions, concurrency
- Real-World Examples production contract test suites
- Troubleshooting Docker, ports, toolchain issues