Skip to main content

Create a Blockchain Running the AVM

Introduction

One of the core features of Avalanche is the ability to create new blockchains. Avalanche supports the creation of new instances of the Avalanche Virtual Machine (AVM). In this tutorial, we’ll create a X-Chain alike blockchain by creating a new instance of the AVM.

If you're interested in building custom blockchains, see Create a Virtual Machine (VM) and Create a Custom Blockchain.

Note: IDs of Blockchains, Subnets, Transactions and Addresses can be different for each run/network. It means that some inputs, endpoints etc. in the tutorial can be different when you try.

Prerequisites

You will need a running node, a user on the node, and some AVAX in the address controlled by the user. All of that is covered in the Run an Avalanche Node tutorial.

Next, you need to have your node be a validator on the Primary Network. You can find out how to do that in the Add a Validator tutorial. It is recommended you do that with API calls, since that is the way you will be interacting with your node in the rest of this tutorial.

Create the Subnet

Every blockchain is validated by a subnet. Before you can create a blockchain, you’ll need a subnet to validate it. You can also use a subnet that already exists if you have a sufficient number of its control keys.

Add Validators to the Subnet

The subnet needs validators in it to, well, validate blockchains.

Create the Genesis Data

Each blockchain has some genesis state when it’s created. Each VM defines the format and semantics of its genesis data. The AVM and Coreth have a static API method named buildGenesis that takes in a JSON representation of a blockchain’s genesis state and returns the byte representation of that state.

The AVM’s documentation specifies that the argument to avm.buildGenesis should look like this:

{
"genesisData":
{
"assetAlias1": { // Each object defines an asset
"name": "human readable name",
"symbol":"AVAL", // Symbol is between 0 and 4 characters
"initialState": {
"fixedCap" : [ // Choose the asset type.
{ // Can be "fixedCap", "variableCap"
"amount":1000, // At genesis, address A has
"address":"A" // 1000 units of asset
},
{
"amount":5000, // At genesis, address B has
"address":"B" // 1000 units of asset
},
... // Can have many initial holders
]
}
},
"assetAliasCanBeAnythingUnique": { // Asset alias can be used in place of assetID in calls
"name": "human readable name", // names need not be unique
"symbol": "AVAL", // symbols need not be unique
"initialState": {
"variableCap" : [ // No units of the asset exist at genesis
{
"minters": [ // The signature of A or B can mint more of
"A", // the asset.
"B"
],
"threshold":1
},
{
"minters": [ // The signatures of 2 of A, B and C can mint
"A", // more of the asset
"B",
"C"
],
"threshold":2
},
... // Can have many minter sets
]
}
},
... // Can list more assets
}
}

To create the byte representation of this genesis state, call avm.buildGenesis. Your call should look like the one below. Note that AVAX does not exist on custom blockchains, but you'll still need a way to pay for transaction fees on this new chain. On custom AVM instances, the transaction fees are denominated in the first asset specified in the genesisData. In this example, fees are paid with asset1 (named myFixedCapAsset.) Make sure that you put enough amount to cover for fees. The default transaction fee is 1,000,000 of whatever asset the fees are denominated in. More information about fees can be found here.

Note that this call is made to the AVM’s static API endpoint, /ext/vm/avm:

