Deploy Plugin¶
The deploy plugin (hardhat-arb-deploy) handles contract deployment. It supports both Solidity and Stylus contracts, manages ephemeral nodes, and handles Docker networking for container-based deploys.
Architecture¶
flowchart TD
Task["arb:deploy task"] --> Type{"Solidity or Stylus?"}
Type -->|".sol"| Sol["Compile Solidity"]
Type --> Discover["Discover Stylus Contract"]
Sol --> SolDeploy["Send deploy transaction"]
Discover --> Mode{"Host or Docker?"}
Mode -->|Host| Validate["Validate Toolchains"]
Mode -->|Docker| Prepare["Image + Volumes"]
Validate --> Network1{"--network?"}
Prepare --> Network2{"--network?"}
Network1 -->|No| EphNode1["Start Temp Node"]
Network1 -->|Yes| ExtRpc1["Use External RPC"]
Network2 -->|No| DockerNet["Create Docker Network"]
DockerNet --> EphNode2["Start Temp Node on Network"]
Network2 -->|Yes| ExtRpc2["Resolve Container RPC"]
EphNode1 --> HostDeploy["cargo stylus deploy (host)"]
ExtRpc1 --> HostDeploy
EphNode2 --> ContainerDeploy["cargo stylus deploy (container)"]
ExtRpc2 --> ContainerDeploy
SolDeploy --> Done["Print Address"]
HostDeploy --> Cleanup1["Stop Node"]
ContainerDeploy --> Cleanup2["Stop Node + Remove Network"]
Cleanup1 --> Done
Cleanup2 --> Done
Modules¶
src/
├── index.ts # Stable entrypoint (re-exports plugin)
├── plugin/
│ ├── index.ts # Plugin definition and task registration
│ ├── type-extensions.ts # Extends StylusConfig with deploy options
│ ├── hooks/
│ │ ├── config.ts # Config hook - resolves stylus.deploy
│ │ └── network.ts # Network hook - stylusViem integration
│ └── tasks/
│ └── deploy.ts # Main task orchestration
├── config/
│ ├── types.ts # StylusDeployConfig, StylusDeployUserConfig
│ ├── defaults.ts # Default values
│ └── resolver.ts # Merge user config with defaults
├── services/
│ ├── deployers/
│ │ ├── types.ts # WasmDeployResult, ProgressCallback
│ │ ├── solidity.ts # Solidity artifact lookup + deploy
│ │ ├── wasm-host.ts # Host cargo stylus deploy
│ │ ├── wasm-container.ts # Docker cargo stylus deploy
│ │ └── viem-stylus.ts # viem bridge for Stylus deploy
│ └── assertions/
│ └── stylus-assertions.ts
└── state/
└── deploy-mode.ts # Test-only host/container override
Task Entry Point¶
The arb:deploy task in plugin/tasks/deploy.ts orchestrates the full flow. It branches on three dimensions:
- Contract type - Solidity (
.sol) vs Stylus (folder name) - Deployment mode - Host vs Docker (for Stylus only)
- Network - Ephemeral node vs external
--network
// Simplified decision tree
const isSol = contract.endsWith('.sol');
const isExternal = externalRpcUrl !== null;
const useHost = host || config.stylus.deploy.useHostToolchain;
if (isSol) deploySolidity(...)
else if (useHost) deployStylusHost(...)
else deployStylusContainer(...)
The task handles all lifecycle management: starting/stopping ephemeral nodes, creating/removing Docker networks, and cleaning up on error.
Solidity Deployment¶
services/deployers/solidity.ts handles Solidity contracts:
-
Find artifact -
findSolidityArtifact()recursively scansartifacts/for a JSON file matching the contract name. Skips.dbg.files and empty bytecodes. -
Encode constructor args -
encodeConstructorArgs()reads the ABI constructor, coerces string arguments to typed values, and ABI-encodes them. -
Deploy -
deploySolidityContract()creates a viem wallet client, sends a deploy transaction (bytecode + encoded args), and waits for the receipt.
// Constructor arg coercion (Foundry-style)
// uint/int → BigInt, bool → true/false, address/bytes/string → pass-through
function parseConstructorArg(value: string, abiType: string): unknown { ... }
The encoding uses encodeAbiParameters from hardhat-arb-utils, which wraps viem's ABI encoding.
Stylus Host Deployment¶
services/deployers/wasm-host.ts handles host-mode Stylus deployment:
- Build command - Assembles
cargo +{toolchain} stylus deploywith endpoint, private key, and--no-verify - Execute - Runs via
execWithProgressfromhardhat-arb-utils/stylus, streaming progress lines - Parse address -
parseDeployedAddress()extracts the contract address from cargo-stylus output using multiple regex patterns
// Address parsing - tries several patterns from cargo-stylus output
const patterns = [
/deployed code at address:\s*(0x[0-9a-fA-F]{40})/i,
/contract deployed at\s*(0x[0-9a-fA-F]{40})/i,
/deployed at[:\s]+(0x[0-9a-fA-F]{40})/i,
/(0x[0-9a-fA-F]{40})/, // fallback
];
parseDeployedAddress is shared between host and container modes.
Stylus Container Deployment¶
services/deployers/wasm-container.ts handles Docker-mode Stylus deployment:
- Prepare toolchain - Runs
rustup toolchain installandrustup target add wasm32-unknown-unknownin a container (cached in volumes) - Deploy - Runs
cargo stylus deployin a temporary container
The deploy container uses the same image and volumes as the compile plugin:
// Container setup
const args = ['run', '--rm', '--name', containerName];
args.push('-v', `${contractPath}:/workspace:rw`);
args.push('-v', `${RUSTUP_VOLUME_NAME}:/usr/local/rustup:rw`);
args.push('-v', `${CARGO_VOLUME_NAME}:/usr/local/cargo:rw`);
args.push('-w', '/workspace');
args.push(imageName, ...command);
Docker Networking¶
The container needs to reach the RPC endpoint. Three scenarios:
| Scenario | How it works |
|---|---|
| Ephemeral node | Container joins the Docker network; uses node container name as host |
| External localhost URL | --add-host=host.docker.internal:host-gateway; URL rewritten |
| External remote URL | Passed directly to container |
// Localhost detection and rewriting
if (isLocalhostUrl(externalRpcUrl)) {
containerOpts = {
rpcEndpoint: toDockerHostUrl(externalRpcUrl), // → host.docker.internal
useHostGateway: true,
};
} else {
containerOpts = { rpcEndpoint: externalRpcUrl };
}
Network and Key Resolution¶
resolveExternalRpcUrl()¶
Shared helper in hardhat-arb-utils/task-helpers (used by both compile and deploy):
- If
--networkisundefinedor"default"→ returnsnull(ephemeral node mode) - Otherwise → looks up the network config, validates it's an HTTP network, returns the URL
- Accepts a
contextstring for error messages (e.g."compilation","deployment")
resolveDeployPrivateKey()¶
- Ephemeral mode - Uses
HARDHAT_ACCOUNTS[0].privateKey(pre-funded on the temp node) - External mode - Reads
accounts[0]from the network config "remote"accounts → error with config example- Empty array → error
- HD wallet → error
- Explicit key array → returns the first key
Integration Points¶
The deploy plugin depends on two sibling packages:
hardhat-arb-node- Providesarb:node startfor temporary nodes, plusgenerateTempContainerName,registerTempContainer,cleanupTempContainer,generateRandomPort, andHARDHAT_ACCOUNTShardhat-arb-utils- ProvidesDockerClientfor Docker operations,createPluginErrorfor error handling,isLocalhostUrl/toDockerHostUrlfor URL rewriting, viem wrappers for transactions, and Stylus utilities (discoverStylusContracts,ensureVolumes,ensureCompileImage,validateAllToolchains,execWithProgress)