> ## Documentation Index
> Fetch the complete documentation index at: https://docs.immutable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy Contracts with Hardhat

Deploy smart contracts to Immutable zkEVM using Hardhat, a popular Ethereum development environment.

<Tip>
  **Use Immutable Contract Presets**: Immutable provides audited, production-ready contract presets that include built-in royalty enforcement, operator allowlists, and metadata management. See the [ERC-721](/docs/products/asset-contracts/erc721), [ERC-1155](/docs/products/asset-contracts/erc1155), and [ERC-20](/docs/products/asset-contracts/erc20) preset documentation for details.
</Tip>

## Immutable zkEVM Collection Requirements

All NFT collections deployed on Immutable zkEVM must implement:

* **Royalty Enforcement**: EIP-2981 royalty standard with on-chain enforcement via the Operator Allowlist
* **Operator Allowlist**: Restrict transfers to approved marketplaces and platforms
* **Metadata Standards**: ERC-721/ERC-1155 metadata standards with proper token/collection URIs

Immutable's preset contracts handle these requirements automatically. If using custom contracts, ensure they meet these standards.

<Info>
  See the [Operator Allowlist](/docs/products/asset-contracts/operator-allowlist) and [Royalties](/docs/products/asset-contracts/royalties) documentation for detailed implementation guidance.
</Info>

## Setup Hardhat

