Skip to main content

Customize a Subnet

All subnets can be customized by utilizing Subnet Configs.

And a subnet created by or forked from Subnet-EVM can be further customized by utilizing one or more of the following methods:

  • Genesis
  • Precompile
  • Chain Configs

Subnet Configs

A subnet can customized by setting parameters for the following:

See here for more info.

Genesis

Each blockchain has some genesis state when it’s created. Each Virtual Machine defines the format and semantics of its genesis data.

The default genesis Subnet EVM provided below has some well defined parameters:

{
"config": {
"chainId": 43214,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"subnetEVMTimestamp": 0,
"feeConfig": {
"gasLimit": 8000000,
"minBaseFee": 25000000000,
"targetGas": 15000000,
"baseFeeChangeDenominator": 36,
"minBlockGasCost": 0,
"maxBlockGasCost": 1000000,
"targetBlockRate": 2,
"blockGasCostStep": 200000
},
"allowFeeRecipients": false
},
"alloc": {
"8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {
"balance": "0x295BE96E64066972000000"
}
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0x00",
"gasLimit": "0x7A1200",
"difficulty": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

Chain Config

chainID: Denotes the chainID of to be created chain. Must be picked carefully since a conflict with other chains can cause issues. One suggestion is to check with chainlist.org to avoid ID collision, reserve and publish your chain ID properly.

Hardforks

homesteadBlock, eip150Block, eip150Hash, eip155Block, byzantiumBlock, constantinopleBlock, petersburgBlock, istanbulBlock, muirGlacierBlock, subnetEVMTimestamp are hardfork activation times. Changing these may cause issues, so treat them carefully.

Fee Config

gasLimit: Sets the max amount of gas consumed per block.

targetBlockRate: Sets the target rate of block production in seconds. A target of 2 will target producing a block every 2 seconds. If the network starts producing faster than this, base fees are increased accordingly.

minBaseFee: Sets a lower bound on the EIP-1559 base fee of a block. Since the block's base fee sets the minimum gas price for any transaction included in that block, this effectively sets a minimum gas price for any transaction.

targetGas: Specifies the targeted amount of gas (including block gas cost) to consume within a rolling 10-seconds window. When the dynamic fee algorithm observes that network activity is above/below the targetGas, it increases/decreases the base fee proportionally to how far above/below the target actual network activity is. If the network starts producing blocks with gas cost higher than this, base fees are increased accordingly.

baseFeeChangeDenominator: Divides the difference between actual and target utilization to determine how much to increase/decrease the base fee. A larger denominator indicates a slower changing, stickier base fee, while a lower denominator allows the base fee to adjust more quickly.

minBlockGasCost: Sets the minimum amount of gas to charge for the production of a block.

maxBlockGasCost: Sets the maximum amount of gas to charge for the production of a block.

blockGasCostStep: Determines how much to increase/decrease the block gas cost depending on the amount of time elapsed since the previous block.

If the block is produced at the target rate, the block gas cost will stay the same as the block gas cost for the parent block.

If it is produced faster/slower, the block gas cost will be increased/decreased by the step value for each second faster/slower than the target block rate accordingly.

note

If the blockGasCostStep is set to a very large number, it effectively requires block production to go no faster than the targetBlockRate. For example, if a block is produced two seconds faster than the target block rate, the block gas cost will increase by 2 * blockGasCostStep.

Custom Fee Recipients

See section Setting a Custom Fee Recipient

Alloc

See section Setting the Genesis Allocation

The fields nonce, timestamp, extraData, gasLimit, difficulty, mixHash, coinbase, number, gasUsed, parentHash defines the genesis block header. The field gasLimit should be set to match the gasLimit set in the feeConfig. You do not need to change any of the other genesis header fields.

Examples

Another example of a genesis file can be found in the networks folder. Note: please remove airdropHash and airdropAmount fields if you want to start with it.

Here are a few examples on how a genesis file is used:

Setting the Genesis Allocation

Alloc defines addresses and their initial balances. This should be changed accordingly for each chain. If you don't provide any genesis allocation, you won't be able to interact with your new chain (all transactions require a fee to be paid from the sender's balance).

The alloc field expects key-value pairs. Keys of each entry must be a valid address. The balance field in the value can be either a hexadecimal or number to indicate initial balance of the address. The default value contains 8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC with 50000000000000000000000000 balance in it. Default:

  "alloc": {
"8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {
"balance": "0x295BE96E64066972000000"
}
}

To specify a different genesis allocation, populate the alloc field in the genesis JSON as follows:

  "alloc": {
"8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {
"balance": "0x52B7D2DCC80CD2E4000000"
},
"Ab5801a7D398351b8bE11C439e05C5B3259aeC9B": {
"balance": "0xa796504b1cb5a7c0000"
}
},

