Builds dapps on Monad blockchain. Use when deploying contracts, setting up frontends with viem/wagmi, or verifying contracts on Monad testnet or mainnet.
---
name: monad-development
description: Builds dapps on Monad blockchain. Use when deploying contracts, setting up frontends with viem/wagmi, or verifying contracts on Monad testnet or mainnet.
license: MIT
compatibility: Requires foundry, node 18+, curl, and internet access for faucet and verification APIs
metadata:
author: monad-skills
version: "1.0.0"
---
# Monad Development
For questions not covered here, fetch https://docs.monad.xyz/llms.txt
## Quick Reference
### Defaults
- **Network:** Always use **testnet** (chain ID 10143) unless user says "mainnet"
- **Verification:** Always verify contracts after deployment unless user says not to
- **Framework:** Use Foundry (not Hardhat)
- **Wallet:** If you generate a wallet, MUST persist it (see Wallet Persistence section)
### Networks
| Network | Chain ID | RPC |
|---------|----------|-----|
| Testnet | 10143 | https://testnet-rpc.monad.xyz |
| Mainnet | 143 | https://rpc.monad.xyz |
Docs: https://docs.monad.xyz
### Explorers
| Explorer | Testnet | Mainnet |
|----------|---------|---------|
| Socialscan | https://monad-testnet.socialscan.io | https://monad.socialscan.io |
| MonadVision | https://testnet.monadvision.com | https://monadvision.com |
| Monadscan | https://testnet.monadscan.com | https://monadscan.com |
### Agent APIs
**IMPORTANT:** Do NOT use a browser. Use these APIs directly with curl.
**Faucet (Testnet Funding):**
```bash
curl -X POST https://agents.devnads.com/v1/faucet \
-H "Content-Type: application/json" \
-d '{"chainId": 10143, "address": "0xYOUR_ADDRESS"}'
```
Returns: `{"txHash": "0x...", "amount": "1000000000000000000", "chain": "Monad Testnet"}`
**Fallback (official faucet):** https://faucet.monad.xyz
If the agent faucet fails, ask the user to fund via the official faucet (do not use a browser yourself).
**Verification (All Explorers):**
ALWAYS use the verification API first. It verifies on all 3 explorers (MonadVision, Socialscan, Monadscan) with one call. Do NOT use `forge verify-contract` as first choice.
```bash
# 1. Get verification data
forge verify-contract <ADDR> <CONTRACT> \
--chain 10143 \
--show-standard-json-input > /tmp/standard-input.json
cat out/<Contract>.sol/<Contract>.json | jq '.metadata' > /tmp/metadata.json
COMPILER_VERSION=$(jq -r '.metadata | fromjson | .compiler.version' out/<Contract>.sol/<Contract>.json)
# 2. Call verification API
STANDARD_INPUT=$(cat /tmp/standard-input.json)
FOUNDRY_METADATA=$(cat /tmp/metadata.json)
cat > /tmp/verify.json << EOF
{
"chainId": 10143,
"contractAddress": "0xYOUR_CONTRACT_ADDRESS",
"contractName": "src/MyContract.sol:MyContract",
"compilerVersion": "v${COMPILER_VERSION}",
"standardJsonInput": $STANDARD_INPUT,
"foundryMetadata": $FOUNDRY_METADATA
}
EOF
curl -X POST https://agents.devnads.com/v1/verify \
-H "Content-Type: application/json" \
-d @/tmp/verify.json
```
**With constructor arguments:** Add `constructorArgs` (ABI-encoded, WITHOUT 0x prefix):
```bash
ARGS=$(cast abi-encode "constructor(string,string,uint256)" "MyToken" "MTK" 1000000000000000000000000)
ARGS_NO_PREFIX=${ARGS#0x}
# Add to request: "constructorArgs": "$ARGS_NO_PREFIX"
```
**Manual verification fallback (if API fails):**
```bash
forge verify-contract <ADDR> <CONTRACT> --chain 10143 \
--verifier sourcify \
--verifier-url "https://sourcify-api-monad.blockvision.org/"
```
## Wallet Persistence
**CRITICAL for agents:** If you generate a wallet for the user, you MUST persist it for future use.
When generating a new wallet:
1. Create wallet: `cast wallet new`
2. **Immediately save** the address and private key to a secure location
3. Inform the user where the wallet details are stored
4. Fund the wallet via faucet before deployment
**Storage options:**
- Write to `~/.monad-wallet` with chmod 600
- Store in a project-specific `.env` file (add to .gitignore)
- Return credentials to user and ask them to save securely
**Why this matters:** Users need access to their wallet to:
- Deploy additional contracts
- Interact with deployed contracts
- Manage funds
- Verify ownership
## Deployment Workflow
Use `forge script` for deployments:
```bash
forge script script/Deploy.s.sol:DeployScript \
--rpc-url https://testnet-rpc.monad.xyz \
--private-key $PRIVATE_KEY \
--broadcast
```
**Deploy script template:**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "forge-std/Script.sol";
import "../src/MyContract.sol";
contract DeployScript is Script {
function run() external {
vm.startBroadcast();
MyContract contract = new MyContract();
console.log("Contract deployed at:", address(contract));
vm.stopBroadcast();
}
}
```
## Technical Details
### EVM Version (Critical)
Always set `evmVersion: "prague"`. Requires Solidity 0.8.27+.
**Foundry** (`foundry.toml`):
```toml
[profile.default]
evm_version = "prague"
solc_version = "0.8.28"
```
### Foundry Tips
**Flags that don't exist (don't use):**
- `--no-commit` - not a valid flag for `forge init` or `forge install`
**Deployment - use `forge script`, NOT `forge create`:**
`forge create --broadcast` is buggy and often ignored. Use `forge script` instead.
```bash
forge script script/Deploy.s.sol:DeployScript \
--rpc-url https://testnet-rpc.monad.xyz \
--private-key $PRIVATE_KEY \
--broadcast
```
**Deploy script must NOT hardcode addresses:**
```solidity
// ✅ Correct - reads private key from --private-key flag
function run() external {
vm.startBroadcast();
new MyContract();
vm.stopBroadcast();
}
// ❌ Wrong - hardcodes address, causes "No associated wallet" error
function run() external {
vm.startBroadcast(0x1234...);
}
```
### Frontend
Import from `viem/chains`. Do NOT define custom chain:
```ts
import { monadTestnet } from "viem/chains";
```
Use with wagmi:
```ts
import { createConfig, http } from 'wagmi'
import { monadTestnet } from 'viem/chains'
const config = createConfig({
chains: [monadTestnet],
transports: {
[monadTestnet.id]: http()
}
})
```
## Example: Deploy ERC20
**1. Create project:**
```bash
forge init my-token
cd my-token
```
**2. Configure `foundry.toml`:**
```toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
evm_version = "prague"
solc_version = "0.8.28"
```
**3. Create contract `src/MyToken.sol`:**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
}
```
**4. Install dependencies:**
```bash
forge install OpenZeppelin/openzeppelin-contracts --no-commit
```
**5. Create deploy script `script/Deploy.s.sol`:**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "forge-std/Script.sol";
import "../src/MyToken.sol";
contract DeployScript is Script {
function run() external {
vm.startBroadcast();
MyToken token = new MyToken(1000000 * 10**18);
console.log("Token deployed at:", address(token));
vm.stopBroadcast();
}
}
```
**6. Deploy:**
```bash
forge script script/Deploy.s.sol:DeployScript \
--rpc-url https://testnet-rpc.monad.xyz \
--private-key $PRIVATE_KEY \
--broadcast
```
**7. Verify:**
```bash
# Use verification API (verifies on all explorers)
STANDARD_INPUT=$(forge verify-contract <TOKEN_ADDRESS> src/MyToken.sol:MyToken --chain 10143 --show-standard-json-input)
COMPILER_VERSION=$(jq -r '.metadata | fromjson | .compiler.version' out/MyToken.sol/MyToken.json)
curl -X POST https://agents.devnads.com/v1/verify \
-H "Content-Type: application/json" \
-d "{
\"chainId\": 10143,
\"contractAddress\": \"<TOKEN_ADDRESS>\",
\"contractName\": \"src/MyToken.sol:MyToken\",
\"compilerVersion\": \"v${COMPILER_VERSION}\",
\"standardJsonInput\": $STANDARD_INPUT,
\"constructorArgs\": \"$(cast abi-encode 'constructor(uint256)' 1000000000000000000000000 | sed 's/0x//')\"
}"
```
don't have the plugin yet? install it then click "run inline in claude" again.
added explicit intent, structured inputs with external connections and env vars, broke original procedure into 10 detailed steps with edge cases, documented decision points for network selection and error handling, formalized output contract for deployed artifacts and explorer verification, and defined outcome signals for user validation.
for questions not covered here, fetch https://docs.monad.xyz/llms.txt
this skill deploys and verifies smart contracts on monad blockchain (testnet or mainnet), sets up frontends using viem/wagmi, and manages wallet persistence for agent-driven development. use it when you need to build a complete dapp stack from contract deployment through frontend integration and on-chain verification across all monad explorers.
required tools and versions:
required environment variables or configuration:
PRIVATE_KEY: deployment wallet private key (set via flag or env var, never hardcoded in scripts)RPC_URL: optional, defaults to testnet https://testnet-rpc.monad.xyz unless user specifies mainnet~/.monad-wallet or .env file: wallet persistence location (agent-created wallets must be saved here immediately)external connections:
network reference:
| network | chain ID | RPC |
|---|---|---|
| testnet | 10143 | https://testnet-rpc.monad.xyz |
| mainnet | 143 | https://rpc.monad.xyz |
explorer reference:
| explorer | testnet | mainnet |
|---|---|---|
| monadscan | https://testnet.monadscan.com | https://monadscan.com |
| monadvision | https://testnet.monadvision.com | https://monadvision.com |
| socialscan | https://monad-testnet.socialscan.io | https://monad.socialscan.io |
input: desired project name, solidity version preference (default 0.8.28)
action:
forge init <project-name>
cd <project-name>
edit foundry.toml to set:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
evm_version = "prague"
solc_version = "0.8.28"
output: initialized foundry project with monad-compatible EVM version (prague) and solidity 0.8.28+
edge cases: if user requests older solidity (pre-0.8.27), warn that prague EVM version requires 0.8.27+. fall back to solidity 0.8.27 if user insists on older version, but document that prague EVM is required.
input: user intention (new wallet vs. existing wallet address)
action if new wallet needed:
cast wallet new
this generates a wallet with address and private key. immediately do one of:
~/.monad-wallet with chmod 600.env file (add .env to .gitignore first)action if user has existing wallet: skip generation and use provided private key.
output: address and private key stored persistently (agent responsibility). wallet ready for funding.
edge cases: if agent creates wallet but fails to persist it, subsequent deployment steps will fail because user has no record of wallet. always persist before proceeding to step 3.
input: wallet address from step 2, network selection (testnet or mainnet)
action (testnet):
curl -X POST https://agents.devnads.com/v1/faucet \
-H "Content-Type: application/json" \
-d '{"chainId": 10143, "address": "0xYOUR_ADDRESS"}'
expected response: {"txHash": "0x...", "amount": "1000000000000000000", "chain": "Monad Testnet"}
if agent faucet fails (network error, rate limit, or 5xx response), inform user to fund via fallback official faucet at https://faucet.monad.xyz. do not attempt to use browser yourself.
action (mainnet): skip faucet step. user must fund wallet externally (bridge from other chains or CEX withdrawal).
output: wallet funded with testnet monad or confirmation that user will fund mainnet wallet externally. wallet balance sufficient for contract deployment (typically 1 MONAD testnet is enough for ERC20 deploy and verification).
edge cases: rate limits on agent faucet (user may request multiple times per hour, faucet may throttle). fallback to official faucet. mainnet deployments require pre-funded wallet, agent cannot provide testnet faucet.
input: contract code (user-provided or generated based on spec, e.g. ERC20 token)
action: create contract in src/ directory with proper SPDX license and pragma.
example for ERC20:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
}
output: contract file in src/<ContractName>.sol, ready for compilation.
edge cases: if contract uses external libraries (OpenZeppelin, etc.), dependencies must be installed in step 5 before compilation. if constructor has arguments, note them for deploy script in step 6.
input: list of required libraries (e.g. openzeppelin-contracts)
action:
forge install <github-org>/<repo> --no-commit
do not use --no-commit flag unless user explicitly agrees. --no-commit adds repos without committing to git, which can lose dependency tracking.
output: libraries installed in lib/ directory, foundry.toml updated with library paths.
edge cases: if forge install fails (network issue, repo not found), verify repo path and retry. some repos may have nested dependencies that install automatically.
input: contract name, constructor arguments (if any), desired initial state
action: create file script/Deploy.s.sol with solidity script that:
example template:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "forge-std/Script.sol";
import "../src/MyContract.sol";
contract DeployScript is Script {
function run() external {
vm.startBroadcast();
MyContract contract = new MyContract(/* constructor args */);
console.log("Contract deployed at:", address(contract));
vm.stopBroadcast();
}
}
output: deploy script ready for execution. script accepts private key via --private-key flag at runtime.
edge cases: if constructor requires complex arguments (arrays, structs), encode them explicitly in the script or pass as calldata. do not hardcode wallet address in vm.startBroadcast(), this causes "no associated wallet" error.
input: complete contract with dependencies installed
action:
forge build
this compiles all contracts in src/ and outputs artifacts to out/.
output: compiled contract artifacts in out/<ContractName>.sol/<ContractName>.json including ABI, bytecode, and metadata.
edge cases: if compilation fails, check solidity version in foundry.toml (must be 0.8.27+). if evm_version is not "prague", update it. if imports fail, verify dependencies are installed in step 5.
input: deploy script from step 6, private key, network selection (testnet or mainnet, defaults to testnet)
action:
forge script script/Deploy.s.sol:DeployScript \
--rpc-url https://testnet-rpc.monad.xyz \
--private-key $PRIVATE_KEY \
--broadcast
for mainnet, replace RPC URL with https://rpc.monad.xyz and chain ID reference.
script execution will broadcast transaction(s) to chosen network and return deployed contract address(es).
output: deployed contract address, transaction hash(es), and gas used. address is needed for step 9 (verification).
edge cases: if deployment fails due to insufficient balance, fund wallet via step 3 (testnet) or externally (mainnet). if private key is invalid or malformed, forge will error. if RPC is unreachable, check network connectivity and RPC URL. if transaction reverts on-chain, check contract logic and constructor arguments for runtime errors.
input: deployed contract address from step 8, contract name (path:name format), compiled contract in out/ from step 7
action:
call verification API which verifies on all three explorers (Monadscan, MonadVision, Socialscan) with single request:
first, extract compiler version and standard JSON input:
forge verify-contract <DEPLOYED_ADDRESS> src/<ContractName>.sol:<ContractName> \
--chain 10143 \
--show-standard-json-input > /tmp/standard-input.json
COMPILER_VERSION=$(jq -r '.metadata | fromjson | .compiler.version' out/<ContractName>.sol/<ContractName>.json)
FOUNDRY_METADATA=$(cat out/<ContractName>.sol/<ContractName>.json | jq '.metadata')
then call verification API:
curl -X POST https://agents.devnads.com/v1/verify \
-H "Content-Type: application/json" \
-d @/tmp/verify.json
where /tmp/verify.json contains:
{
"chainId": 10143,
"contractAddress": "0x<DEPLOYED_ADDRESS>",
"contractName": "src/<ContractName>.sol:<ContractName>",
"compilerVersion": "v<COMPILER_VERSION>",
"standardJsonInput": <STANDARD_INPUT>,
"foundryMetadata": <FOUNDRY_METADATA>,
"constructorArgs": "<ABI_ENCODED_ARGS_NO_0X_PREFIX>"
}
if contract has constructor arguments, encode them:
ARGS=$(cast abi-encode "constructor(type1,type2)" arg1 arg2)
ARGS_NO_PREFIX=${ARGS#0x}
# add "constructorArgs": "$ARGS_NO_PREFIX" to JSON
output: verification success response from API confirming contract is verified on all explorers. users can view contract source and ABI on Monadscan, MonadVision, Socialscan.
edge cases: if verification API fails (network error, malformed input), fallback to manual verification via sourcify:
forge verify-contract <DEPLOYED_ADDRESS> src/<ContractName>.sol:<ContractName> \
--chain 10143 \
--verifier sourcify \
--verifier-url "https://sourcify-api-monad.blockvision.org/"
if constructor arguments are complex (nested arrays, structs), ensure they are ABI-encoded correctly. if verification fails due to mismatched compiler version, double-check solc_version in foundry.toml matches COMPILER_VERSION in contract metadata.
do NOT use forge verify-contract without --show-standard-json-input as first choice for monad. always use verification API first.
input: node 18+ installed, project with package.json, desired frontend framework (React, Vue, etc.)
action:
install viem and wagmi:
npm install viem wagmi
import monad testnet chain and create wagmi config:
import { createConfig, http } from 'wagmi'
import { monadTestnet } from 'viem/chains'
const config = createConfig({
chains: [monadTestnet],
transports: {
[monadTestnet.id]: http()
}
})
for mainnet, import monadMainnet (if available in viem) or define custom chain with ID 143 and RPC https://rpc.monad.xyz.
use config with wagmi hooks to connect wallet, read contract state, and write transactions to monad.
output: frontend configured with monad network support, ready to interact with deployed contracts.
edge cases: if monadMainnet is not in viem/chains (newer version may add it), define custom chain manually. if user wants to add multiple chains (monad + ethereum, etc.), add them to chains array and transports object. if frontend fails to connect to RPC, check RPC URL in config and network connectivity.
if user says "testnet" or does not specify network:
if user says "mainnet":
if user says "don't verify":
if user provides existing wallet address and private key:
if agent generates new wallet but user does not provide storage location:
if faucet API fails (network error, 5xx, rate limit):
if deployment fails with "no associated wallet" error:
if compilation fails with EVM version error:
if contract verification fails with constructor argument mismatch:
successful deployment and verification produces:
out/<ContractName>.sol/<ContractName>.jsonscript/Deploy.s.sol saved to project repooptional frontend output (if step 10 executed):
the skill worked when: