Staking and neuron management

This document specifies extensions of the Rosetta API enabling staking funds and managing governance "neurons" on the Internet Computer.

Operations within a transaction are applied in order, so the order of operations is significant. Transactions that contain idempotent operations provided by this API can be re-tried within the 24-hour window.
Due to limitations of the governance canister smart contract, neuron management operations are not reflected on the chain. If you lookup transactions by identifier returned from the /construction/submit endpoint, these transactions might not exist or miss neuron management operations. Instead, /construction/submit returns the statuses of all the operations in the metadata field using the same format as /block/transaction would return.

Deriving neuron address

Since version

1.3.0

Call the /construction/derive endpoint with metadata field account_type set to "neuron" to compute the ledger address corresponding to the neuron controlled by the public key.

Request

{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "public_key": {
    "hex_bytes": "1b400d60aaf34eaf6dcbab9bba46001a23497886cf11066f7846933d30e5ad3f",
    "curve_type": "edwards25519"
  },
  "metadata": {
    "account_type": "neuron",
    "neuron_index": 0
  }
}
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).

Response

{
  "account_identifier": {
    "address": "531b163cd9d6c1d88f867bdf16f1ede020be7bcd928d746f92fbf7e797c5526a"
  }
}

Stake funds

Since version

1.0.5

Idempotent?

yes

To stake funds, execute a transfer to the neuron address followed by a STAKE operation.

The only field that you must set for the STAKE operation is account, which should be equal to the ledger account of the neuron controller. You can specify neuron_index field in the metadata field of the STAKE operation. If you do specify the neuron_index, its value must be the same as you used to derive the neuron account identifier.

Request

{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101",
  },
  "operations": [
    {
      "operation_identifier": { "index": 0 },
      "type": "TRANSACTION",
      "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
      "amount": {
        "value": "-100000000",
        "currency": { "symbol": "ICP", "decimals": 8 }
      }
    },
    {
      "operation_identifier": { "index": 1 },
      "type": "TRANSACTION",
      "account": { "address": "531b163cd9d6c1d88f867bdf16f1ede020be7bcd928d746f92fbf7e797c5526a" },
      "amount": {
        "value": "100000000",
        "currency": { "symbol": "ICP", "decimals": 8 }
      }
    },
    {
      "operation_identifier": { "index": 2 },
      "type": "FEE",
      "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
      "amount": {
        "value": "-10000",
        "currency": { "symbol": "ICP", "decimals": 8 }
      }
    },
    {
      "operation_identifier": { "index": 3 },
      "type": "STAKE",
      "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
      "metadata": {
        "neuron_index": 0
      }
    }
  ]
}

Response

{
  "transaction_identifier": {
    "hash": "2f23fd8cca835af21f3ac375bac601f97ead75f2e79143bdf71fe2c4be043e8f"
  },
  "metadata": {
    "operations": [
      {
        "operation_identifier": { "index": 0 },
        "type": "TRANSACTION",
        "status": "COMPLETED",
        "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
        "amount": {
          "value": "-100000000",
          "currency": { "symbol": "ICP", "decimals": 8 }
        }
      },
      {
        "operation_identifier": { "index": 1 },
        "type": "TRANSACTION",
        "status": "COMPLETED",
        "account": { "address": "531b163cd9d6c1d88f867bdf16f1ede020be7bcd928d746f92fbf7e797c5526a" },
        "amount": {
          "value": "100000000",
          "currency": { "symbol": "ICP", "decimals": 8 }
        }
      },
      {
        "operation_identifier": { "index": 2 },
        "type": "FEE",
        "status": "COMPLETED",
        "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
        "amount": {
          "value": "-10000",
          "currency": { "symbol": "ICP", "decimals": 8 }
        }
      },
      {
        "operation_identifier": { "index": 3 },
        "type": "STAKE",
        "status": "COMPLETED",
        "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
        "metadata": {
          "neuron_index": 0
        }
      }
    ]
  }
}