The keys in the allocation are hex addresses without the canonical 0x prefix. The balances are denominated in Wei (10^18 Wei = 1 Whole Unit of Native Token) and expressed as hex strings with the canonical 0x prefix. You can use this converter to translate between decimal and hex numbers.

The above example yields the following genesis allocations (denominated in whole units of the native token ie. 1 AVAX/1 WAGMI):

0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC: 100000000 (0x52B7D2DCC80CD2E4000000=100000000000000000000000000 Wei)
0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B: 49463 (0xa796504b1cb5a7c0000=49463000000000000000000 Wei)

Setting a Custom Fee Recipient

By default, all fees are burned (sent to the blackhole address with "allowFeeRecipients": false). However, it is possible to enable block producers to set a fee recipient (who will get compensated for blocks they produce).

To enable this feature, you'll need to add the following to your genesis file (under the "config" key):

{
"config": {
"allowFeeRecipients": true
}
}

Fee Recipient

With allowFeeRecipients enabled, your validators can specify their addresses to collect fees. They need to update their EVM chain config with the following to specify where the fee should be sent to.

{
"feeRecipient": "<YOUR 0x-ADDRESS>"
}
warning

If allowFeeRecipients feature is enabled on the subnet, but a validator doesn't specify a "feeRecipient", the fees will be burned in blocks it produces.

Precompiles

Subnet EVM can provide custom functionalities with precompiled contracts. These precompiled contracts can be activated through ChainConfig (in genesis or as an upgrade).

Restricting Smart Contract Deployers

If you'd like to restrict who has the ability to deploy contracts on your subnet, you can provide an AllowList configuration in your genesis file:

{
"config": {
"contractDeployerAllowListConfig": {
"blockTimestamp": 0,
"adminAddresses": ["0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"]
}
}
}

In this example, 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC is named as the Admin of the ContractDeployerAllowList. This enables it to add other Admins or to add Deployers. Both Admins and Deployers can deploy contracts. To provide a great UX with factory contracts, the tx.Origin is checked for being a valid deployer instead of the caller of CREATE. This means that factory contracts will still be able to create new contracts as long as the sender of the original transaction is an allow listed deployer.

The Stateful Precompile powering the ContractDeployerAllowList adheres to the following Solidity interface at 0x0200000000000000000000000000000000000000 (you can load this interface and interact directly in Remix):

// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface AllowListInterface {
// Set [addr] to have the admin role over the allow list
function setAdmin(address addr) external;

// Set [addr] to be enabled on the allow list
function setEnabled(address addr) external;

// Set [addr] to have no role over the allow list
function setNone(address addr) external;

// Read the status of [addr]
function readAllowList(address addr) external view returns (uint256);
}

If you attempt to add a Deployer and you are not an Admin, you will see something like: admin fail

If you attempt to deploy a contract but you are not an Admin not a Deployer, you will see something like: deploy fail

The allow list has three roles: None, Deployer, and Admin.

If you call readAllowList(addr) then you can read the current role of addr, which will return a uint256 with a value of 0, 1, or 2, corresponding to the roles None, Deployer, and Admin respectively.

WARNING: if you remove all of the admins from the allow list, it will no longer be possible to update the allow list without modifying the subnet-evm to schedule a network upgrade.

Restricting Who Can Submit Transactions

Similar to restricting contract deployers, this precompile restricts which addresses may submit transactions on chain. Like the previous section, you can activate the precompile by including an AllowList configuration in your genesis file:

{
"config": {
"txAllowListConfig": {
"blockTimestamp": 0,
"adminAddresses": ["0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"]
}
}
}