curl -X POST --data '{
"jsonrpc": "2.0",
"id" : 1,
"method" : "avm.buildGenesis",
"params" : {
"genesisData": {
"asset1": {
"name": "myFixedCapAsset",
"symbol":"MFCA",
"initialState": {
"fixedCap" : [
{
"amount":100000000,
"address": "avax1dmrwka6uck44zkaamagq46hhntta67yxfy9h9z"
},
{
"amount":100000000,
"address": "avax1u4uvatmymlue3zf4w0admnyj6vsw9mqk7hjckl"
},
{
"amount":5000000,
"address": "avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70"
},
{
"amount":5000000,
"address": "avax1hzrwdlpum8xmt3pgstejx4sz86ajylmmaylspc"
}
]
}
},
"asset2": {
"name": "myVarCapAsset",
"symbol":"MVCA",
"initialState": {
"variableCap" : [
{
"minters": [
"avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70",
"avax1hzrwdlpum8xmt3pgstejx4sz86ajylmmaylspc"
],
"threshold":1
},
{
"minters": [
"avax1je76jegcc0qylnz473ag9l5ywvhe8we8e5qw0k",
"avax1y9sull9tpaz9s507uekmm4sejwvndrela0mu43",
"avax1grn5kuzalzek7uk405fmgae06ly8cw52ms070k"
],
"threshold":2
}
]
}
}
}
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/vm/avm

This returns the byte representation of your blockchain’s genesis state:

{
"jsonrpc": "2.0",
"result": {
"bytes": "111TNWzUtHKoSvxohjyfEwE2X228ZDGBngZ4mdMUVMnVnjtnawW1b1zbAhzyAM1v6d7ECNj6DXsT7qDmhSEf3DWgXRj7ECwBX36ZXFc9tWVB2qHURoUfdDvFsBeSRqatCmj76eZQMGZDgBFRNijRhPNKUap7bCeKpHDtuCZc4YpPkd4mR84dLL2AL1b4K46eirWKMaFVjA5btYS4DnyUx5cLpAq3d35kEdNdU5zH3rTU18S4TxYV8voMPcLCTZ3h4zRsM5jW1cUzjWVvKg7uYS2oR9qXRFcgy1gwNTFZGstySuvSF7MZeZF4zSdNgC4rbY9H94RVhqe8rW7MXqMSZB6vBTB2BpgF6tNFehmYxEXwjaKRrimX91utvZe9YjgGbDr8XHsXCnXXg4ZDCjapCy4HmmRUtUoAduGNBdGVMiwE9WvVbpMFFcNfgDXGz9NiatgSnkxQALTHvGXXm8bn4CoLFzKnAtq3KwiWqHmV3GjFYeUm3m8Zee9VDfZAvDsha51acxfto1htstxYu66DWpT36YT18WSbxibZcKXa7gZrrsCwyzid8CCWw79DbaLCUiq9u47VqofG1kgxwuuyHb8NVnTgRTkQASSbj232fyG7YeX4mAvZY7a7K7yfSyzJaXdUdR7aLeCdLP6mbFDqUMrN6YEkU2X8d4Ck3T"
},
"id": 1
}

Create the Blockchain

Now let’s create the new blockchain. To do so, we call platform.createBlockchain. Your call should look like the one below. You have to change subnetID to the subnet that will validate your blockchain, and supply a username that controls a sufficient number of the subnet’s control keys. As a reminder, you can find out what a subnet’s threshold and control keys are by calling platform.getSubnets.

curl -X POST --data '{
"jsonrpc": "2.0",
"method": "platform.createBlockchain",
"params" : {
"subnetID": "KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT",
"vmID":"avm",
"name":"My new AVM",
"genesisData": "111TNWzUtHKoSvxohjyfEwE2X228ZDGBngZ4mdMUVMnVnjtnawW1b1zbAhzyAM1v6d7ECNj6DXsT7qDmhSEf3DWgXRj7ECwBX36ZXFc9tWVB2qHURoUfdDvFsBeSRqatCmj76eZQMGZDgBFRNijRhPNKUap7bCeKpHDtuCZc4YpPkd4mR84dLL2AL1b4K46eirWKMaFVjA5btYS4DnyUx5cLpAq3d35kEdNdU5zH3rTU18S4TxYV8voMPcLCTZ3h4zRsM5jW1cUzjWVvKg7uYS2oR9qXRFcgy1gwNTFZGstySuvSF7MZeZF4zSdNgC4rbY9H94RVhqe8rW7MXqMSZB6vBTB2BpgF6tNFehmYxEXwjaKRrimX91utvZe9YjgGbDr8XHsXCnXXg4ZDCjapCy4HmmRUtUoAduGNBdGVMiwE9WvVbpMFFcNfgDXGz9NiatgSnkxQALTHvGXXm8bn4CoLFzKnAtq3KwiWqHmV3GjFYeUm3m8Zee9VDfZAvDsha51acxfto1htstxYu66DWpT36YT18WSbxibZcKXa7gZrrsCwyzid8CCWw79DbaLCUiq9u47VqofG1kgxwuuyHb8NVnTgRTkQASSbj232fyG7YeX4mAvZY7a7K7yfSyzJaXdUdR7aLeCdLP6mbFDqUMrN6YEkU2X8d4Ck3T",
"username":"USERNAME GOES HERE",
"password":"PASSWORD GOES HERE"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/P

The response contains the transaction ID:

{
"jsonrpc": "2.0",
"result": {
"txID": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH",
"changeAddr": "P-avax103y30cxeulkjfe3kwfnpt432ylmnxux8r73r8u"
},
"id": 1
}

Verify Success

After a few seconds, the transaction to create our blockchain should have been accepted and the blockchain should exist (assuming the request was well-formed, etc.)

To check, call platform.getBlockchains. This returns a list of all blockchains that exist.

curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"platform.getBlockchains",
"params" :{}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/P

The response confirms that the blockchain was created:

{
"jsonrpc": "2.0",
"result": {
"blockchains": [
{
"id": "AXerNQX7voY2AABaRdTAyXcawBURBR6thPRJp43LtPpZZi6Qz",
"name": "X-Chain",
"subnetID": "11111111111111111111111111111111LpoYY",
"vmID": "jvYyfQTxGMJLuGWa55kdP2p2zSUYsQ5Raupu4TW34ZAUBAbtq"
},
{
"id": "tZGm6RCkeGpVETUTp11DW3UYFZmm69zfqxchpHrSF7wgy8rmw",
"name": "C-Chain",
"subnetID": "11111111111111111111111111111111LpoYY",
"vmID": "mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6"
},
{
"id": "sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk",
"name": "My new TSVM",
"subnetID": "KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT",
"vmID": "tGas3T58KzdjLHhBDMnH2TvrddhqTji5iZAMZ3RXs2NLpSnhH"
},
{
"id": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH",
"name": "My new AVM",
"subnetID": "KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT",
"vmID": "jvYyfQTxGMJLuGWa55kdP2p2zSUYsQ5Raupu4TW34ZAUBAbtq"
}
]
},
"id": 1
}

Validating the Blockchain

Every blockchain needs a set of validators to validate and process transactions on it. You can check if a node is validating a given blockchain by calling platform.getBlockchainStatus on that node:

curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"platform.getBlockchainStatus",
"params" :{
"blockchainID":"xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/P
{
"jsonrpc": "2.0",
"result": {
"status": "Validating"
},
"id": 1
}

If it responds "Validating", the node is validating the given chain. If it responds "Syncing", then the chain tracked by this node but it is not validating. If it responde "Created" then the chain exists but it is not being synced. Note that in order to validate or watch a subnet, you need to start your node with argument --whitelisted-subnets=[subnet ID goes here] (e.g. --whitelisted-subnets=KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT) as well as add the node to the subnet's validator set.

More information can be found in the Adding a Subnet Validator tutorial.

Interacting with the New Blockchain

You can interact with this new instance of the AVM almost the same way you’d interact with the X-Chain. There are some small differences:

  • The API endpoint of your blockchain is 127.0.0.1:9650/ext/bc/xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH. The last part in the endpoint is the blockchain ID. This can be a different ID when you create your blockchain. You can also alias this chain ID with myxchain for simpler API URLs. More information:

    admin.aliasChain

  • Addresses are prepended with custom blockchain's ID xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH- rather than X-. This can be a different ID when you create your blockchain.

  • Fees are paid with the first asset specified in the genesis data, as noted above, rather than AVAX...

Verify Balance

In the genesis data we specified that address avax1dmrwka6uck44zkaamagq46hhntta67yxfy9h9z has 100,000,000 units of the asset with alias asset1. Let’s verify that:

curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"avm.getBalance",
"params" :{
"address":"xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax1dmrwka6uck44zkaamagq46hhntta67yxfy9h9z",
"assetID":"asset1"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"balance": "100000000",
"utxoIDs": [
{
"txID": "9tKDkdk4PUj3GW3tw6fuYywVRwP5gXDj7XTEuPkmLAhauPN8a",
"outputIndex": 0
}
]
},
"id": 1
}

Send Asset

Let's send some asset1 to another address. First, create a recipient address:

curl -X POST --data '{
"jsonrpc": "2.0",
"method": "avm.createAddress",
"params": {
"username":"USERNAME GOES HERE",
"password":"PASSWORD GOES HERE"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"address": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax1u4uvatmymlue3zf4w0admnyj6vsw9mqk7hjckl"
},
"id": 1
}

Now let's send 1 unit of asset1 to the new address with avm.send.

curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"avm.send",
"params" :{
"assetID" : "asset1",
"amount" : 1,
"from" : ["xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax1dmrwka6uck44zkaamagq46hhntta67yxfy9h9z"],
"to" : "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax1u4uvatmymlue3zf4w0admnyj6vsw9mqk7hjckl",
"changeAddr": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax1dmrwka6uck44zkaamagq46hhntta67yxfy9h9z",
"username": "USERNAME GOES HERE",
"password": "PASSWORD GOES HERE"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"txID": "2MqZ5x6keEF1mZ4d6rb12bN4euirTqwTTm1AZGVzTT7n3eKQqq",
"changeAddr": "g1GK7GErN3BqauK6BhhU8uCNfaBTMz4VWr3JdwvXXNCwpwQJQ-avax1dmrwka6uck44zkaamagq46hhntta67yxfy9h9z"
},
"id": 1
}

