Skip to main content

ICP Rosetta staking API reference

Intermediate
Rosetta

This section will give you an overview of how ICP staking on the Internet Computer using Rosetta works.

This guide assumes you are familiar with the flow of operations.

You have a Rosetta instance running at: http://127.0.0.1:8081
You are familiar with the concept of staking on the NNS in general.

Neuron lifecycle

A neuron is a type of asset managed by the Internet Computer governance canister. Neurons allow their controllers to participate in the governance of the network by submitting and voting on proposals.

Creating a neuron

  •   Transferring some amount of ICPs from any ledger account to the neuron’s address. Neuron address on the ledger that belongs to the Governance canister. The caller computes the neuron address from two parts:

    •   The principal of the governance canister.

    •   The subaccount that is itself a hash of the controller’s principal with an integer nonce. One principal can control multiple neurons by picking different integer nonces for the subaccount computation.

Creating a neuron involves calling the claim_or_refresh_neuron_from_account method of the governance canister. If the call is successful, the payload contains the neuron ID, a numeric identifier of the newly created neuron. The governance canister maintains a mapping between neuron IDs and subaccounts. Most management operations can be performed using either the neuron ID or the subaccount address.

Neuron attributes

A neuron has the following attributes that control its lifecycle and rewards distribution:

Dissolve delay

Dissolve delay is how long it will take a neuron to become "liquid" once it starts dissolving. If the neuron is already dissolving, the delay indicates the amount of time left before the neuron transitions to the DISSOLVED state. The dissolve delay of a newly created neuron is 0. Only neurons with a dissolve delay of at least 15778800 seconds (approximately 6 months) can be used to vote on proposals (as of 2021-07-27, the exact delay requirements are subject to change). Once set, the dissolve delay cannot be reduced via management commands.

Dissolve state

NOT_DISSOLVING

A neuron has a fixed dissolve delay and accrues age. This is the default state of a newly created neuron.

DISSOLVING

A neuron’s dissolve delay decreases with the passage of time. Once the dissolve delay becomes 0, the neuron transitions to the DISSOLVED state, and the staked ICPs can be disbursed.

DISSOLVED

A neuron’s dissolve delay is 0. The neuron holder is free to either disburse the staked ICPs or increase the dissolve delay, which will cause a transition to the NOT_DISSOLVING state.

Age

Age is the amount of time that passed since the last time the neuron transitioned to the NOT_DISSOLVING state. If the neuron is DISSOLVING, its age is 0.

Maturity

Maturity is the total amount of unspent rewards accrued by this neuron. A neuron’s controller can spend maturity in two ways:

  • Spawn a new neuron with a dissolve delay of seven days and stake equal to the maturity.

  • Merge maturity into the stake, increasing voting power.

Voting power

Voting power is proportional to the amount of staked ICP. Age and dissolve delay multiplicative bonuses increase the voting power if the neuron is in the NOT_DISSOLVING state.

Rewards

Once a proposal is accepted, all the neurons that voted on that proposal receive rewards in the form of a maturity increase. The governance canister distributes rewards daily. The rewards are proportional to the voting power.

For more information on neuron state transitions and rewards, see understanding the Internet Computer’s Network Nervous System, neurons, and ICP utility tokens.

State transitions

stateDiagram-v2 state "Non-dissolving Neuron" as non_dissolving state "Dissolving Reward Neuron" as dissolving state "Dissolved Reward Neuron" as dissolved [*] --> non_dissolving : transfer + claim_or_refresh_neuron_from_account non_dissolving --> non_dissolving : increase_dissolve_delay non_dissolving --> dissolving : start_dissolving dissolving --> non_dissolving : stop_dissolving dissolving --> dissolved : passage of time dissolved --> non_dissolving: increase_dissolve_delay dissolved --> [*] : disburse

Change auto-stake-maturity

                            
Since version        1.7.0  
Idempotent?          Yes    
Minimal access levelcontroller