In this example, 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC is named as the Admin of the ContractDeployerAllowList. This enables them to add other Admins or to add Allowed. Both Admins and Allowed can submit transactions to the chain.

The Stateful Precompile powering the TxAllowList adheres to the following Solidity interface at 0x0200000000000000000000000000000000000002 (you can load this interface and interact directly in Remix):

// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface AllowListInterface {
// Set [addr] to have the admin role over the allow list
function setAdmin(address addr) external;

// Set [addr] to be enabled on the allow list
function setEnabled(address addr) external;

// Set [addr] to have no role over the allow list
function setNone(address addr) external;

// Read the status of [addr]
function readAllowList(address addr) external view returns (uint256);
}

If you attempt to add an Allowed and you are not an Admin, you will see something like: admin fail

If you attempt to submit a transaction but you are not an Admin or not Allowed, you will see something like: cannot issue transaction from non-allow listed address

The allow list has three roles: None, Allowed, and Admin.

If you call readAllowList(addr) then you can read the current role of addr, which will return a uint256 with a value of 0, 1, or 2, corresponding to the roles None, Allowed, and Admin respectively.

WARNING: if you remove all of the admins from the allow list, it will no longer be possible to update the allow list without modifying the subnet-evm to schedule a network upgrade.

Minting Native Coins

You can mint native(gas) coins with a precompiled contract. In order to activate this feature, you can provide nativeMinterConfig in genesis:

{
"config": {
"contractNativeMinterConfig": {
"blockTimestamp": 0,
"adminAddresses": ["0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"]
}
}
}

adminAddresses denotes admin accounts who can add other Admin or Minter accounts. Minters and Admins are both eligible to mint native coins for other addresses. ContractNativeMinter uses same methods as in ContractDeployerAllowList.

The Stateful Precompile powering the ContractNativeMinter adheres to the following Solidity interface at 0x0200000000000000000000000000000000000001 (you can load this interface and interact directly in Remix):

// (c) 2022-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface NativeMinterInterface {
// Set [addr] to have the admin role over the minter list
function setAdmin(address addr) external;

// Set [addr] to be enabled on the minter list
function setEnabled(address addr) external;

// Set [addr] to have no role over the minter list
function setNone(address addr) external;

// Read the status of [addr]
function readAllowList(address addr) external view returns (uint256);

// Mint [amount] number of native coins and send to [addr]
function mintNativeCoin(address addr, uint256 amount) external;
}

Note: Both ContractDeployerAllowList and ContractNativeMinter can be used together.

Examples

Subnet-EVM contains example contracts for precompiles under /contract-examples. It's a hardhat project with tests, tasks. For more information see contract examples README.

Chain Configs

As described in this doc, each blockchain of subnets can have its own custom configuration. If a subnet's chain id is 2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt, the config file for this chain is located at {chain-config-dir}/2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt/config.json.

For blockchains created by or forked from Subnet-evm, most C-Chain configs are applicable except Avalanche Specific APIs.

Priority Regossip

A transaction is "regossiped" when the node does not find the transaction in a block after priority-regossip-frequency (defaults to 1m). By default, up to 16 transactions (max 1 per address) are regossiped to validators per minute.

Operators can use "priority regossip" to more aggressively "regossip" transactions for a set of important addresses (like bridge relayers). To do so, you'll need to update your chain config with the following:

{
"priority-regossip-addresses": ["<YOUR 0x-ADDRESS>"]
}

By default, up to 32 transactions from priority addresses (max 16 per address) are regossipped to validators per second. You can override these defaults with the following config:

{
"priority-regossip-frequency": "1s",
"priority-regossip-max-txs": 32,
"priority-regossip-addresses": ["<YOUR 0x-ADDRESS>"],
"priority-regossip-txs-per-address": 16
}

Fee Recipient

This works together with allowFeeRecipients to specify where the fees should be sent to.

With allowFeeRecipients enabled, validators can specify their addresses to collect fees.

{
"feeRecipient": "<YOUR 0x-ADDRESS>"
}
warning

If allowFeeRecipients feature is enabled on the subnet, but a validator doesn't specify a "feeRecipient", the fees will be burned in blocks it produces.