> ## Documentation Index
> Fetch the complete documentation index at: https://seilabs-docs-evm-cookbook.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify Contracts

> Comprehensive guide on how to verify your EVM smart contracts on Sei

export const AddSeiButton = ({network = 'mainnet', label = 'Add Sei to MetaMask'}) => {
  const SEI_MAINNET_CHAIN_PARAMS = {
    chainId: '0x531',
    chainName: 'Sei Network',
    rpcUrls: ['https://evm-rpc.sei-apis.com'],
    nativeCurrency: {
      name: 'Sei',
      symbol: 'SEI',
      decimals: 18
    },
    blockExplorerUrls: ['https://seiscan.io']
  };
  const SEI_TESTNET_CHAIN_PARAMS = {
    chainId: '0x530',
    chainName: 'Sei Testnet',
    rpcUrls: ['https://evm-rpc-testnet.sei-apis.com'],
    nativeCurrency: {
      name: 'Sei',
      symbol: 'SEI',
      decimals: 18
    },
    blockExplorerUrls: ['https://testnet.seiscan.io']
  };
  const chainParams = network === 'testnet' ? SEI_TESTNET_CHAIN_PARAMS : SEI_MAINNET_CHAIN_PARAMS;
  const [status, setStatus] = useState(null);
  const [isHovered, setIsHovered] = useState(false);
  const [isBusy, setIsBusy] = useState(false);
  const addOrSwitchSeiNetwork = async params => {
    if (typeof window === 'undefined' || !window.ethereum) {
      throw new Error('MetaMask is not installed');
    }
    const ethereum = window.ethereum;
    try {
      await ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{
          chainId: params.chainId
        }]
      });
    } catch (switchError) {
      if (switchError && switchError.code === 4902) {
        await ethereum.request({
          method: 'wallet_addEthereumChain',
          params: [params]
        });
      } else {
        throw switchError;
      }
    }
  };
  const onClick = async e => {
    e.preventDefault();
    setIsBusy(true);
    setStatus(null);
    try {
      await addOrSwitchSeiNetwork(chainParams);
      setStatus({
        type: 'success',
        message: `${chainParams.chainName} added or switched.`
      });
    } catch (err) {
      const message = err && err.message ? err.message : 'Failed to add or switch network.';
      setStatus({
        type: 'error',
        message
      });
    } finally {
      setIsBusy(false);
    }
  };
  return <span className="inline-flex flex-col items-start gap-1">
      <button type="button" onClick={onClick} disabled={isBusy} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} className="inline-flex items-center gap-1 px-3 py-1.5 text-white transition-colors min-w-[160px]" style={{
    backgroundColor: isHovered ? 'var(--sei-maroon-200)' : 'var(--sei-maroon-100)',
    color: '#ffffff',
    fontFamily: 'var(--sei-font-mono)',
    textTransform: 'uppercase',
    letterSpacing: '0.04em',
    fontSize: '10px',
    opacity: isBusy ? 0.7 : 1,
    cursor: isBusy ? 'default' : 'pointer'
  }}>
        {isBusy ? 'Adding…' : label}
      </button>
      {status && (status.type === 'error' ? <span className="text-red-600 dark:text-red-400" style={{
    fontSize: '11px'
  }}>
            {status.message}
          </span> : <span className="text-green-700 dark:text-green-400" style={{
    fontSize: '11px'
  }}>
            {status.message}
          </span>)}
    </span>;
};