If you do not want to manually have to stake the maturity that you acquired, you can set your neuron to automatically stake new maturity. You can always deactivate this feature by calling it with the boolean false.

An example command for this feature resembles the following:

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '
{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "CHANGE_AUTO_STAKE_MATURITY",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 0,
        "set_auto_stake_maturity": true
      }
    }
  ],
  "metadata": null
}'

Derive neuron ID

                  
Since version1.3.0
Idempotent?          no        
Minimal access levelcontroller

Staking requires transferring tokens to a sub-account of the governance canister. To complete the transfer, you need to know both your ICP ledger account identifier (source of the transfer) and the account identifier of the governance sub-account (destination of the transfer). Make sure you have enough ICP in your source account to make the transfer to the governance canister. To derive the account identifier of your public key, you can look at how the derive endpoint works.

Deriving the governance canister account identifier

For neuron creation on the Internet Computer, the process begins with the transfer of ICP tokens to the address destined to become the neuron. This address, while earmarked for a future neuron, does not yet represent an active neuron until the governance canister acknowledges the deposit. It's crucial to understand that, at this juncture, the neuron has not been created; the address is simply a placeholder for the future neuron. By transferring the ICP tokens directly to this designated address, you effectively earmark them for neuron creation. However, the governance canister must be notified of this transfer to activate the neuron. This process is done in the next step of the guide. Once the governance canister is aware, it uses the transferred tokens at the specified address to formally establish the neuron.

You can derive the governance canister account identifier you need to send ICP similarly to how you derive your own account identifier.

curl --location '0.0.0.0:8081/construction/derive'  --header 'Content-Type: application/json' --data '{
 "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_key": {
    "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
    "curve_type": "secp256k1"
  },
  "metadata": {
    "account_type": "neuron",
    "neuron_index": 0
  }
}'

The response is the account identifier we need to fund with ICP.

{"account_identifier":{"address":"26d877d2a04f0a9ae5e5a481102d5a865115263f2ed93a0804e3ed7b8d824921"}}

Since version 1.3.0, you can control many neurons using the same key. You can differentiate between neurons by specifying different values of the neuron_index metadata field. The rosetta node supports neuron_index in all neuron management operations. neuron_index is an arbitrary integer between 0 and 264 - 1 (18446744073709551615). It is equal to zero if not specified. If you use JavaScript to construct requests to the Rosetta node, consider using the BigInt type to represent the neuron_index. The Number type can precisely represent only values below 253 - 1 (9007199254740991).

We can then fund this account with the amount of ICP we would like to stake. In this example, we want to stake 10 ICP, or 1_000_000_000 e8s.

Disburse a neuron

                            
Since version        1.4.0  
Idempotent?          No    
Minimal access levelcontroller

This section will show you how to use Rosetta to disburse an existing neuron.

Preconditions

  • account.address is the ledger address of the neuron controller or hotkey.
  • amount is the amount that should be extracted from the dissolved neuron and needs to be equal to or less than the amount of ICP locked in the neuron.
  • metadata.recipient contains an account_identifier that should receive the ICP after the neuron is disbursed.
  •  The neuron needs to be in a DISSOLVED state to be able to be disbursed.

Postconditions

  • The neurons locked funds were reduced by the amount specified in the request.
  • metadata.recipient received the specified amount.
curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "DISBURSE",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "amount": {
        "value": "1000000000",
        "currency": {
          "symbol": "ICP",
          "decimals": 8
        }
      },
      "metadata": {
        "neuron_index": 2,
        "recipient": "47e0ae0de8af04a961c4b3225cd77b9652777286ce142c2a07fab98da5263100"
      }
    }
  ],
  "metadata": null
}'

Start or stop dissolving

                                
Since version        1.1.0      
Idempotent?          yes        
Minimal access levelcontroller