We can verify transaction status with:

curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"avm.getTxStatus",
"params" :{
"txID": "2MqZ5x6keEF1mZ4d6rb12bN4euirTqwTTm1AZGVzTT7n3eKQqq"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"status": "Accepted"
},
"id": 1
}

Now we can confirm balances are changed accordingly:

curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"avm.getBalance",
"params" :{
"address":"xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax1u4uvatmymlue3zf4w0admnyj6vsw9mqk7hjckl",
"assetID": "asset1"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"balance": "1",
"utxoIDs": [
{
"txID": "2MqZ5x6keEF1mZ4d6rb12bN4euirTqwTTm1AZGVzTT7n3eKQqq",
"outputIndex": 0
}
]
},
"id": 1
}

As mentioned above, transaction fees are paid with asset1. We can confirm 1,000,000 unit (default) is used as fee in our transaction. Let's check senders balance after the transaction.

curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"avm.getBalance",
"params" :{
"address":"xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax1dmrwka6uck44zkaamagq46hhntta67yxfy9h9z",
"assetID": "asset1"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"balance": "98999999",
"utxoIDs": [
{
"txID": "2MqZ5x6keEF1mZ4d6rb12bN4euirTqwTTm1AZGVzTT7n3eKQqq",
"outputIndex": 1
}
]
},
"id": 1
}

