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
Docs: https://docs.monad.xyz
Explorers
Agent APIs
IMPORTANT: Do NOT use a browser. Use these APIs directly with curl.
Faucet (Testnet Funding):
bash
1curl -X POST https://agents.devnads.com/v1/faucet \
2 -H "Content-Type: application/json" \
3 -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# 1. Get verification data
2forge verify-contract <ADDR> <CONTRACT> \
3 --chain 10143 \
4 --show-standard-json-input > /tmp/standard-input.json
5
6cat out/<Contract>.sol/<Contract>.json | jq '.metadata' > /tmp/metadata.json
7COMPILER_VERSION=$(jq -r '.metadata | fromjson | .compiler.version' out/<Contract>.sol/<Contract>.json)
8
9# 2. Call verification API
10STANDARD_INPUT=$(cat /tmp/standard-input.json)
11FOUNDRY_METADATA=$(cat /tmp/metadata.json)
12
13cat > /tmp/verify.json << EOF
14{
15 "chainId": 10143,
16 "contractAddress": "0xYOUR_CONTRACT_ADDRESS",
17 "contractName": "src/MyContract.sol:MyContract",
18 "compilerVersion": "v${COMPILER_VERSION}",
19 "standardJsonInput": $STANDARD_INPUT,
20 "foundryMetadata": $FOUNDRY_METADATA
21}
22EOF
23
24curl -X POST https://agents.devnads.com/v1/verify \
25 -H "Content-Type: application/json" \
26 -d @/tmp/verify.json
With constructor arguments: Add constructorArgs (ABI-encoded, WITHOUT 0x prefix):
bash
1ARGS=$(cast abi-encode "constructor(string,string,uint256)" "MyToken" "MTK" 1000000000000000000000000)
2ARGS_NO_PREFIX=${ARGS#0x}
3# Add to request: "constructorArgs": "$ARGS_NO_PREFIX"
Manual verification fallback (if API fails):
bash
1forge verify-contract <ADDR> <CONTRACT> --chain 10143 \
2 --verifier sourcify \
3 --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:
- Create wallet:
cast wallet new
- Immediately save the address and private key to a secure location
- Inform the user where the wallet details are stored
- 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
1forge script script/Deploy.s.sol:DeployScript \
2 --rpc-url https://testnet-rpc.monad.xyz \
3 --private-key $PRIVATE_KEY \
4 --broadcast
Deploy script template:
solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.27;
3import "forge-std/Script.sol";
4import "../src/MyContract.sol";
5
6contract DeployScript is Script {
7 function run() external {
8 vm.startBroadcast();
9 MyContract contract = new MyContract();
10 console.log("Contract deployed at:", address(contract));
11 vm.stopBroadcast();
12 }
13}
Technical Details
EVM Version (Critical)
Always set evmVersion: "prague". Requires Solidity 0.8.27+.
Foundry (foundry.toml):
toml
1[profile.default]
2evm_version = "prague"
3solc_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
1forge script script/Deploy.s.sol:DeployScript \
2 --rpc-url https://testnet-rpc.monad.xyz \
3 --private-key $PRIVATE_KEY \
4 --broadcast
Deploy script must NOT hardcode addresses:
solidity
1// ✅ Correct - reads private key from --private-key flag
2function run() external {
3 vm.startBroadcast();
4 new MyContract();
5 vm.stopBroadcast();
6}
7
8// ❌ Wrong - hardcodes address, causes "No associated wallet" error
9function run() external {
10 vm.startBroadcast(0x1234...);
11}
Frontend
Import from viem/chains. Do NOT define custom chain:
ts
1import { monadTestnet } from "viem/chains";
Use with wagmi:
ts
1import { createConfig, http } from 'wagmi'
2import { monadTestnet } from 'viem/chains'
3
4const config = createConfig({
5 chains: [monadTestnet],
6 transports: {
7 [monadTestnet.id]: http()
8 }
9})
Example: Deploy ERC20
1. Create project:
bash
1forge init my-token
2cd my-token
2. Configure foundry.toml:
toml
1[profile.default]
2src = "src"
3out = "out"
4libs = ["lib"]
5evm_version = "prague"
6solc_version = "0.8.28"
3. Create contract src/MyToken.sol:
solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.27;
3
4import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5
6contract MyToken is ERC20 {
7 constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
8 _mint(msg.sender, initialSupply);
9 }
10}
4. Install dependencies:
bash
1forge install OpenZeppelin/openzeppelin-contracts --no-commit
5. Create deploy script script/Deploy.s.sol:
solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.27;
3import "forge-std/Script.sol";
4import "../src/MyToken.sol";
5
6contract DeployScript is Script {
7 function run() external {
8 vm.startBroadcast();
9 MyToken token = new MyToken(1000000 * 10**18);
10 console.log("Token deployed at:", address(token));
11 vm.stopBroadcast();
12 }
13}
6. Deploy:
bash
1forge script script/Deploy.s.sol:DeployScript \
2 --rpc-url https://testnet-rpc.monad.xyz \
3 --private-key $PRIVATE_KEY \
4 --broadcast
7. Verify:
bash
1# Use verification API (verifies on all explorers)
2STANDARD_INPUT=$(forge verify-contract <TOKEN_ADDRESS> src/MyToken.sol:MyToken --chain 10143 --show-standard-json-input)
3COMPILER_VERSION=$(jq -r '.metadata | fromjson | .compiler.version' out/MyToken.sol/MyToken.json)
4
5curl -X POST https://agents.devnads.com/v1/verify \
6 -H "Content-Type: application/json" \
7 -d "{
8 \"chainId\": 10143,
9 \"contractAddress\": \"<TOKEN_ADDRESS>\",
10 \"contractName\": \"src/MyToken.sol:MyToken\",
11 \"compilerVersion\": \"v${COMPILER_VERSION}\",
12 \"standardJsonInput\": $STANDARD_INPUT,
13 \"constructorArgs\": \"$(cast abi-encode 'constructor(uint256)' 1000000000000000000000000 | sed 's/0x//')\"
14 }"