Managing neurons

Setting dissolve timestamp

Since version

1.1.0

Idempotent?

yes

Minimal access level

controller

This operation updates the time when the neuron can reach the DISSOLVED state.

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.

Preconditions
  • account.address is the ledger address of the neuron contoller.

Example
{
  "operation_identifier": { "index": 4 },
  "type": "SET_DISSOLVE_TIMESTAMP",
  "account": {
    "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d"
  },
  "metadata": {
    "neuron_index": 0,
    "dissolve_time_utc_seconds": 1879939507
  }
}

Start dissolving

Since version

1.1.0

Idempotent?

yes

Minimal access level

controller

The START_DISSOLVNG operation changes the state of the neuron to DISSOLVING.

Preconditions
  • account.address is the ledger address of the neuron contoller.

Postconditions
  • The neuron is in the DISSOLVING state.

Example
{
  "operation_identifier": { "index": 5 },
  "type": "START_DISSOLVING",
  "account": {
    "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d"
  },
  "metadata": {
    "neuron_index": 0
  }
}

Stop dissolving

Since version

1.1.0

Idempotent?

yes

Minimal access level

controller

The STOP_DISSOLVNG operation changes the state of the neuron to NOT_DISSOLVING.

Preconditions
  • account.address is a ledger address of a neuron contoller.

Postconditions
  • The neuron is in NOT_DISSOLVING state.

Example
{
  "operation_identifier": { "index": 6 },
  "type": "STOP_DISSOLVING",
  "account": {
    "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d"
  },
  "metadata": {
    "neuron_index": 0
  }
}

Adding hotkeys

Since version

1.2.0

Idempotent?

yes

Minimal access level

controller

The ADD_HOTKEY operation adds a hotkey to the neuron. The Governance canister smart contract allows some non-critical operations to be signed with a hotkey instead of the controller’s key (e.g., voting and querying maturity).

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

  • The neuron has less than 10 hotkeys.

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

Add a principal as a hotkey

{
  "operation_identifier": { "index": 0 },
  "type": "ADD_HOTKEY",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0,
    "principal": "sp3em-jkiyw-tospm-2huim-jor4p-et4s7-ay35f-q7tnm-hi4k2-pyicb-xae"
  }
}

Add a public key as a hotkey

{
  "operation_identifier": { "index": 0 },
  "type": "ADD_HOTKEY",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0,
    "public_key": {
      "hex_bytes":  "1b400d60aaf34eaf6dcbab9bba46001a23497886cf11066f7846933d30e5ad3f",
      "curve_type": "edwards25519"
    }
  }
}

Spawn neurons

Since version

1.3.0

Idempotent?

yes

Minimal access level

controller

The SPAWN operation creates a new neuron from an existing neuron with enough maturity. This 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.

{
  "operation_identifier": { "index": 0 },
  "type": "SPAWN",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0,
    "controller": "sp3em-jkiyw-tospm-2huim-jor4p-et4s7-ay35f-q7tnm-hi4k2-pyicb-xae",
    "spawned_neuron_index": 1
  }
}
  • controller metadata field is optional and equal to the existing neuron controller by default.

  • 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.

Merge neuron maturity

Since version

1.4.0

Idempotent?

no

Minimal access level

controller

The MERGE_MATURITY operation merges the existing maturity of the neuron into its stake. The percentage of maturity to merge can be specified, otherwise the entire maturity is merged.

Preconditions
  • account.address is the ledger address of the neuron controller.

  • The neuron has non-zero maturity to merge.

Postconditions
  • Maturity decreased by the amount merged.

  • Neuron stake increased by the amount merged.

Example
{
  "operation_identifier": { "index": 0 },
  "type": "MERGE_MATURITY",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0,
    "percentage_to_merge": 14
  }
}
percentage_to_merge metadata field is optional and equal to 100 by default. If specified, the value must be an integer between 1 and 100 (bounds included).

Accessing neuron attributes

