@kadena/hardhat-chainweb
TypeScript icon, indicating that this package has built-in type declarations

1.4.5 • Public • Published

Hardhat Kadena chainweb Plugin

@kadena/hardhat-chainweb is a Hardhat plugin that allows Ethereum developers to create a Kadena Chainweb EVM network, switch between chains, and request SPV proofs.

What is Chainweb?

Chainweb is a blockchain architecture designed by Kadena, which features a parallelized Proof-of-Work (PoW) system. Instead of a single blockchain, Chainweb consists of multiple chains running in parallel, each processing transactions independently while being cryptographically linked to one another. This allows it to achieve high scalability and security without sacrificing decentralization.

Features

  • 🔗 Multi-chain network simulation
  • 🔄 Cross-chain transaction support
  • ✅ SPV proof generation and verification
  • 🛠 Compatible with standard Hardhat workflows
  • 🔌 Support for both in-process and external networks
  • 📡 RPC server with HTTP and WebSocket support
  • 🔘 Multiple chainweb configuration support
  • 🔀 Support forks

Installation

npm install @kadena/hardhat-chainweb
# or
yarn add @kadena/hardhat-chainweb
# or
pnpm install @kadena/hardhat-chainweb

Quick Start

  1. Import the plugin in your hardhat.config.ts (or .js):
import '@kadena/hardhat-chainweb';
  1. Add basic configuration:
module.exports = {
  solidity: '0.8.20',
  chainweb: {
    hardhat: {
      chains: 3, // Creates a 3-chain network
    },
  },
};
  1. Run your tests:
npx hardhat test
  1. Run chainweb node
npx hardhat node
  1. Run your script against localhost
npx hardhat run ./scripts/my-scripts.js --chainweb localhost

Configuration Guide

interface HardhatChainwebUserConfig extends HardhatUserConfig {
  chainweb: {
    hardhat: ChainwebUserConfig;
    [chainwebName: string]: ChainwebUserConfig;
  };
  defaultChainweb: string
}

