Deploy smart contracts from your dev machine. Sign transactions from anywhere — no private keys in .env files ever. Run Hardhat tasks, scripts, and interact with contracts directly from the browser.
Every Hardhat developer knows the drill: export your MetaMask private key, paste it into .env, and pray you never accidentally commit it. hardhat-airsign eliminates this entirely.
Dev Machine (Terminal) Signer (Browser)
┌─────────────────────┐ ┌──────────────────────┐
│ npx hardhat run │ HTTP / WS │ localhost:9090 │
│ scripts/deploy.js │◄──────────────► │ ( ngrok URL ) │
│ --network sepolia │ │ │
│ │ 1. unsigned tx │ ┌────────────────┐ │
│ getSigners() ──────────────────────► │ │ Connect Wallet │ │
│ │ │ └────────────────┘ │
│ │ 2. signed tx │ │
│ broadcasts tx ◄────────────────────── │ MetaMask signs │
└─────────────────────┘ └──────────────────────┘
- Run
npx hardhat airsign-start— a signing server starts in the background on port9090 - Open the URL in a browser and connect your wallet (MetaMask, Rainbow, Coinbase, etc.)
- Run your deploy script — transactions appear in the browser for approval
- Click Confirm, MetaMask signs, and the tx is broadcast. Done.
Your existing deploy scripts work without any changes.
- Remote signing — sign transactions from any machine, no private keys on the dev box
- Runner UI — run Hardhat tasks and scripts directly from the browser with a 3-column interface (list, detail, console)
- Contracts UI — read and write deployed contract functions, deploy new contracts, and view decoded event logs — all from the browser
- Wallet-proxied RPC — no Alchemy/Infura URL needed. When no RPC URL is configured, JSON-RPC calls are automatically proxied through the connected browser wallet
- Signing modal — transaction approvals appear as an overlay without interrupting your workflow
- Multi-wallet support — MetaMask, Coinbase Wallet, Ledger, Trust Wallet, Rabby, Safe (Gnosis), Rainbow, Phantom, Brave, Zerion, OKX, Uniswap, Bitget, Frame, and any injected or WalletConnect-compatible wallet via RainbowKit
- Multi-chain — Ethereum, Sepolia, Polygon, Arbitrum, Optimism, Base, BSC, Avalanche, and more
- Zero config — existing deploy scripts work as-is, just set
remoteSigner: true - Block explorer links — click through to Etherscan/Polygonscan after signing
- Transaction history — see all signed/rejected transactions in the current session
- Activity log — tracks all contract interactions (reads, writes, deploys) with tx hashes and explorer links
| Dependency | Supported Version |
|---|---|
| Hardhat | v2.x (^2.0.0) |
| ethers.js | v5.x (^5.0.0) |
| Node.js | >= 18 |
Note: This plugin uses
@nomiclabs/hardhat-ethers(ethers v5). Projects using@nomicfoundation/hardhat-etherswith ethers v6 or Hardhat v3 are not yet supported. Hardhat 3 + ethers v6 support is on the roadmap.
npm install hardhat-airsign// hardhat.config.js
require("@nomiclabs/hardhat-ethers");
require("hardhat-airsign");
module.exports = {
solidity: "0.8.24",
networks: {
sepolia: {
url: "https://rpc.sepolia.org", // optional if you want to use your own RPC endpoint
remoteSigner: true, // <-- that's it. no accounts, no private keys needed.
},
},
};Tip: You don't even need an RPC URL. When
urlis omitted, AirSign proxies all JSON-RPC calls through the connected browser wallet (MetaMask, etc.). If you prefer a dedicated RPC endpoint, just addurl: "https://..."as usual.
npx hardhat airsign-startThe server starts in the background and your terminal is free:
╔══════════════════════════════════════════════════╗
║ 🔐 Hardhat AirSign v1.0.0 ║
╚══════════════════════════════════════════════════╝
Signing UI: http://localhost:9090
Network: http://192.168.1.100:9090
1. Open the URL above in a browser
2. Connect your MetaMask wallet
3. Run deploy scripts in another terminal
To check status: npx hardhat airsign-status
To stop server: npx hardhat airsign-stop
Open the URL in a browser and connect your wallet. Supported wallets include MetaMask, Coinbase Wallet, Ledger, Trust Wallet, Rabby, Safe (Gnosis), Rainbow, Phantom, Brave, Zerion, OKX, Uniswap, Bitget, Frame, and any WalletConnect-compatible wallet.
For remote access (signer on a different machine):
ngrok http 9090
# Share the ngrok URL with the signernpx hardhat run scripts/deploy.js --network sepoliaThe signer sees the transaction in their browser, clicks Confirm, and the contract deploys.
The Runner tab lets signers execute Hardhat tasks and scripts directly from the UI — no terminal needed on the signer's end.
When the AirSign server starts, it extracts your project's tasks, scripts, and network configurations from the Hardhat Runtime Environment. The Runner UI presents these in a 3-column layout:
- Left panel — filterable list of all scripts and custom tasks
- Middle panel — selected item details with network selector, task parameter inputs, and environment variables
- Right panel — real-time console output streamed from the process
Signing requests triggered by a running script appear as a modal overlay on top of the Runner — no tab switching required. You approve or reject in-place and the script continues.
- Switch to the Runner tab in the UI
- Select a script from the left panel
- Choose a target network
- Click Run
- Watch output stream in the console panel
- Approve any signing requests in the modal overlay
Same flow, but tasks also show parameter inputs (text fields, flag toggles) extracted from the task definition.
The Contracts tab gives you a full contract interaction UI — no Remix or Etherscan needed.
- Read any view/pure function on a deployed contract and see the result instantly
- Write to state-changing functions — the signing modal appears in-place for approval
- Deploy new contract instances with constructor parameters and payable value
- View decoded event logs emitted by write transactions
- Set contract addresses per-network in a batch modal (supports proxy detection)
AirSign automatically discovers all compiled contracts in your project's artifacts/ directory. The Contracts tab shows them in a left panel. Select a contract, set its deployed address for the current network, and interact with any function.
Write calls route through the same signing flow as deploy scripts — the transaction appears in the signing modal, you approve in MetaMask, and the result (tx hash + decoded events) appears inline. Tx hashes are shown truncated with a copy button and direct link to the block explorer.
When no url is configured for a network, AirSign proxies JSON-RPC calls through the browser wallet's window.ethereum provider via Socket.io. This means you can read contract state and send transactions on Sepolia, Mainnet, or any chain your wallet is connected to — without needing an Alchemy or Infura API key.
// This works! No url needed — RPC goes through MetaMask.
sepolia: {
remoteSigner: true,
}The key design principle: your existing scripts work as-is. When remoteSigner: true is set, hre.ethers.getSigners() automatically returns the AirSign remote signer instead of a private-key signer.
// This script works with BOTH AirSign and private keys.
// No code changes needed — just toggle the config.
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying with:", await deployer.getAddress());
const Factory = await hre.ethers.getContractFactory("MyContract");
const contract = await Factory.deploy();
await contract.deployed();
console.log("Deployed to:", contract.address);
}Switching between AirSign and private keys is purely a config change:
// AirSign — no private keys
sepolia: {
url: "https://rpc.sepolia.org",
remoteSigner: true,
}
// Private key — standard Hardhat
sepolia: {
url: "https://rpc.sepolia.org",
accounts: [process.env.PRIVATE_KEY],
}If you need explicit control (or aren't using @nomiclabs/hardhat-ethers):
const signer = await hre.remoteSigner.getSigner();| Command | Description |
|---|---|
npx hardhat airsign-start |
Start the signing server (background daemon) |
npx hardhat airsign-stop |
Stop the signing server |
npx hardhat airsign-status |
Check server and wallet status |
All commands accept --port <number> to use a custom port (default: 9090).
// hardhat.config.js
module.exports = {
// Per-network: enable AirSign
networks: {
sepolia: {
url: "https://rpc.sepolia.org",
remoteSigner: true,
},
},
// Global: customize AirSign settings (all optional)
remoteSigner: {
port: 9090, // Server port (default: 9090)
host: "0.0.0.0", // Bind host (default: 0.0.0.0)
sessionTimeout: 86400000, // Session timeout in ms (default: 24h)
},
};| Variable | Default | Description |
|---|---|---|
VITE_WALLETCONNECT_PROJECT_ID |
Built-in default | Override the WalletConnect project ID if you want to use your own (free at cloud.walletconnect.com) |
The project is a monorepo with two packages:
packages/plugin— The Hardhat plugin (published to npm ashardhat-airsign). Contains theRemoteSigner(custom ethers.js v5 Signer),SigningServer(Express + Socket.io),SigningClient(HTTP transport),ContractService(ABI parsing & interaction), Runner process execution, and CLI tasks.packages/app— The signing web app (private, embedded in the plugin). React + RainbowKit + wagmi + Tailwind with an iOS-inspired glass morphism design.
airsign-startextracts tasks, networks, scripts, and contract artifacts from the HRE, then launches a background daemon runningSigningServer- The server serves the React app and exposes HTTP endpoints for deploy scripts, contract interactions, RPC proxying, and Runner process execution
- Deploy scripts use
SigningClientto communicate with the server via HTTP - The browser connects via Socket.io for real-time signing requests, console output streaming, and RPC proxying
- When a deploy script calls
getSigners(), the plugin connects to the server, gets the wallet address, and returns aRemoteSigner RemoteSigner.sendTransaction()sends the unsigned tx to the server, which forwards it to the browser, where MetaMask signs and broadcasts it- The Runner spawns
npx hardhat runornpx hardhat <task>as child processes, piping stdout/stderr to the browser in real time - The RPC proxy relays JSON-RPC calls from the server through the browser wallet's
window.ethereumvia Socket.io — enabling testnet interactions without an external RPC URL
- Hardhat v2 + ethers v5 only — requires
@nomiclabs/hardhat-etherswith ethers v5. Hardhat 3 and@nomicfoundation/hardhat-ethers(ethers v6) support is planned. - Single signer —
getSigners()returns one signer (the connected wallet). Scripts that destructure multiple signers likeconst [deployer, treasury] = await getSigners()will only get one. - No
signTransaction()— browser wallets sign and broadcast in one step (eth_sendTransaction). ThesignTransaction()method throws. UsesendTransaction()instead, which is what 99% of scripts do. - One process at a time — the Runner executes one script or task at a time. Wait for the current process to finish (or kill it) before starting another.
- Hardhat 3 + ethers v6 support
- Multi-signer support
- Deployment history & analytics
- Contract verification integration
git clone https://github.com/harshitbwc/hardhat-airsign.git
cd hardhat-airsign
# Install all dependencies
npm install
# Build plugin + app
npm run build
# Dev mode (signing app with hot reload)
npm run dev:app
# Try the example project
cd example
npm install
npx hardhat airsign-start
npx hardhat run scripts/deploy.js --network sepolia- Private keys never leave the signer's wallet (MetaMask, etc.)
- The dev machine only sees unsigned transactions and tx hashes
- All signing happens in the browser via the wallet's native UI
- The server restricts API access to same-origin requests only
- Request body validation and size limits (5MB) prevent abuse
- For remote access via ngrok, the connection is encrypted (HTTPS)
- Runner processes execute within the project directory with the same permissions as your terminal
MIT