Install and configure Hardhat following the [official Hardhat setup guide](https://hardhat.org/hardhat-runner/docs/getting-started#installation). Install the toolbox and `dotenv` packages:

```bash theme={null}
npm install --save-dev @nomicfoundation/hardhat-toolbox dotenv
```

## Configure Hardhat

To deploy contracts to Immutable zkEVM, configure your `hardhat.config.ts` file with network settings:

```typescript theme={null}
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";

dotenv.config();

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.19",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
<HardhatNetworkConfig />
};

export default config;
```

<Info>
  **Network Details:**

  | Network     | RPC URL                             | Chain ID |
  | ----------- | ----------------------------------- | -------- |
  | **Testnet** | `https://rpc.testnet.immutable.com` | 13473    |
  | **Mainnet** | `https://rpc.immutable.com`         | 13371    |

  The deployer account must have sufficient IMX for gas fees. Get testnet IMX from the [Immutable Testnet Faucet](https://portal.immutable.com/faucet).
</Info>

### Environment Variables

Create a `.env` file in your project root:

```bash theme={null}
PRIVATE_KEY=your_wallet_private_key_here
```

<Warning>
  **Never commit `.env` files to version control.** Add `.env` and `.env.local` to your `.gitignore` file to prevent accidentally exposing private keys or API secrets.
</Warning>

## Add Contract

After installing the preset contract library (`npm install @imtbl/contracts`), create a `contracts` directory and add your contract file.

<Tip>
  **Solidity Version**: The preset contracts use Solidity `0.8.19`. Ensure your `hardhat.config.ts` has `version: "0.8.19"` in the `solidity` section.
</Tip>

**Operator Allowlist Addresses** (required constructor parameter):

| Network     | Operator Allowlist Address                   |
| ----------- | -------------------------------------------- |
| **Testnet** | `0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE` |
| **Mainnet** | `0x1B16F1Da2E5DF531512E15F68c86ac0A7C2a6929` |

The Operator Allowlist restricts NFT transfers to approved marketplaces. For details on how it works and how to manage the allowlist, see [Operator Allowlist](/docs/products/asset-contracts/operator-allowlist).

<Tabs>
  <Tab title="ImmutableERC721 Preset">
    Create `contracts/MyERC721.sol`:

    ```solidity theme={null}
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.19;

    import "@imtbl/contracts/contracts/token/erc721/preset/ImmutableERC721.sol";

    contract MyERC721 is ImmutableERC721 {
        constructor(
            address owner_,
            string memory name_,
            string memory symbol_,
            string memory baseURI_,
            string memory contractURI_,
            address operatorAllowlist_,
            address royaltyReceiver_,
            uint96 feeNumerator_
        )
            ImmutableERC721(
                owner_,
                name_,
                symbol_,
                baseURI_,
                contractURI_,
                operatorAllowlist_,
                royaltyReceiver_,
                feeNumerator_
            )
        {}
    }
    ```
  </Tab>

  <Tab title="ImmutableERC1155 Preset">
    Create `contracts/MyERC1155.sol`:

    ```solidity theme={null}
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.19;

    import "@imtbl/contracts/contracts/token/erc1155/preset/ImmutableERC1155.sol";

    contract MyERC1155 is ImmutableERC1155 {
        constructor(
            address owner_,
            string memory name_,
            string memory baseURI_,
            string memory contractURI_,
            address operatorAllowlist_,
            address royaltyReceiver_,
            uint96 feeNumerator_
        )
            ImmutableERC1155(
                owner_,
                name_,
                baseURI_,
                contractURI_,
                operatorAllowlist_,
                royaltyReceiver_,
                feeNumerator_
            )
        {}
    }
    ```
  </Tab>
</Tabs>

## Compile

Compile your contracts to generate artifacts and type definitions:

```bash theme={null}
npx hardhat compile
```

**Output:**

```
Compiling...
Compiled 1 contract successfully
```

This generates:

* Contract artifacts in `artifacts/`
* ABI files for contract interaction

## Test

Write tests in the `test/` directory using Hardhat's testing framework (Mocha and Chai):

```typescript theme={null}
import { expect } from "chai";
import { ethers } from "hardhat";

describe("MyERC721", function () {
  it("Should deploy with correct name and symbol", async function () {
    const [owner] = await ethers.getSigners();

    const MyERC721 = await ethers.getContractFactory("MyERC721");
    const contract = await MyERC721.deploy(
      owner.address,
      "My Collection",
      "MYC",
      "https://example.com/metadata/",
      "https://example.com/collection.json",
      "0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE", // Testnet allowlist
      owner.address,
      200 // 2% royalty
    );

    await contract.waitForDeployment();

    expect(await contract.name()).to.equal("My Collection");
    expect(await contract.symbol()).to.equal("MYC");
  });
});
```

Run tests:

```bash theme={null}
npx hardhat test
```

<Info>
  Hardhat automatically compiles contracts before running tests, so `npx hardhat compile` is not required before testing.
</Info>

## Deploy

### Write Deployment Script

**Important**: Immutable zkEVM requires specific gas configuration. Always include transaction overrides with `maxFeePerGas`, `maxPriorityFeePerGas`, and `gasLimit`.

Create `scripts/deploy.ts`:

<Tabs>
  <Tab title="Deploy ERC-721 Preset">
    ```typescript theme={null}
    import { ethers } from "hardhat";

    async function main() {
      const [deployer] = await ethers.getSigners();
      console.log("Deploying contracts with account:", deployer.address);
      console.log("Account balance:", (await ethers.provider.getBalance(deployer.address)).toString());

      const MyERC721 = await ethers.getContractFactory("MyERC721");

      const contract = await MyERC721.connect(deployer).deploy(
        deployer.address,                           // owner
        "My Collection",                            // name
        "MYC",                                      // symbol
        "https://example.com/metadata/",            // baseURI
        "https://example.com/collection.json",      // contractURI
        "0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE", // operatorAllowlist (testnet)
        deployer.address,                           // royaltyReceiver
        200,                                        // feeNumerator (2%)
        {
          maxPriorityFeePerGas: 10e9, // 10 gwei
          maxFeePerGas: 15e9, // 15 gwei
          gasLimit: 200000, // Set an appropriate gas limit for your transaction
        }
      );

      await contract.waitForDeployment();
      const address = await contract.getAddress();

      console.log("Contract deployed to:", address);
      console.log("Transaction hash:", contract.deploymentTransaction()?.hash);
    }

    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    ```
  </Tab>

  <Tab title="Deploy ERC-1155 Preset">
    ```typescript theme={null}
    import { ethers } from "hardhat";

    async function main() {
      const [deployer] = await ethers.getSigners();
      console.log("Deploying contracts with account:", deployer.address);
      console.log("Account balance:", (await ethers.provider.getBalance(deployer.address)).toString());

      const MyERC1155 = await ethers.getContractFactory("MyERC1155");

      const contract = await MyERC1155.connect(deployer).deploy(
        deployer.address,                           // owner
        "My Collection",                            // name
        "https://example.com/metadata/",            // baseURI
        "https://example.com/collection.json",      // contractURI
        "0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE", // operatorAllowlist (testnet)
        deployer.address,                           // royaltyReceiver
        200,                                        // feeNumerator (2%)
        {
          maxPriorityFeePerGas: 10e9, // 10 gwei
          maxFeePerGas: 15e9, // 15 gwei
          gasLimit: 200000, // Set an appropriate gas limit for your transaction
        }
      );

      await contract.waitForDeployment();
      const address = await contract.getAddress();

      console.log("Contract deployed to:", address);
      console.log("Transaction hash:", contract.deploymentTransaction()?.hash);
    }

    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    ```
  </Tab>
</Tabs>

<Info>
  **Constructor Parameters:**

  * `owner`: Address with admin rights (typically deployer)
  * `name`: Collection name (e.g., "My NFT Collection")
  * `symbol`: Token symbol for ERC-721 (e.g., "MYC")
  * `baseURI`: Base URI for token metadata (e.g., "[https://api.example.com/metadata/](https://api.example.com/metadata/)")
  * `contractURI`: Collection-level metadata URI
  * `operatorAllowlist`: Address of the Operator Allowlist contract (see table above)
  * `royaltyReceiver`: Address that receives royalty payments
  * `feeNumerator`: Royalty percentage in basis points (200 = 2%, 1000 = 10%)
</Info>

### Deploy to Testnet

Deploy your contract to Immutable zkEVM testnet:

```bash theme={null}
npx hardhat run scripts/deploy.ts --network immutableZkevmTestnet
```

**Expected output:**

```
Deploying contracts with account: 0x1234...
Account balance: 1000000000000000000
Contract deployed to: 0x96dBDB46eCeEFd7082AE6461A83A6f08C8F5cd1C
Transaction hash: 0x5678...
```

Save the deployed contract address for the next steps.

### Deploy to Mainnet

<Warning>
  **Production Deployment**: Test thoroughly on testnet before deploying to mainnet. Ensure:

  * Contract has been audited if using custom logic
  * Deployer account has sufficient IMX for gas fees
  * All parameters (metadata URIs, allowlist address, royalty settings) are correct
  * **Mainnet Operator Allowlist address**: `0x1B16F1Da2E5DF531512E15F68c86ac0A7C2a6929`
</Warning>

```bash theme={null}
npx hardhat run scripts/deploy.ts --network immutableZkevmMainnet
```

## Gas Pricing

Immutable zkEVM enforces a minimum gas price of 10 gwei to protect against spam traffic and ensure efficient transaction processing.

### Minimum Requirements

* **Minimum Gas Price**: Transactions with a tip cap below 10 gwei are rejected by the RPC
* **Fee Cap**: Must be greater than or equal to 10 gwei
* **Base Fee**: Transactions only mine when the gas fee cap is greater than or equal to the base fee

The base fee is generally expected to stay below the minimum gas tip cap.

### Recommended Gas Configuration

When deploying contracts, use the following gas settings:

```typescript theme={null}
{
  maxPriorityFeePerGas: 10e9, // 10 gwei (minimum)
  maxFeePerGas: 15e9, // 15 gwei
  gasLimit: 200000, // Set an appropriate gas limit for your transaction
}
```

**Gas Parameters:**

* `maxPriorityFeePerGas`: Priority fee (tip) you're willing to pay to miners (minimum 10 gwei)
* `maxFeePerGas`: Maximum total fee per gas unit (base fee + priority fee)
* `gasLimit`: Maximum gas units the transaction can consume (set higher for contract deployments)

<Tip>
  For advanced gas price optimization, use the [Ethereum RPC specification](https://ethereum.github.io/execution-apis/api-documentation/) `eth_feeHistory` method to analyze recent gas prices and adjust accordingly.
</Tip>

## Troubleshooting

### UNPREDICTABLE\_GAS\_LIMIT Error

**Error message:**

```bash theme={null}
reason: 'cannot estimate gas; transaction may fail or may require manual gas limit',
code: 'UNPREDICTABLE_GAS_LIMIT',
error: Error: gas required exceeds allowance or always failing transaction
```

**Causes:**

1. **Incorrect constructor arguments**: Verify all parameters are correct (especially addresses)
2. **Insufficient gas limit**: The default limit may be too low for complex contracts
3. **Contract logic error**: The contract may revert due to a bug

**Solutions:**

1. **Verify constructor arguments**: Double-check all addresses, strings, and numbers
2. **Estimate and increase gas limit**: First estimate the required gas, then set `gasLimit` accordingly:
   ```typescript theme={null}
   // Estimate gas to determine required limit
   const estimatedGas = await MyERC721.getDeployTransaction(...constructorArgs).then(tx =>
     ethers.provider.estimateGas(tx)
   );
   console.log("Estimated gas:", estimatedGas.toString());

   // Set gasLimit higher than estimate (add ~20% buffer)
   const gasOverrides = {
     maxPriorityFeePerGas: 10e9, // 10 gwei
     maxFeePerGas: 15e9, // 15 gwei
     gasLimit: Math.ceil(Number(estimatedGas) * 1.2),
   };
   ```
3. **Test locally**: Run `npx hardhat test` to catch errors before deployment

## Link Contract to Immutable Hub

After deployment, link your contract via [Immutable Hub](https://hub.immutable.com) to enable metadata management, analytics, and Minting API support.

## Minting API Prerequisites

If your contract will use the [Minting API](/docs/products/asset-contracts/minting-api) for programmatic minting, grant the Minting API the required `MINTER_ROLE`:

```typescript theme={null}
// Get the Minting API address from Hub (under Contract > Minting API section)
const MINTING_API_ADDRESS = "0x..."; // From Hub

// Grant minter role
const MINTER_ROLE = await contract.MINTER_ROLE();
await contract.grantRole(MINTER_ROLE, MINTING_API_ADDRESS);

console.log("Minter role granted to Minting API");
```

<Warning>
  **Preset Compatibility**: The Minting API has been rigorously tested with Immutable's preset contracts. If using custom contracts, thoroughly test compatibility. Immutable provides no compatibility guarantees for custom implementations.
</Warning>

See the [Minting API documentation](/docs/products/asset-contracts/minting-api) for complete setup instructions.

## Contract Verification

Verify your contract on Immutable Explorer to receive a green checkmark indicating legitimacy.

<Steps>
  <Step title="Navigate to contract on Explorer">
    Go to your contract on [Immutable Explorer](https://explorer.immutable.com):

    * **Testnet**: `https://explorer.testnet.immutable.com/address/YOUR_CONTRACT_ADDRESS`
    * **Mainnet**: `https://explorer.immutable.com/address/YOUR_CONTRACT_ADDRESS`
  </Step>

  <Step title="Click 'Verify & Publish'">
    Select the verification method (via source code or compiler settings).
  </Step>

  <Step title="Submit source code and compiler settings">
    Provide your contract source code and match the compiler settings from your `hardhat.config.ts`.
  </Step>

  <Step title="Receive verification badge">
    Once verified, your contract displays a green checkmark and users can read the source code directly on Explorer.
  </Step>
</Steps>

<Tip>
  Verified contracts allow users to interact with contract functions directly through the Explorer UI, improving transparency and trust.
</Tip>

## Next Steps

<CardGroup cols={2}>
  <Card title="Interact with Contracts" icon="code" href="/docs/products/passport/wallet#send-transactions">
    Send transactions and interact with your deployed contracts using Passport
  </Card>

  <Card title="Minting API" icon="sparkles" href="/docs/products/asset-contracts/minting-api">
    Mint NFTs programmatically using Immutable's Minting API
  </Card>

  <Card title="Manage Metadata" icon="image" href="/docs/products/asset-contracts/erc721#metadata">
    Configure token and collection metadata for your NFTs
  </Card>

  <Card title="Royalties" icon="coins" href="/docs/products/asset-contracts/royalties">
    Understand how royalties work and how to configure them
  </Card>
</CardGroup>
