Contract Verification
Contract verification publishes your smart contract's source code on Monoscan, allowing anyone to read the code, audit it, and interact with it directly through the explorer. Verified contracts display a green checkmark and expose read/write interfaces.
Why Verify
| Benefit | Description |
|---|---|
| Transparency | Anyone can inspect your contract's logic |
| Trust | Users can confirm the deployed bytecode matches the source |
| Interaction | Monoscan provides a UI for calling read and write functions |
| Composability | Other developers can import your verified ABI |
Always verify contracts after deployment. Unverified contracts are opaque to users and make debugging significantly harder.
Monoscan Web Verification
The simplest method is verifying directly through the Monoscan web interface.
Steps
- Navigate to your contract on Monoscan:
https://testnet.monoscan.xyz/address/<your-contract-address> - Click the Contract tab
- Click Verify & Publish
- Fill in the form:
- Compiler version -- Must match the version used during compilation (e.g.,
v0.8.19+commit.7dd6d404) - Optimization -- Must match your compiler settings (enabled/disabled, number of runs)
- License type -- Select the SPDX license identifier from your contract
- Compiler version -- Must match the version used during compilation (e.g.,
- Paste your Solidity source code (or upload standard JSON input for multi-file contracts)
- If the contract has constructor arguments, enter them as ABI-encoded hex
- Click Verify and Publish
If successful, the contract page will display the source code with a green checkmark.
Hardhat Verify Plugin
The @nomicfoundation/hardhat-verify plugin (included in hardhat-toolbox) automates verification from the command line.
Configuration
Add the Monoscan endpoint to hardhat.config.ts:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.19",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
"monolythium-testnet": {
url: "https://evm.testnet.mononodes.xyz",
chainId: 6940,
accounts: [process.env.PRIVATE_KEY!],
},
"monolythium-mainnet": {
url: "https://evm.mainnet.mononodes.xyz",
chainId: 6941,
accounts: [process.env.PRIVATE_KEY!],
},
},
etherscan: {
apiKey: {
"monolythium-testnet": "",
"monolythium-mainnet": "",
},
customChains: [
{
network: "monolythium-testnet",
chainId: 6940,
urls: {
apiURL: "https://testnet.monoscan.xyz/api",
browserURL: "https://testnet.monoscan.xyz",
},
},
{
network: "monolythium-mainnet",
chainId: 6941,
urls: {
apiURL: "https://mainnet.monoscan.xyz/api",
browserURL: "https://mainnet.monoscan.xyz",
},
},
],
},
};
export default config;
Monoscan does not require an API key for verification. Pass an empty string for the apiKey value.
Verify a Contract
# Basic verification (no constructor args)
npx hardhat verify --network monolythium-testnet <contract-address>
# With constructor arguments
npx hardhat verify --network monolythium-testnet <contract-address> <arg1> <arg2>
Example
For a contract deployed with constructor argument 10000000000000000 (0.01 LYTH in alyth):
npx hardhat verify --network monolythium-testnet 0x1234...abcd 10000000000000000
Complex Constructor Arguments
If your constructor takes complex types (arrays, structs, bytes), create an arguments file:
// arguments.js
module.exports = [
10000000000000000n, // minimumDeposit
"0x6149063DF73A0d4065C9083a3E731256Ed10dc95", // owner address
["0xabc...", "0xdef..."], // allowed addresses
];
npx hardhat verify --network monolythium-testnet \
--constructor-args arguments.js \
<contract-address>
Foundry Verify
Foundry's forge verify-contract command supports Monoscan verification.
Basic Verification
forge verify-contract <contract-address> src/MyContract.sol:MyContract \
--chain-id 6940 \
--verifier-url https://testnet.monoscan.xyz/api \
--etherscan-api-key ""
With Constructor Arguments
forge verify-contract <contract-address> src/MessageBoard.sol:MessageBoard \
--chain-id 6940 \
--verifier-url https://testnet.monoscan.xyz/api \
--etherscan-api-key "" \
--constructor-args $(cast abi-encode "constructor(uint256)" 10000000000000000)
With Compiler Settings
If you used specific optimizer settings, pass them explicitly:
forge verify-contract <contract-address> src/MyContract.sol:MyContract \
--chain-id 6940 \
--verifier-url https://testnet.monoscan.xyz/api \
--etherscan-api-key "" \
--num-of-optimizations 200 \
--compiler-version v0.8.19
Check Verification Status
forge verify-check <guid> \
--chain-id 6940 \
--verifier-url https://testnet.monoscan.xyz/api
Multi-File Contracts
Contracts that import from multiple files (e.g., OpenZeppelin) require either standard JSON input or flattened source.
Standard JSON Input (Recommended)
Standard JSON input preserves the original file structure and is the most reliable method.
Hardhat: The verify plugin automatically uses standard JSON input. No extra steps needed.
Foundry: Generate the JSON input and submit it:
# Generate standard JSON input
forge verify-contract <address> src/MyContract.sol:MyContract \
--chain-id 6940 \
--verifier-url https://testnet.monoscan.xyz/api \
--etherscan-api-key "" \
--show-standard-json-input > standard-input.json
You can then paste the contents of standard-input.json into Monoscan's web verification form using the Standard JSON Input option.
Flattened Source
Flattening merges all imports into a single file. Use this as a fallback if standard JSON input fails.
# Hardhat
npx hardhat flatten contracts/MyContract.sol > Flattened.sol
# Foundry
forge flatten src/MyContract.sol > Flattened.sol
Flattened files may contain duplicate SPDX license identifiers or pragma statements. Remove duplicates before submitting to Monoscan.
Proxy Contracts
If your contract uses a proxy pattern (UUPS, Transparent, or Beacon), you need to verify both the proxy and the implementation.
Transparent Proxy
- Verify the implementation contract using any method above
- Mark as proxy on Monoscan:
- Go to the proxy contract address on Monoscan
- Click More Options > Is this a proxy?
- Monoscan will detect the implementation address from storage slot
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc - Confirm the detected implementation address
- The proxy page will now show the implementation's read/write interface
UUPS Proxy
Same process as transparent proxy. The UUPS implementation address is stored in the same EIP-1967 storage slot.
After Upgrades
When you upgrade a proxy to a new implementation:
- Verify the new implementation contract
- Monoscan should automatically detect the change on the next page load
- If not, re-run the proxy detection process
Troubleshooting
Compiler Version Mismatch
Symptom: Verification fails with "bytecode does not match"
Fix: Ensure the exact compiler version matches. Check your compiled artifacts:
# Hardhat -- check artifacts
cat artifacts/build-info/*.json | jq '.solcLongVersion'
# Foundry -- check cache
cat out/MyContract.sol/MyContract.json | jq '.metadata.compiler.version'
Optimization Settings Mismatch
Symptom: Bytecodes differ despite matching source code
Fix: Optimization must match exactly:
| Setting | Must Match |
|---|---|
| Optimizer enabled | Yes/No must be identical |
| Optimizer runs | Exact number (commonly 200) |
| EVM version | Must match (Monolythium uses paris) |
| Via IR | Must match if enabled |
Constructor Argument Encoding
Symptom: "Unable to verify" or "constructor arguments do not match"
Fix: Constructor arguments are ABI-encoded and appended to the deployment bytecode. To extract them:
# Get the deployment transaction input data
cast tx <deploy-tx-hash> --rpc-url https://evm.testnet.mononodes.xyz input
# The constructor args are the trailing bytes after the contract bytecode
# Decode them:
cast abi-decode "constructor(uint256)" <trailing-hex>
Missing Libraries
Symptom: Verification fails for contracts that use external libraries
Fix: Provide library addresses during verification:
# Hardhat
npx hardhat verify --network monolythium-testnet <address> \
--libraries contracts/libs/MyLib.sol:MyLib:<lib-address>
# Foundry
forge verify-contract <address> src/MyContract.sol:MyContract \
--chain-id 6940 \
--verifier-url https://testnet.monoscan.xyz/api \
--etherscan-api-key "" \
--libraries src/libs/MyLib.sol:MyLib:<lib-address>
Verification Pending
Symptom: Verification submitted but status shows "pending"
Fix: Wait a few minutes. If it remains pending, resubmit. Monoscan processes verification requests asynchronously.
FAQ
Does Monoscan require an API key?
No. Use an empty string for the API key in Hardhat and Foundry configurations.
Can I verify a contract deployed on mainnet?
Yes. Replace the Monoscan URL with https://mainnet.monoscan.xyz/api and the chain ID with 6941.
What if my contract was deployed with a different tool than I am verifying with?
That is fine. Verification only compares compiled bytecode. As long as the source code, compiler version, and settings produce the same bytecode, verification will succeed regardless of the deployment tool.
Can I re-verify a contract?
Yes. Re-verification overwrites the previously published source. This is useful if you initially verified with flattened source and want to switch to standard JSON input.
What happens if I verify the wrong source code?
Monoscan compares the compiled bytecode of your submitted source against the on-chain bytecode. If they do not match, verification will fail. You cannot accidentally publish incorrect source for a contract.
Related
- Deploying Contracts -- Smart contract deployment guide
- Hardhat & Foundry -- Development framework setup
- Monoscan -- Block explorer features