export const SandboxEmbed = props => {
  const {src, kind = 'codesandbox', title, description, height, label} = props || ({});
  const KINDS = {
    codesandbox: {
      name: 'CodeSandbox',
      host: 'codesandbox.io',
      defaultHeight: 500
    },
    remix: {
      name: 'Remix IDE',
      host: 'remix.ethereum.org',
      defaultHeight: 620
    },
    stackblitz: {
      name: 'StackBlitz',
      host: 'stackblitz.com',
      defaultHeight: 500
    }
  };
  const meta = KINDS[kind] || KINDS.codesandbox;
  const parsedHeight = Number(height);
  const frameHeight = Number.isFinite(parsedHeight) && parsedHeight > 0 ? parsedHeight : meta.defaultHeight;
  const allowAttr = 'clipboard-read; clipboard-write';
  const [frameSrc, setFrameSrc] = useState(null);
  const [btnHover, setBtnHover] = useState(false);
  const [isDark, setIsDark] = useState(true);
  useLayoutEffect(() => {
    const el = document.documentElement;
    const update = () => setIsDark(el.classList.contains('dark'));
    update();
    const obs = new MutationObserver(update);
    obs.observe(el, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => obs.disconnect();
  }, []);
  const themedSrc = (() => {
    if (!src) return src;
    try {
      const url = new URL(src);
      url.searchParams.set('theme', isDark ? 'dark' : 'light');
      return url.toString();
    } catch {
      return src;
    }
  })();
  const loadEditor = () => {
    if (themedSrc) setFrameSrc(themedSrc);
  };
  const loaded = frameSrc !== null;
  const HAIRLINE = 'rgba(128, 128, 128, 0.25)';
  const surfaceStyle = {
    backgroundColor: 'rgba(128, 128, 128, 0.08)'
  };
  const monoStyle = {
    fontFamily: 'var(--sei-font-mono)'
  };
  const buttonStyle = {
    backgroundColor: btnHover ? 'var(--sei-maroon-200)' : 'var(--sei-maroon-100)',
    color: '#ffffff',
    fontFamily: 'var(--sei-font-mono)',
    textTransform: 'uppercase',
    letterSpacing: '0.04em',
    fontSize: '10px',
    cursor: 'pointer'
  };
  const PlayIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
			<path d="M8 5v14l11-7z" />
		</svg>;
  const ExternalIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
			<path d="M14 5h5v5" />
			<path d="M19 5l-9 9" />
			<path d="M19 14v5a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h5" />
		</svg>;
  return <div className="not-prose w-full rounded-lg border overflow-hidden my-4" style={{
    borderColor: HAIRLINE
  }}>
			<div className="flex items-center justify-between gap-3 px-4 py-2.5 border-b" style={{
    ...surfaceStyle,
    borderBottomColor: HAIRLINE
  }}>
				<div className="flex flex-col min-w-0">
					<span className="text-sm font-medium text-neutral-900 dark:text-white truncate" style={monoStyle}>
						{title || meta.name}
					</span>
					<span className="text-xs text-neutral-600 dark:text-neutral-400">{meta.name}</span>
				</div>
				<div className="flex items-center gap-3 shrink-0">
					{themedSrc ? <a href={themedSrc} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors">
							Open <ExternalIcon />
						</a> : null}
					{!loaded && themedSrc ? <button type="button" onClick={loadEditor} onMouseEnter={() => setBtnHover(true)} onMouseLeave={() => setBtnHover(false)} className="inline-flex items-center gap-1.5 px-3 py-1.5 transition-colors" style={buttonStyle}>
							<PlayIcon />
							{label || 'Load editor'}
						</button> : null}
				</div>
			</div>

			{description ? <div className="px-4 pt-3 pb-1 text-sm text-neutral-600 dark:text-neutral-400">{description}</div> : null}

			{!themedSrc ? <div className="px-4 py-6 text-sm text-red-600 dark:text-red-400" style={monoStyle}>
					SandboxEmbed: missing required `src`.
				</div> : loaded ? <iframe src={frameSrc} title={title || meta.name} className="w-full block border-0" style={{
    height: frameHeight + 'px',
    backgroundColor: 'rgba(128, 128, 128, 0.05)'
  }} allow={allowAttr} loading="lazy" allowFullScreen /> : <button type="button" onClick={loadEditor} className="w-full flex flex-col items-center justify-center gap-2 text-neutral-600 dark:text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200 transition-colors" style={{
    height: frameHeight + 'px',
    cursor: 'pointer',
    ...surfaceStyle
  }}>
					<PlayIcon />
					<span className="text-sm" style={monoStyle}>Click to load {meta.name}</span>
					<span className="text-xs">Loads {meta.host} in an embedded editor</span>
				</button>}
		</div>;
};

Verify your deployed contract with flattened source code, JSON input, Sourcify, or other methods. Verification makes your contract's source code public and verifiable. This creates transparency and trust.

## Benefits of verification

* Transparency: the source code is publicly available.
* Trust: the community can verify the contract's functionality.
* Security: verification gives greater confidence in the contract's integrity.

## Sourcify

[Sourcify](https://sourcify.dev/) is a decentralized, open-source service for contract verification. Sei has full Sourcify support on both Sei Mainnet (chain ID 1329) and Sei Testnet (chain ID 1328).

Sourcify verifies contracts byte by byte. It recompiles your source code with the exact same compiler settings and compares the output with the on-chain bytecode. You do not need an API key.

### Verify with Foundry

After you deploy your contract with Foundry, verify it on Sourcify:

```bash theme={"dark"}
forge verify-contract \
  --verifier sourcify \
  --chain-id <CHAIN_ID> \
  <DEPLOYED_CONTRACT_ADDRESS> \
  <PATH:CONTRACT_NAME>
```

**Sei Testnet example:**

```bash theme={"dark"}
forge verify-contract \
  --verifier sourcify \
  --chain-id 1328 \
  0x1234567890abcdef1234567890abcdef12345678 \
  src/Counter.sol:Counter
```

**Sei Mainnet example:**

```bash theme={"dark"}
forge verify-contract \
  --verifier sourcify \
  --chain-id 1329 \
  0x1234567890abcdef1234567890abcdef12345678 \
  src/Counter.sol:Counter
```

To check the verification status, run:

```bash theme={"dark"}
forge verify-check \
  --verifier sourcify \
  --chain-id 1328 \
  <VERIFICATION_JOB_ID>
```

You can also deploy and verify in a single step with `forge create`:

```bash theme={"dark"}
forge create src/Counter.sol:Counter \
  --rpc-url https://evm-rpc-testnet.sei-apis.com \
  --private-key $PRIVATE_KEY \
  --broadcast \
  --verify \
  --verifier sourcify \
  --chain-id 1328
```

<Info>To pass constructor arguments, put `--constructor-args` last, because it takes every value after it. For example: `forge create src/Token.sol:Token --broadcast --constructor-args "MyToken" "MTK" 18`</Info>

### Verify with Hardhat

The [`hardhat-verify`](https://hardhat.org/docs/learn-more/smart-contract-verification) plugin supports Sourcify by default. You do not need extra configuration or an API key.

#### 1. Install dependencies

```bash theme={"dark"}
npm install --save-dev hardhat @nomicfoundation/hardhat-verify
```

#### 2. Configure `hardhat.config.ts`

```ts theme={"dark"}
import { defineConfig, configVariable } from "hardhat/config";
import hardhatVerify from "@nomicfoundation/hardhat-verify";

export default defineConfig({
  networks: {
    sei_testnet: {
      type: "http",
      chainId: 1328,
      url: "https://evm-rpc-testnet.sei-apis.com",
      accounts: [configVariable("SEI_PRIVATE_KEY")],
    },
    sei_mainnet: {
      type: "http",
      chainId: 1329,
      url: "https://evm-rpc.sei-apis.com",
      accounts: [configVariable("SEI_PRIVATE_KEY")],
    },
  },

  solidity: {
    compilers: [
      {
        version: "0.8.28",
        settings: {
          optimizer: { enabled: true, runs: 200 },
        },
      },
    ],
  },

  plugins: [hardhatVerify],
});
```

<Info>`hardhat-verify` enables Sourcify verification by default. You do not need a `verify` config block for Sourcify.</Info>

#### 3. Deploy

```bash theme={"dark"}
npx hardhat ignition deploy ignition/modules/Counter.ts --network sei_testnet
```

#### 4. Verify with Sourcify

```bash theme={"dark"}
npx hardhat verify sourcify --network sei_testnet <CONTRACT_ADDRESS>
```

If your contract has constructor arguments, add them to the command:

```bash theme={"dark"}
npx hardhat verify sourcify --network sei_testnet <CONTRACT_ADDRESS> "arg1" "arg2"
```

If you run `npx hardhat verify` without a subtask, it tries to verify on all enabled providers (Etherscan, Blockscout, and Sourcify) at the same time.

### Verify via Sourcify UI

You can also verify contracts directly through the [Sourcify web interface](https://verify.sourcify.dev/):

1. Go to [verify.sourcify.dev](https://verify.sourcify.dev/)
2. Select **Sei** (chain ID 1329) or **Sei Testnet** (chain ID 1328)
3. Enter your contract address
4. Upload your source files or standard JSON input
5. Click **Verify**

## Remix Contract Verification plugin

Remix IDE verifies contracts automatically through its Contract Verification plugin. The plugin integrates with both the Sourcify and Etherscan verification services.

If you do not have a deployed contract yet, open Remix with a sample `Counter.sol` and deploy it to Sei Testnet. Then follow the steps below to verify it.

<AddSeiButton network="testnet" label="Add Sei Testnet" />

<SandboxEmbed kind="remix" src="https://remix.ethereum.org/?#activate=solidity,fileManager&code=Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yNDsKCi8vLyBAdGl0bGUgQ291bnRlciDigJQgbWluaW1hbCBjb250cmFjdCB0byBjb21waWxlICYgZGVwbG95IHRvIFNlaSB0ZXN0bmV0Lgpjb250cmFjdCBDb3VudGVyIHsKICAgIHVpbnQyNTYgcHVibGljIGNvdW50OwogICAgZnVuY3Rpb24gaW5jcmVtZW50KCkgZXh0ZXJuYWwgeyBjb3VudCsrOyB9CiAgICBmdW5jdGlvbiBkZWNyZW1lbnQoKSBleHRlcm5hbCB7IGNvdW50LS07IH0KfQ" title="Counter.sol · deploy to Sei Testnet" description="Remix IDE with a minimal Counter contract preloaded. Deploy it to Sei Testnet, then verify it with the Contract Verification plugin below." />

### Setup and configuration

1. **Activate the plugin**: In Remix IDE, go to the **Plugin Manager** and activate the **Contract Verification** plugin.

2. **Configure network settings**:
   * Search for and select **Sei** as your blockchain
   * Choose your deployment environment:
     * **Testnet** (1328) for development
     * **Mainnet** (1329) for production

3. **Enable verification services**: Activate both verification methods:
   * **Sourcify**
   * **Etherscan**

4. **Configure Etherscan**: Set these values:
   * **API Key**: Get a key from [seiscan.io](https://seiscan.io)
   * **URL**: `https://seiscan.io`

### Deploy and verify

After you configure the plugin, deploy your contract through Remix as usual. When the deployment succeeds, the Contract Verification plugin verifies your contract automatically. It submits the contract to both Sourcify and Seiscan at the same time.

The plugin interface shows the verification status. Your verified contract is then publicly visible on the Seiscan explorer.

## Verify via 0xngmi Etherscan Verification (third‑party)

If a contract is already verified on another chain, you can batch-verify it with the community tool [Etherscan Verification by 0xngmi](https://etherscan-verification.0xngmi.com/). The tool copies the verified source from a "source chain" and submits it to [Seiscan](https://seiscan.io). This works only if the bytecode and compiler settings match.

<Warning>Prerequisite: The contract is already verified on at least one supported chain. That verified deployment must use the same compiler version, optimization runs, constructor arguments, and library addresses as the deployment on Sei.</Warning>

Steps:

1. Open [Etherscan Verification by 0xngmi](https://etherscan-verification.0xngmi.com/).
2. In **Source Contract**, paste the address of the contract that is already verified and select its chain.
3. In **Target Contracts**, add the address of your Sei deployment and select **Sei** as the target chain.
4. Click **Verify Contracts** and wait for the result.
5. Confirm the result on [Seiscan](https://seiscan.io). Open the contract page and check the **Contract** tab.

<Info>If verification fails with a bytecode mismatch, check the compiler version, optimization runs, constructor arguments, and linked library addresses again. They must be identical to the deployment on Sei.</Info>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.