A Hardhat plugin for seamless development of blended smart contract applications that combine Rust and Solidity contracts in the Fluent blockchain ecosystem.
- hardhat-plugin
This plugin simplifies the development of blended applications by:
- Enabling seamless integration between Rust and Solidity contracts
- Automatically compiling Rust contracts to WebAssembly
- Generating Hardhat-compatible artifacts
- Providing unified testing environment for both Rust and Solidity contracts
# npm
npm install @fluent.xyz/hardhat-plugin
# pnpm
pnpm add @fluent.xyz/hardhat-plugin
Import the plugin in your hardhat.config.js
:
require("@fluent.xyz/hardhat-plugin");
Or if you are using TypeScript, in your hardhat.config.ts
:
import "@fluent.xyz/hardhat-plugin";
- Node.js 16+
- pnpm 3+
- Rust and Cargo
- Docker (for local testing with Fluent node)
The plugin adds several tasks to Hardhat for working with Rust contracts:
Compiles Rust contracts to WebAssembly and generates Hardhat artifacts. The compile
task is extended to include Rust compilation in the standard Hardhat compilation flow.
npx hardhat compile # Compile full project (Solidity + Rust)
npx hardhat compile:rust # Compile only Rust contracts
Executes Rust contract unit tests and presents results in Mocha format. The test
task includes Rust tests in the standard Hardhat test suite.
npx hardhat test # Run all tests
npx hardhat test --network local # Run with local Fluent node
npx hardhat test --skip-wasm-tests # Skip Rust tests
npx hardhat test --skip-solidity-tests # Skip Solidity tests
Note: The Hardhat network doesn't support WASM execution - use Fluent local node for running tests.
Starts a local Fluent node for development and testing.
npx hardhat node:fluent # Start local node with default configuration
The node runs in a Docker container and is preconfigured for development use. Node data (chain data, logs, and configurations) is persisted in the .local-node
directory, which should be added to your .gitignore
:
# .gitignore
.local-node/
This ensures consistent state between restarts while keeping repository clean from local development data.
Removes compilation artifacts and build directories:
- Rust
target
directories - Generated WASM files
- Hardhat artifacts
npx hardhat clean # Clean build artifacts
All tasks can be configured through the plugin settings in your hardhat.config.ts
. See the Configuration section for details.
The plugin can be configured through the hardhat.config.ts
file. All configuration options are optional and come with sensible defaults.
import { HardhatUserConfig } from "hardhat/config";
import "@fluent.xyz/hardhat-plugin";
const config: HardhatUserConfig = {
solidity: "0.8.19",
fluent: {
// Plugin configuration goes here
}
};
export default config;
The plugin offers two ways to configure contracts: automatic discovery (default) and manual configuration.
By default, the plugin automatically discovers Rust contracts in your project - you don't need to configure anything. The following configuration is applied automatically:
fluent: {
discovery: {
enabled: true, // Enable/disable auto-discovery
paths: ['contracts', 'src'], // Directories to search
ignore: ['**/target/**', '**/node_modules/**'] // Patterns to ignore
}
}
This means writing:
const config: HardhatUserConfig = {
solidity: "0.8.19",
fluent: {}
};
is equivalent to explicitly configuring auto-discovery as shown above.
You can customize these settings by overriding any of the discovery options:
fluent: {
discovery: {
paths: ['contracts', 'custom-contracts'], // Add custom search paths
ignore: ['**/target/**', '**/tests/**'] // Custom ignore patterns
}
}
You can explicitly configure contracts instead of using auto-discovery. When contracts are manually configured, auto-discovery is automatically disabled.
fluent: {
contracts: [
{
path: "contracts/my-contract",
interface: {
path: "contracts/IMyContract.sol"
},
// Optional contract-specific settings
compile: {
debug: false, // Override global compile settings
},
test: {
timeout: 10000, // Override global test settings
}
}
]
}
Control how your Rust contracts are compiled to WebAssembly:
fluent: {
compile: {
target: "wasm32-unknown-unknown", // Target architecture
debug: false, // Enable debug mode
options: [ // Cargo build options
"--release",
"--target=wasm32-unknown-unknown",
"--no-default-features",
"-C link-arg=-zstack-size=131072",
"-C target-feature=+bulk-memory",
"-C opt-level=z",
"-C strip=symbols"
]
}
}
Configure how contract tests are executed:
fluent: {
test: {
command: "cargo test", // Test command
options: ["--release"], // Command options
timeout: 5000, // Test timeout in milliseconds
retries: 0 // Number of retry attempts
}
}
Configure the local Fluent node for development and testing:
fluent: {
node: {
docker: {
image: "ghcr.io/fluentlabs-xyz/fluent",
tag: "latest",
pull: "if-not-present" // Docker pull policy
},
network: {
chain: "dev", // Chain configuration
dataDir: "./datadir", // Data directory
blockTime: "5sec", // Block time
port: 30305, // P2P port
httpPort: 8545 // HTTP RPC port
}
}
}
Set environment variables for the compilation and test processes:
fluent: {
env: {
RUST_LOG: "info"
// Add any other environment variables
}
}
Settings are merged with the following precedence (highest to lowest):
- Contract-specific settings (
contracts[].compile
,contracts[].test
) - Global plugin settings (
compile
,test
, etc.) - Default settings
Example of inheritance:
fluent: {
// Global settings
compile: {
debug: true
},
test: {
timeout: 5000
},
// Contract-specific settings override global ones
contracts: [
{
path: "contracts/contract-a",
interface: {
path: "contracts/IContractA.sol"
},
compile: {
debug: false // Overrides global debug setting
},
test: {
timeout: 10000 // Overrides global timeout setting
}
}
]
}
The plugin comes with the following default settings:
{
compile: {
target: "wasm32-unknown-unknown",
debug: false,
options: [
"--release",
"--target=wasm32-unknown-unknown",
"--no-default-features",
"-C link-arg=-zstack-size=131072",
"-C target-feature=+bulk-memory",
"-C opt-level=z",
"-C strip=symbols",
],
},
test: {
command: "cargo test",
options: ["--release", "--test-threads=1"],
timeout: 5000,
retries: 0,
},
node: {
docker: {
image: "ghcr.io/fluentlabs-xyz/fluent",
tag: "latest",
pull: "if-not-present",
},
network: {
chain: "dev",
dataDir: "./datadir",
blockTime: "5sec",
port: 30305,
httpPort: 8545,
},
},
discovery: {
enabled: true,
paths: ["contracts", "src"],
ignore: ["**/target/**", "**/node_modules/**"],
},
env: {
RUST_LOG: "info",
},
}
Recommended project structure:
contracts/
├── IRandomGenerator.sol # Solidity interface for Rust contract
├── Lottery.sol # Solidity contract
└── random-generator/ # Rust contract
├── Cargo.toml
└── lib.rs
test/
├── Lottery.ts # Integration tests
└── RandomGenerator.ts
hardhat.config.ts # Hardhat configuration
Check out our example projects in the examples
directory:
A complete lottery application demonstrating:
- Integration between Rust and Solidity contracts
- Random number generation in Rust
- Contract deployment using Ignition
- Integration tests
- Local development setup
cd examples/lottery
pnpm install
pnpm hardhat compile
# In a separate terminal
pnpm hardhat node:fluent
pnpm hardhat test --network local
- The Hardhat network doesn't support WASM execution - use Fluent local node
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Submit a Pull Request
We follow the Contributor Covenant. We expect all contributors to be respectful and inclusive in all interactions. Key points:
- Use welcoming and inclusive language
- Respect different viewpoints and experiences
- Accept constructive criticism gracefully
- Focus on what's best for the community
-
Prepare the Codebase
Ensure the code is ready for release:- Run
pnpm run lint
to check for linting issues. - Run
pnpm run lint:fix
to automatically fix formatting issues. - Run all tests:
pnpm run test
.
- Run
-
Build the Project
Runpnpm run build
to compile the project. -
Update the Version
Choose the appropriate version bump:- For a patch release:
pnpm run version:patch
- For a minor release:
pnpm run version:minor
- For a major release:
pnpm run version:major
- For a patch release:
-
Push Changes
-
Push the updated code and tags to the main branch:
git push origin main --follow-tags
-
-
Automatic Deployment
GitHub Actions will handle publishing the new version upon detecting the pushed tag.
That’s it! 🎉
MIT License