Accessing public information

Since version

1.3.0

Minimal access level

public

Call the /account/balance endpoint to access the staked amount and publicly available neuron metadata.

Preconditions
  • public_key contains the public key of a neuron’s controller.

  • This operation is available only in online mode.

  • The request should not specify any block identifier because the endpoint always returns the latest state of the neuron.

Request

{
  "network_identifier": {
    "blockchain": "Internet Computer",
    "network": "00000000000000020101"
  },
  "account_identifier": {
    "address": "a4ac33c6a25a102756e3aac64fe9d3267dbef25392d031cfb3d2185dba93b4c4"
  },
  "metadata": {
    "account_type": "neuron",
    "neuron_index": 0,
    "public_key": {
      "hex_bytes": "ba5242d02642aede88a5f9fe82482a9fd0b6dc25f38c729253116c6865384a9d",
      "curve_type": "edwards25519"
    }
  }
}

Response

{
  "block_identifier": {
    "index": 1150,
    "hash": "ca02e34bafa2f58b18a66073deb5f389271ee74bd59a024f9f7b176a890039b2"
  },
  "balances": [
    {
      "value": "100000000",
      "currency": {
        "symbol": "ICP",
        "decimals": 8
      }
    }
  ],
  "metadata": {
    "verified_query": false,
    "retrieved_at_timestamp_seconds": 1639670156,
    "state": "DISSOLVING",
    "age_seconds": 0,
    "dissolve_delay_seconds": 240269355,
    "voting_power": 195170955,
    "created_timestamp_seconds": 1638802541
  }
}

Accessing protected information

Since version

1.5.0

Idempotent?

yes

Minimal access level

hotkey

The NEURON_INFO operation retrieves the state of the neuron from the governance canister smart contract, including protected fields such as maturity. This operation does not change the state of the neuron. Either the neuron controller or a hotkey can execute this operation.

Preconditions
  • account.address is the ledger address of the neuron controller or hotkey.

Calling NEURON_INFO as a controller:
{
  "operation_identifier": { "index": 0 },
  "type": "NEURON_INFO",
  "account": { "address": "907ff6c714a545110b42982b72aa39c5b7742d610e234a9d40bf8cf624e7a70d" },
  "metadata": {
    "neuron_index": 0
  }
}
Calling NEURON_INFO with a hotkey:
{
  "operation_identifier": { "index": 0 },
  "type": "NEURON_INFO",
  "account": { "address": "8af54f1fa09faeca18d294e0787346264f9f1d6189ed20ff14f029a160b787e8" },
  "metadata": {
    "neuron_index": 0,
    "controller": {
      "hex_bytes": "ba5242d02642aede88a5f9fe82482a9fd0b6dc25f38c729253116c6865384a9d",
      "curve_type": "edwards25519"
    }
  }
}

Since Rosetta API identifies neurons by the controller’s public key and neuron index, the caller has to specify the public key when executing the operation using a hotkey.

The Rosetta API returns the state of the neuron as operation metadata in the /construction/submit endpoint.

Example response from the /construction/submit endpoint:
{
  "transaction_identifier": {
    "hash": "0000000000000000000000000000000000000000000000000000000000000000"
  },
  "metadata": {
    "operations": [
      {
        "operation_identifier": { "index": 0 },
        "type": "NEURON_INFO",
        "status": "COMPLETED",
        "account": {
          "address": "8af54f1fa09faeca18d294e0787346264f9f1d6189ed20ff14f029a160b787e8"
        },
        "metadata": {
          "controller": "sp3em-jkiyw-tospm-2huim-jor4p-et4s7-ay35f-q7tnm-hi4k2-pyicb-xae",
          "kyc_verified": true,
          "maturity_e8s_equivalent": 1000,
          "neuron_fees_e8s": 0,
          "neuron_id": 18089972080608815000,
          "neuron_index": 0,
          "state": "DISSOLVING"
        }
      }
    ]
  }
}