If you want to retrieve the ICP that was locked in the neuron, you will have to set the neuron to a dissolving state. You must wait at least 6 months between setting the neuron to DISSOLVING and receiving back your ICP. The waiting period increases if the set dissolve timestamp is further in the future than 6 months.

Start dissolving

To set a neuron to DISSOLVING, you can build the following payload request:

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data
'{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "START_DISSOLVING",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 0
      }
    }
  ],
  "metadata": null
}'

After that, you can fetch the neuron info and it should display that it is now in DISSOLVING state.

{
    "transaction_identifier": {
        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
    },
    "metadata": {
        "operations": [
            {
                "account": {
                    "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
                },
                "metadata": {
                    "controller": "iowfl-yzooa-br3dt-77erl-nlm7f-kplhq-php75-hw3an-aeqn2-swh4t-3qe",
                    "followees": {},
                    "hotkeys": [],
                    "kyc_verified": true,
                    "maturity_e8s_equivalent": 0,
                    "neuron_fees_e8s": 0,
                    "neuron_id": 3094748712371737240,
                    "neuron_index": 0,
                    "staked_maturity_e8s": null,
                    "state": "DISSOLVING"
                },
                "operation_identifier": {
                    "index": 0
                },
                "status": "COMPLETED",
                "type": "NEURON_INFO"
            }
        ]
    }
}

Stop dissolving

To stop a neuron from dissolving, you can call Rosetta with the following parameters:

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data
'{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "STOP_DISSOLVING",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 0
      }
    }
  ],
  "metadata": null
}'

The corresponding state of the neuron will switch to `NOT_DISSOLVING`.