interface ChainwebUserConfig {
  chains: number;                          // Number of chains (2, 3, 10, or 20 for auto-graph)
  graph?: Record<number, number[]>;        // Custom chain connection graph
  type?: 'in-process' | 'external';        // Network type (default: 'in-process')
  chainIdOffset?: number;                  // Base chain ID (default: 626000)
  logging?: 'none' | 'info' | 'debug';     // Logging level
  accounts?: HardhatNetworkAccountsConfig; // Account configuration
  externalHostUrl?: string;                // For external networks
  precompiles?: {                          // Custom precompile addresses
    chainwebChainId?: string;              // chainweb chainId precompile address
    spvVerify?: string;                    // verify spv proof precompile address
  };
  networkOptions?:                         // override default values that created networks use
    | HardhatNetworkUserConfig
    | HttpNetworkUserConfig
}
Property Type Description
accounts HardhatNetworkAccountsConfig (optional) Defines the accounts configuration for the network (default: Hardhat network accounts).
chains number Specifies the number of chains in the Chainweb network.
graph { [key: number]: number[] } (optional) Defines the graph structure of the Chainweb network where keys represent chain IDs and values are arrays of connected chain IDs (default: Pearson graph).
type 'in-process' | 'external' (optional) Defines Chainweb type: “in-process” uses the Hardhat network, and “external” uses an external network (which you need to add to the networks—default: 'in-process').
externalHostUrl string (optional) Defines the base url for external networks (default: http://localhost:8545)
logging 'none' | 'info' | 'debug' (optional) Sets the logging level for debugging purposes (default: "info").
chainIdOffset number (optional) chain id offset to be set (default: 626000).
precompiles { chainwebChainId?: string, spvVerify?: string } (optional) if you are using external networks the precompile addresses might be different from the default ones so you can set them via this config
networkOptions HardhatNetworkUserConfig | HttpNetworkUserConfig (optional) You can override any option that hardhat adds by default for the created networks. check the examples

Network Types

1. In-Process Network (Default)

Best for development and testing. Uses Hardhat's built-in network.

module.exports = {
  solidity: '0.8.28',
  chainweb: {
    hardhat: {
      chains: 3,
      type: 'in-process', // this is the default value and can be skipped
    },
  },
};

2. External Network

For connecting to existing Chainweb networks.

module.exports = {
  solidity: '0.8.28',
  chainweb: {
    // the external chainweb name can be anything except 'localhost' or `hardhat`
    testnet: {
      type: 'external',
      chains: 2,
      externalHostUrl: 'http://testnet.your-domain.org',
      precompiles: {
        chainwebChainId: '0x0000000000000000000000000000000000000100',
        spvVerify: '0x0000000000000000000000000000000000000101',
      },
    },
  },
};

Setting the Default Chainweb

You can set the default chainweb by adding defaultChainweb to the hardhat config file. (default value is hardhat)

Example of Setting Default Chainweb

module.exports = {
  chainweb: {
    hardhat: {
      chains: 3, // Number of chains in the Chainweb network
    },
    // this is using 20 in-process chains
    myChainweb: {
      chains: 20,
    },
  },
  defaultChainweb: 'myChainweb',
};

Chain Graph Configuration

The chain graph defines how chains are connected. For standard configurations (2, 3, 10, or 20 chains), the plugin automatically generates an optimal Pearson graph. For custom configurations, you must specify the graph manually.

module.exports = {
  chainweb: {
    // this config uses 4 in-process chains.
    custom_graph: {
      chains: 4,
      graph: {
        0: [1, 2], // Chain 0 connects to chains 1 and 2
        1: [0, 3], // Chain 1 connects to chains 0 and 3
        2: [0, 3], // Chain 2 connects to chains 0 and 3
        3: [1, 2], // Chain 3 connects to chains 1 and 2
      },
    },
  },
};

Chain IDs and Networking

The plugin uses the Chainweb configuration and extends the Hardhat config by adding networks to it.

  • Each chain gets a unique chain ID: chainIdOffset + chainIndex
  • Default offset is 626000 (e.g., Chain 0 = 626000, Chain 1 = 626001)
  • Network names follow the pattern: chainweb_${networkName}${chainIndex}

Override Network Configurations

Override the config for all chains created for a chainweb

If you want to override any option, you can use networkOptions the custom network config

module.exports = {
  solidity: '0.8.20',
  chainweb: {
    hardhat: {
      chains: 2,
      // the two created networks use the following config
      networkOptions: {
        gasPrice: 0.1,
        allowUnlimitedContractSize: true,
        forking: {
          url: 'http://the-fork-url',
        },
      },
    },
  },
};

Override the config a specific chain of the chainweb

You also can override the config for spesific chain you just need to add the config in networks section

module.exports = {
  solidity: '0.8.20',
  networks:{
    // wh you want only the chain 0 of chainweb uses the following config
    chainweb_hardhat0:{
      gasPrice: 0.1,
      allowUnlimitedContractSize: true,
      forking: {
        url: 'http://the-fork-url',
      },
    }
  }
  chainweb: {
    hardhat: {
      chains: 2,
    },
  },
};

API Reference

Hardhat Runtime Environment Extensions

The plugin adds a chainweb property to the Hardhat Runtime Environment (HRE):

interface ChainwebPluginApi {
  getProvider: (cid: number) => HardhatEthersProvider;
  requestSpvProof: (targetChain: number, origin: Origin) => Promise<string>;
  switchChain: (cid: number) => Promise<void>;
  getChainIds: () => number[];
  callChainIdContract: () => Promise<number>;
  deployContractOnChains: ({
    name: string;
    signer?: Signer;
    factoryOptions?: FactoryOptions;
    constructorArgs?: ContractMethodArgs;
    overrides?: Overrides;
  }) => Promise<{
    deployments: {
      // ContractFactory from ethers
      contract: ReturnType<ContractFactory['deploy']>;
      address: string;
      chain: number;
      network: {
        name: string;
      };
    };
  }>;
  createTamperedProof: (targetChain: number, origin: Origin) => Promise<string>;
  computeOriginHash: (origin: Origin) => string;
  runOverChains: <T>(callback: (chainId: number) => Promise<T>) => Promise<T[]>;
  initialize: () => void;
}

Origin type

Origin type for spv proof creation

interface Origin {
  chain: bigint;
  originContractAddress: string;
  height: bigint;
  txIdx: bigint;
  eventIdx: bigint;
}
Function Parameters Return Type Description
getProvider cid: number HardhatEthersProvider Retrieves the provider for a specified chain.
requestSpvProof targetChain: number, origin: Origin Promise<string> Requests an SPV proof for a cross-chain transaction.
switchChain cid: number Promise<void> Switches the active chain.
getChainIds None number[] Returns an array of available chain IDs.
callChainIdContract None Promise<number> Calls a contract to get the chain ID.
deployContractOnChains {name: string;signer?: Signer;factoryOptions?: FactoryOptions; constructorArgs?: ContractMethodArgs ;overrides?: Overrides;} checkdeployContractOnChainsofChainwebPluginApi Deploys a contract on multiple chains.
createTamperedProof targetChain: number, origin: Origin Promise<string> Creates a tampered SPV proof for testing purposes.
computeOriginHash origin: Origin string Computes the hash of a transaction origin.
runOverChains callback: (chainId: number) => Promise<T> Promise<T[]> Run the callback for all chains; the function switches the context so no need to call switchChain inside the callback
initialize None void This function is called internally when usingnode, test, run command, so you mostly dont need it, However if you need to use the plugin in other command (e.g developing another plugin on top of this ) then you can call the function

Example

import { chainweb } from "hardhat"

await chainweb.deployContractOnChains({name: "SimpleToken",  constructorArgs: [ethers.parseUnits('1000000')] }) // deploy contract on all chains
await chainweb.switchChain(0); // configure hardhat to use chain 0

Precompiles

The in-process chainweb comes with two precompiles that the addresses are accessible via hre.config.chainweb[config_name].precompiles.

CHAIN_ID_PRECOMPILE

the precompile that returns the chain index.

address: address(uint160(uint256(keccak256('/Chainweb/Chain/Id/'))))

Example of getting chainwebChainId

address public constant CHAIN_ID_PRECOMPILE =
  address(uint160(uint256(keccak256('/Chainweb/Chain/Id/'))));

function getChainwebChainId() public view returns (uint32 cid) {
  (bool success, bytes memory c) = CHAIN_ID_PRECOMPILE.staticcall('');
  require(success, ChainwebChainIdRetrievalFailed());
  require(c.length == 4, InvalidChainwebChainId());
  cid = uint32(bytes4(c));
}

VALIDATE_PROOF_PRECOMPILE

the precompile that verifys a spv proof;

address: address(uint160(uint256(keccak256('/Chainweb/KIP-34/VERIFY/SVP/'))))

Example of getting spvProof

address public constant VALIDATE_PROOF_PRECOMPILE =
    address(uint160(uint256(keccak256('/Chainweb/KIP-34/VERIFY/SVP/'))));
...

function verifySPV(
  bytes memory proof
)
  public
  view
  returns (CrossChainMessage memory crossChainMessage, bytes32 originHash)
{
  (bool success, bytes memory data) = VALIDATE_PROOF_PRECOMPILE.staticcall(
    proof
  );
  require(success, SPVVerificationFailed());
  crossChainMessage = abi.decode(data, (CrossChainMessage));
  originHash = keccak256(abi.encode(crossChainMessage.origin));
  require(!completed[originHash], AlreadyCompleted(originHash));
};

Overloading hardhat-switch-network

This plugin overrides switchNetwork from hardhat-switch-network to load the correct Chainweb provider while also supporting switching by chain index. For example, switchNetwork(1) switches to chain 1 of Chainweb.

RPC Server

You can run Chainweb as a single node using the hardhat node command. This will spin up the chains based on the Chainweb configuration and expose both http and ws ports with URLs following this pattern. You can also pass standard Hardhat configuration options like hostname and port to change the default values. Note that fork is not supported yet.

Chain URLs

http://127.0.0.1:8545/chain/:chainIndex

Example for multiple chains:

http://127.0.0.1:8545/chain/0

http://127.0.0.1:8545/chain/1

SPV Proof Requests

The server also accepts requests for SPV proofs with the following URL pattern:

GET /:externalHostUrl/chain/:targetChain/spv/chain/:sourceChain/height/:height/transaction/:txIdx/event/:eventIdx

check the Origin interface for the details

Example SPV proof request:

http://127.0.0.1:8545/chain/1/spv/chain/0/height/1234/transaction/16666/event/1123123123

Tasks

you can use the following command with chainweb which override the defaultChainweb

hardhat node

npx hardhat node --chainweb CHAINWEB_NAME

hardhat run

npx hardhat run ./scripts/my-script.js --chainweb CHAINWEB_NAME

hardhat test

npx hardhat test --chainweb CHAINWEB_NAME

hardhat print-config

Print the final configuration; this is useful for debugging since the plugin adds the networks to the final config

npx hardhat print-config
# the complete hardhat config as JSON

Examples

FAQ

Why Adding Custom Chainwebs

You likely add external chainwebs for targeting different networks. like one for tesnet, mainnet or devnet and etc

module.exports = {
  solidity: '0.8.28',
  chainweb: {
    // testing external dev-net
    devnet: {
      type: 'external',
      chains: 20,
      externalHostUrl: 'http://localhost:1234',
    },
    // this network is not available yet
    testnet: {
      type: 'external',
      chains: 20,
      externalHostUrl: 'http://testnet.kadena.io',
      // if the network uses different precompiles address
      precompiles: {
        chainwebChainId: '0x0000000000000000000000000000000000000100',
        spvVerify: '0x0000000000000000000000000000000000000101',
      },
    },
  },
};

You can use several "in-process" chainweb config for testing your smart contract behavior against different chain numbers or graphs or any other configuration

module.exports = {
  solidity: '0.8.28',
  chainweb: {
    // use 2 chains for regular development so tests run fast
    hardhat: {
      chains: 2,
    },
    // use 5 chains with a custom graph that is close to testnet
    semiTestnet: {
      chains: 20,
    },
    // use 3 chains with a custom graph
    nonStandardGraphChainweb: {
      chains: 3,
      graph: {
        0: [1],
        1: [2],
        2: [0],
      },
    },
  },
};

Research Graph behaviors

Graph has direct impact on the security and performance of the chainweb is also might change the SPV proof creation and verification time, so you can use this option for more advanced scenarios.

module.exports = {
  chainweb: {
    // this config uses 4 in-process chains.
    custom_graph_chainweb: {
      chains: 4,
      // using a custom graph to see the network behavior
      graph: {
        0: [1],
        1: [0, 2, 3],
        2: [0],
        3: [1, 2, 0],
      },
    },
  },
};

How to use advance hardhat configs

All in-process networks inherit the default hardhat network configuration so if for example you want to set allowUnlimitedContractSize you just need to add it to the config

module.exports = {
  solidity: '0.8.28',
  networks: {
    // then both hardhat and semiTestnet chainweb uses this config since both are internal
    hardhat: {
      gasPrice : 0.1
    }
  }
  chainweb: {
    hardhat: {
      chains: 2,
    },
    semiTestnet: {
      chains: 20,
    },
  },
};

What is the difference between in-process and external Chainweb types?

  • in-process: Runs Chainweb as part of Hardhat's local network, ideal for development and testing.
  • external: Connects to an existing Chainweb network (e.g., testnet or mainnet) using an external RPC.

How do I switch between different Chainwebs?

Use the --chainweb flag in your Hardhat commands:

npx hardhat test --chainweb devnet

Or manually set the defaultChainweb in your config.

How do I deploy a contract to all chains in my Chainweb setup?

Use the deployContractOnChains function:

await chainweb.deployContractOnChains('SimpleToken');

This will deploy the contract to all available chains.

How can I request an SPV proof for a cross-chain transaction?

Use the requestSpvProof function:

const proof = await chainweb.requestSpvProof(targetChain, origin);

Can I customize the Chainweb chain connection graph?

Yes, you can define a custom graph in your configuration:

chainweb: {
  customGraphChainweb: {
    chains: 4,
    graph: {
      0: [1, 2],
      1: [0, 3],
      2: [0, 3],
      3: [1, 2],
    },
  },
}

What happens if I don’t specify a graph configuration?

The plugin will automatically generate an optimal Pearson graph for standard configurations (2, 3, 10, or 20 chains).

How do I override Hardhat network settings for specific chain?

You can define custom network configurations in networks:

networks: {
  // use different chain id anf gas price for chain 0 of hardhat chainweb
  chainweb_hardhat0: {
    chainId: 123,
    gasPrice: 0.1,
  },
}

Does this plugin support forking an existing network?

You can use forking either via networkOptions property in the chianweb config or passing --fork to the command support it

npx hardhat node --frok https://THE_FORK_URL

using config

chainweb: {
  customGraphChainweb: {
    chains: 4,
    networkOptions: {
      forking:{
        url: "https://THE_FORK_URL"
      }
    }
  },
}

Does this plugin support deploying oversized contracts?

Yes by adding allowUnlimitedContractSize to the networkOptions

chainweb: {
  customGraphChainweb: {
    chains: 4,
    networkOptions: {
      allowUnlimitedContractSize: true
    }
  },
}

License

This project is licensed under the MIT License.


Package Sidebar

Install

npm i @kadena/hardhat-chainweb

Weekly Downloads

215

Version

1.4.5

License

MIT

Unpacked Size

317 kB

Total Files

98

Last publish

Collaborators

  • heekyun
  • bhuijgen
  • randynamic
  • andy_kadena
  • alber70g
  • javadkh2