This address had 100,000,000 asset1, then we sent 1 unit to the other address and paid 1,000,000 for the transaction fee, resulting in a balance of 98,999,999 units of asset1.

Mint Asset

Our blockchain has another asset asset2 named myVarCapAsset. It is a variable-cap asset. Let's mint more units of this asset with avm.mint. Address avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70 controls the mintable asset asset2, and it also has 5,000,000 unit asset1, which is enough to pay the transaction fee.

curl -X POST --data '{
"jsonrpc":"2.0",
"id" : 1,
"method" :"avm.mint",
"params" :{
"amount": 1,
"assetID": "asset2",
"from": ["xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70"],
"to": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70",
"minters": [
"xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70"
],
"changeAddr": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70",
"username": "USERNAME GOES HERE",
"password": "PASSWORD GOES HERE"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"txID": "2UQL5u5ZEELHfRpAtDYtmFF8BMSdoWNWS1Zf2dkbVSDeTbXeJQ",
"changeAddr": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70"
},
"id": 1
}

Let's check the balance with avm.getAllBalances.

curl -X POST --data '{
"jsonrpc":"2.0",
"id" : 1,
"method" :"avm.getAllBalances",
"params" :{
"address":"xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH-avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/myxchain
{
"jsonrpc": "2.0",
"result": {
"balances": [
{
"asset": "asset2",
"balance": "1"
},
{
"asset": "asset1",
"balance": "4000000"
}
]
},
"id": 1
}

As we can see, 1 unit of asset2 was minted. Address avax16k8n4d8xmhplqn5vhhm342g6n9rkxuj8wn6u70 had 5,000,000 asset1, as defined in the genesis data, and now has 4,000,000 asset1 after paying the transaction fee.