```json
{
    "transaction_identifier": {
        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
    },
    "metadata": {
        "operations": [
            {
                "account": {
                    "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
                },
                "metadata": {
                    "controller": "iowfl-yzooa-br3dt-77erl-nlm7f-kplhq-php75-hw3an-aeqn2-swh4t-3qe",
                    "followees": {},
                    "hotkeys": [],
                    "kyc_verified": true,
                    "maturity_e8s_equivalent": 0,
                    "neuron_fees_e8s": 0,
                    "neuron_id": 3094748712371737240,
                    "neuron_index": 0,
                    "staked_maturity_e8s": null,
                    "state": "NOT_DISSOLVING"
                },
                "operation_identifier": {
                    "index": 0
                },
                "status": "COMPLETED",
                "type": "NEURON_INFO"
            }
        ]
    }
}

Hotkeys generation

This section will explain how to generate a hotkey for neuron management and add or remove hotkeys to your neuron using ICP Rosetta. The recommended way to get hotkeys is to programmatically generate them using the same process used in the public ic repository. However, it is possible to generate hotkeys manually as described below.

Generating a hotkey

The manual hotkey generation process consists of generating a PEM file, then deriving the corresponding private and public keys.

Generate PEM

To generate the keys, start with generating a private PEM file:

openssl genpkey -algorithm ed25519 -outform PEM -out private.pem

Example output:

-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIHAOt4HGrNdcIFhBy7N9p6iq3iRowd4NZjDZ8aaaDCcX
-----END PRIVATE KEY-----

Then create the public PEM from it:

openssl pkey -in private.pem -pubout > public.pem

Example output:

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA4JKtE2KNVUTo96cl202FgWv5ctwP7f1ds1O73PZ6+VE=
-----END PUBLIC KEY-----

Generating hex key representation

Create the private DER file:

openssl pkey -inform pem -outform der -in private.pem -out private.der

Create the public DER file:

openssl pkey -inform pem -outform der -in private.pem -out public.der -pubout

Hex representation

The generated DER files are in a binary format not intended to be readable by humans. But you can get a hex representation of both keys:

Private key

xxd -p private.der

Example output:

302e020100300506032b657004220420700eb781c6acd75c205841cbb37d
a7a8aade2468c1de0d6630d9f1a69a0c2717

Public key

xxd -p public.der

302a300506032b6570032100e092ad13628d5544e8f7a725db4d85816bf9
72dc0fedfd5db353bbdcf67af951

You need to keep only the last 32 bytes of the public key:

xxd -s 12 -c 32 -p public.der

Example output:

e092ad13628d5544e8f7a725db4d85816bf972dc0fedfd5db353bbdcf67af951

This is the public key you can use in Rosetta operations to identify your hotkey!

Frequently asked questions

  • Why do I get "Algorithm ed25519 not found" while generating the PEM file?

The version of OpenSSL included in macOS doesn't support ed25519 by default. You may have to install another version of OpenSSL (for example, through brew) or run the command from a Linux machine.

Adding hotkeys to a neuron

                                
Since version        1.2.0      
Idempotent?          yes        
Minimal access levelcontroller

The ADD_HOTKEY operation adds a hotkey to the neuron. The Governance canister allows some noncritical operations to be signed with a hotkey instead of the controller’s key (e.g., voting and querying maturity). The neuron you are trying to add a hotkey for must not have more than 10 hotkeys already linked to it.

The command has two forms: one form accepts an ICP principal as a hotkey, and another form accepts a public key.

Add a principal as a hotkey

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '
{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "ADD_HOTKEY",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 1,
        "principal": "sp3em-jkiyw-tospm-2huim-jor4p-et4s7-ay35f-q7tnm-hi4k2-pyicb-xae"
      }
    }
  ],
  "metadata": null
}'

Add a public key as a hotkey

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data
'{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "ADD_HOTKEY",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 1,
        "public_key": {
          "hex_bytes": "04995aca22a49c8e579a8501f38d64952054f4b33f793376f1d013d93edd10a55c29522e41b5fed16283b70a6f3fa00c6d66420938e89c5d67a58f30c8a5df059e",
          "curve_type": "secp256k1"
        }
      }
    }
  ],
  "metadata": null
}'

When you call NeuronInfo afterwards you can see the hotkey added to the list of hotkeys, like so.

{
    "transaction_identifier": {
        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
    },
    "metadata": {
        "operations": [
            {
                "account": {
                    "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
                },
                "metadata": {
                    "controller": "iowfl-yzooa-br3dt-77erl-nlm7f-kplhq-php75-hw3an-aeqn2-swh4t-3qe",
                    "followees": {},
                    "hotkeys": [
                        "ykyib-l2eur-2idfx-uyka5-hag7i-3mypz-3kqc2-d6pid-tzat5-bysqw-mae"
                    ],
                    "kyc_verified": true,
                    "maturity_e8s_equivalent": 0,
                    "neuron_fees_e8s": 0,
                    "neuron_id": 3094748712371737240,
                    "neuron_index": 0,
                    "staked_maturity_e8s": 0,
                    "state": "NOT_DISSOLVING"
                },
                "operation_identifier": {
                    "index": 0
                },
                "status": "COMPLETED",
                "type": "NEURON_INFO"
            }
        ]
    }
}

Removing hotkeys from a neuron

The REMOVE_HOTKEY operation removes a previously added hotkey from the neuron. The command has two forms: one form accepts an ICP principal as a hotkey, and another form accepts a public key.

Remove a principal as a hotkey

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '
{
  "operation_identifier": { "index": 0 },
  "type": "REMOVE_HOTKEY",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0,
    "principal": "sp3em-jkiyw-tospm-2huim-jor4p-et4s7-ay35f-q7tnm-hi4k2-pyicb-xae"
  }
}'

Remove a public key as a hotkey

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data
' {
  "operation_identifier": { "index": 0 },
  "type": "REMOVE_HOTKEY",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0,
    "public_key": {
      "hex_bytes":  "1b400d60aaf34eaf6dcbab9bba46001a23497886cf11066f7846933d30e5ad3f",
      "curve_type": "edwards25519"
    }
  }
}'

After this operation the hotkey should not be included in the NeuronInfo anymore.

Lock a neuron

                                
Since version        1.1.0      
Idempotent?          yes        
Minimal access levelcontroller

In this section of the staking tutorial, you will learn how to lock your neuron. The amount of rewards you can expect to receive is, among other factors, dependent on the amount of time a neuron is locked up for. If the dissolve timestamp is set to a value that is before 6 months in the future, you will not be getting any rewards for the locked period. This is because the last 6 months of a dissolving neuron, the neuron will not get any rewards. If you set the dissolve timestamp to 1 year in the future and start dissolving the neuron right away, you will receive rewards for the next 6 months.

The dissolve timestamp always increases monotonically.

  •   If the neuron is in the DISSOLVING state, this operation can move the dissolve timestamp further into the future.

  •   If the neuron is in the NOT_DISSOLVING state, invoking SET_DISSOLVE_TIMESTAMP with time T will attempt to increase the neuron’s dissolve delay (the minimal time it will take to dissolve the neuron) to T - current_time.

  •   If the neuron is in the DISSOLVED state, invoking SET_DISSOLVE_TIMESTAMP will move it to the NOT_DISSOLVING state and will set the dissolve delay accordingly.

An example call to the construction/payloads endpoint resembles the following:

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 4
      },
      "type": "SET_DISSOLVE_TIMESTAMP",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 0,
        "dissolve_time_utc_seconds": 1712156627
      }
    }
  ],
  "metadata": null
}'

The response will confirm that the dissolve timestamp has been changed.

{
    "transaction_identifier": {
        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
    },
    "metadata": {
        "operations": [
            {
                "account": {
                    "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
                },
                "metadata": {
                    "dissolve_time_utc_seconds": 1712156627,
                    "neuron_index": 0
                },
                "operation_identifier": {
                    "index": 0
                },
                "status": "COMPLETED",
                "type": "SET_DISSOLVE_TIMESTAMP"
            }
        ]
    }
}

If you then check the neuron you will see that it changed state from DISSOLVED to NOT_DISSOLVING. If you set the timestamp to a sufficiently far time in the future you can now start to collect rewards for with your neuron.

Fetch neuron info

                            
Since version        1.5.0  
Idempotent?          yes    
Minimal access levelhotkey

In this section of the staking tutorial you will learn how to fetch information about an existing neuron. There are a few variations to this that depend on whether you are the controller of the neuron or whether you are using a hotkey.

Calling NeuronInfo as a controller

Assuming that you are the controller of the neuron with the neuron_id 3094748712371737240 and you are not using a hotkey, the corresponding payloads call would resemble the following:

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "NEURON_INFO",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
    }
  ],
  "metadata": null
}'

Calling NeuronInfo with a hotkey

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "NEURON_INFO",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
    }
  ],
    "metadata": {
    "neuron_index": 0,
    "controller": {
      "public_key": {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
      }
    }
  }
}'

The result of the NeuronInfo call would resemble the following:

{
    "transaction_identifier": {
        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
    },
    "metadata": {
        "operations": [
            {
                "account": {
                    "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
                },
                "metadata": {
                    "controller": "iowfl-yzooa-br3dt-77erl-nlm7f-kplhq-php75-hw3an-aeqn2-swh4t-3qe",
                    "followees": {},
                    "hotkeys": [],
                    "kyc_verified": true,
                    "maturity_e8s_equivalent": 0,
                    "neuron_fees_e8s": 0,
                    "neuron_id": 3094748712371737240,
                    "neuron_index": 0,
                    "staked_maturity_e8s": null,
                    "state": "DISSOLVED"
                },
                "operation_identifier": {
                    "index": 0
                },
                "status": "COMPLETED",
                "type": "NEURON_INFO"
            }
        ]
    }
}

Right now the neuron is DISSOLVED as it has not yet been locked up.

Currently, ICP Rosetta does not support fetching neuron info of a neuron that you are not either the controller of or have a hotkey for.

Spawn a neuron

                                
Since version        1.3.0      
Idempotent?          yes        
Minimal access levelcontroller

In this section you will learn how to split a neuron and spawn a new one out of your existing neuron. The SPAWN operation transfers all the maturity from the existing neuron to the staked amount of the newly spawned neuron.

Preconditions

  •   account.address is a ledger address of a neuron controller.

  •   The parent neuron has at least 1 ICP worth of maturity.

Postconditions

  •   Parent neuron maturity is set to 0.

  •   A new neuron is spawned with a balance equal to the transferred maturity.

An example command to spawn a new neuron resembles the following. Make sure to give the spawned_neuron_index a different index than the parent neuron.

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '{
{
  "operation_identifier": { "index": 0 },
  "type": "SPAWN",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0,
    "controller": {
      "principal": "sp3em-jkiyw-tospm-2huim-jor4p-et4s7-ay35f-q7tnm-hi4k2-pyicb-xae"
    },
    "spawned_neuron_index": 1,
    "percentage_to_spawn": 75
  }
}'
  • spawned_neuron_index metadata field is required. The Rosetta node uses this index to compute the subaccount for the spawned neuron. All spawned neurons must have different values of spawned_neuron_index.

  • controller metadata field is optional and equal to the existing neuron controller by default.

  • percentage_to_spawn metadata field is optional and equal to 100 by default. If specified, the value must be an integer between 1 and 100 (bounds included).

Stake ICP

                  
Since version1.5.0
Idempotent?  yes  
Minimal access levelcontroller

This section of the staking tutorial will show you how to use the ICP that was previously sent to the governance account identifier corresponding to your public key. You can use this ICP to create and fund a neuron.

The full amount of ICP that was sent to the governance account identifier will be used for staking.

A call to the endpoint construction/payloads would resemble the following.

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "STAKE",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 0
      }
    }
  ],
  "metadata": null
}'

The transaction's returned output will resemble the following:

{'transaction_identifier': {'hash': '0000000000000000000000000000000000000000000000000000000000000000'}, 'metadata': {'operations': [{'account': {'address': '8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4'}, 'metadata': {'neuron_id': 3094748712371737240, 'neuron_index': 0}, 'operation_identifier': {'index': 0}, 'status': 'COMPLETED', 'type': 'STAKE'}]}}

Neuron management operations don’t strictly adhere to the Rosetta specification. If the transaction only contains the neuron management operations (like the example above), the returned transaction hash won’t be on the ICP ledger chain. The returned transaction hash does not make any sense for governance operations and is thus left blank.

The governance canister will take your public key and the neuron index provided (in this case it was 0) and either create a new neuron_id or add the ICP to an already existing neuron with the same derived neuron_id. In this case the neuron with the neuron_id 3094748712371737240 was created.

The neuron_id is an internal representation of neurons on the governance canister. The neuron index corresponds to the index of neurons that a certain public key controls. Taking the public key 047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367, the index 0 and given how many existing neurons already exist, the governance canister arrives at the neuron_id 3094748712371737240

Stake maturity

                                
Since version        1.4.0      
Idempotent?          no        
Minimal access levelcontroller

Once you have a neuron starts accumulating maturity, you can either decide to leave the maturity as it is until you decide to convert it to ICP, or you can make use of it and stake it in addition to your ICP. To do the latter, you will have to stake your maturity.

You can set how much of the existing maturity you want to stake. The maturity needs to be larger than zero for the operation to work. In the following example command, 100% of the maturity is staked.

curl --location '0.0.0.0:8081/construction/payloads'  --header 'Content-Type: application/json' --data '
{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_keys": [
    {
      "hex_bytes": "047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367",
      "curve_type": "secp256k1"
    }
  ],
  "operations": [
    {
      "operation_identifier": {
        "index": 0
      },
      "type": "STAKE_MATURITY",
      "account": {
        "address": "8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4"
      },
      "metadata": {
        "neuron_index": 0,
        "percentage_to_stake": 100
      }
    }
  ],
  "metadata": null
}'