⚠️ This documentation covers the Legacy, Closed-Beta of NVNM Chain L2, powered by MANTRA Chain. For the *NEW* NVNM Chain documentation, please visit https://docs.nvnmchain.io.

Reference

Using Precompiles

Practical guide to using precompiles in EVM contracts

Overview

Precompiles allow EVM contracts to interact directly with native chain modules. Precompiles are special contract addresses that execute native Go code instead of EVM bytecode.

All Standard EVM Precompiles

MANTRA Chain supports all standard EVM precompiles that are available on Ethereum and other EVM-compatible chains. These precompiles work identically to how they work on Ethereum, Base, Polygon, Arbitrum, and other EVM chains.

Standard Ethereum Precompiles

All of the following standard EVM precompiles are available and fully functional:

  1. ECRecover (0x01) - Elliptic curve signature recovery
  2. SHA256 (0x02) - SHA-256 hash function
  3. RIPEMD160 (0x03) - RIPEMD-160 hash function
  4. Identity (0x04) - Data copying function
  5. ModExp (0x05) - Modular exponentiation
  6. BN256Add (0x06) - Elliptic curve addition on the BN256 curve
  7. BN256Mul (0x07) - Elliptic curve scalar multiplication on the BN256 curve
  8. BN256Pairing (0x08) - Bilinear pairing on the BN256 curve
  9. Bls12-381 (0x09-0x0F) - BLS12-381 curve operations

These precompiles behave exactly as they do on Ethereum and other EVM chains, ensuring full compatibility with existing EVM contracts and libraries.

Additional Cosmos SDK Precompiles

In addition to all standard EVM precompiles, NVNM Chain exposes additional stateful precompiles that let developers interact with selected Cosmos SDK modules and NVNM-specific functionality from Solidity.

The EVM communicates with the Cosmos side of the chain through these precompiles - native Go code that directly interacts with module state (for example, the Bank module).

Active NVNM Precompiles

The following 5 stateful precompiles are currently active on NVNM Chain:

PrecompileAddressWhat it does
ICS20 (IBC Transfer)0x0000000000000000000000000000000000000802Initiate IBC token transfers from EVM contracts
Bank0x0000000000000000000000000000000000000804Send native tokens and query balances
Governance0x0000000000000000000000000000000000000805Submit and vote on governance proposals
Slashing0x0000000000000000000000000000000000000806Query slashing-related state from the EVM
Anchoring0x0000000000000000000000000000000000000A00Create and manage on-chain anchoring records

Note

Staking (0x800) and distribution (0x801) precompiles are not available on NVNM Chain. Those operations remain on the MANTRA L1 provider chain.

ERC20 Precompiles

Each TokenFactory token has its own ERC20 precompile address, allowing:

  • Control token balance: Manage token balances from EVM
  • Transfer tokens: Transfer TokenFactory tokens
  • Use tokens in EVM contracts: Seamless integration

This enables TokenFactory tokens (created via Cosmos SDK) to be used directly in EVM contracts without wrapping.

Precompile Addresses

The following precompile addresses are fixed on NVNM Chain and can be used directly in your Solidity contracts:

// NVNM Stateful Precompile Addresses
address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802;
address constant BANK_PRECOMPILE = 0x0000000000000000000000000000000000000804;
address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805;
address constant SLASHING_PRECOMPILE = 0x0000000000000000000000000000000000000806;
address constant ANCHORING_PRECOMPILE = 0x0000000000000000000000000000000000000A00;

Note

TokenFactory ERC20 precompile addresses are deterministic based on the token denom. See the TokenFactory Denoms page for available tokens.

Using Precompiles

Basic Pattern

Precompiles are called like regular contract calls:

// Call a precompile
IPrecompileInterface(precompileAddress).functionName(params);

Note

If your application needs staking or reward-distribution flows, call those on the MANTRA L1 provider chain rather than through an NVNM precompile.

Example: Bank Precompile

interface IBankPrecompile {
    function send(
        address to,
        string memory denom,
        uint256 amount
    ) external returns (bool);
    
    function balanceOf(
        address account,
        string memory denom
    ) external view returns (uint256);
}

contract PaymentContract {
    address constant BANK_PRECOMPILE = 0x0000000000000000000000000000000000000804;
    
    function sendNativeToken(
        address to,
        string memory denom,
        uint256 amount
    ) external {
        IBankPrecompile(BANK_PRECOMPILE).send(to, denom, amount);
    }
    
    function getBalance(
        address account,
        string memory denom
    ) external view returns (uint256) {
        return IBankPrecompile(BANK_PRECOMPILE).balanceOf(account, denom);
    }
}

Example: Governance Precompile

interface IGovernancePrecompile {
    function vote(
        uint256 proposalId,
        uint8 option
    ) external returns (bool);
    
    function createProposal(
        string memory title,
        string memory description,
        // ... other proposal parameters
    ) external returns (uint256);
}

contract GovernanceContract {
    address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805;
    
    function voteOnProposal(uint256 proposalId, uint8 option) external {
        IGovernancePrecompile(GOVERNANCE_PRECOMPILE).vote(proposalId, option);
    }
}

TokenFactory ERC20 Precompiles

TokenFactory tokens automatically get ERC20 precompile addresses:

interface ITokenFactoryERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
}

contract TokenContract {
    // Precompile address for a specific TokenFactory token.
    // This address is deterministic based on the token denom.
    address immutable tokenPrecompile;

    constructor(address tokenPrecompile_) {
        tokenPrecompile = tokenPrecompile_;
    }
    
    function transferToken(address to, uint256 amount) external {
        ITokenFactoryERC20(tokenPrecompile).transfer(to, amount);
    }
}

Best Practices

Error Handling

Always check return values from precompiles:

function safeDelegate(address validator, uint256 amount) external {
    bool success = IStakingPrecompile(STAKING_PRECOMPILE).delegate(validator, amount);
    require(success, "Delegation failed");
}

Gas Considerations

Precompiles execute native Go code, which is generally more gas-efficient than contract calls, but still consider:

  • Gas costs for precompile calls
  • Batch operations when possible
  • Optimize for frequently called functions

Security

  • Validate inputs before calling precompiles
  • Use access controls for precompile calls
  • Test precompile interactions thoroughly

Next Steps