Prerequisites

For this tutorial, you will need:

  • A working install of the cardano-node and cardano-cli binaries. Check out our pre-built Docker containers if you need help with this.
  • A running instance of cardano-node fully synced with the blockchain.
  • The latest Cardano-Tools python library.
  • A Shelley wallet with enough ADA for the registration fees, transaction fees, and pool pledge.

If you are new to setting up a node, check out this tutorial.

Setting Up Accounts

First, we are going to create the necessary owner accounts for registering a stake pool. Once these are created, we will transfer funds from our wallet software, i.e., Daedalus, to these accounts. One could theoretically use their Daedalus wallets directly; however, currently, Daedalus does not provide an easy way to export the signing and verification keys we need.

In order to provide a more complete demonstration, we will create three new accounts:

  • Owner #1 account (both wallet and staking accounts)
  • Owner #2 account (both wallet and staking accounts)
  • Rewards account (both wallet and staking accounts)

A single account may be used to register a stake pool; however, it is often desirable to have multiple accounts responsible for the pledge, for instance, when a pool has multiple operators.

The code to create the accounts is demonstrated below. Note that the ShelleyTools object needs the path to the cardano-cli binary, the path to the node.socket file, and the path to a working directory where the generated key files will be placed.

from cardano_tools import ShelleyTools

# Inputs (change for your node!)
path_to_cli = "/home/cardano/.cabal/bin/cardano-cli"
path_to_socket = "/home/cardano/node/node.socket"
working_dir = "/home/cardano/pool-setup/"

# Create a ShelleyTools object
shelley = ShelleyTools(
    path_to_cli,
    path_to_socket,
    working_dir)
)
shelley.debug = True  # This enables useful output

# Create the new address and all the required key files.
owner1_addr = shelley.make_address("owner1")
owner2_addr = shelley.make_address("owner2")
rewards_addr = shelley.make_address("rewards")
print(f"Owner #1 account number: {owner1_addr}")
print(f"Owner #2 account number: {owner2_addr}")
print(f"Rewards account number: {rewards_addr}")

account-creation-example


Fund the Wallets

The wallets will all need a limited amount of ADA in order to pay the transaction fees when registering the staking addresses and the pool certificates. For this example, we will send each of the owner addresses the amount of ADA we want to stake plus an extra 5 ADA. The rewards account will be used to pay the pool deposit of 500 ADA, so we will send 505 ADA to this wallet.

The following code snippet may be used to verify the wallets have received the funds send from your Daedalus account(s).

# ...using the ShelleyTools object created previously...

from pathlib import Path
working_dir = Path("/home/cardano/pool-setup/")
owner1_addr = open(working_dir / "owner1.addr", 'r').read()
owner2_addr = open(working_dir / "owner2.addr", 'r').read()
rewards_addr = open(working_dir / "rewards.addr", 'r').read()

# List the UTXOs in each account
print(f"Owner #1's account number: {owner1_addr}")
print(
    json.dumps(
        shelley.get_utxos(owner1_addr),
        indent=4,
        sort_keys=True
    )
)

print(f"Owner #2's account number: {owner2_addr}")
print(
    json.dumps(
        shelley.get_utxos(owner2_addr),
        indent=4,
        sort_keys=True
    )
)

print(f"Rewards account number: {rewards_addr}")
print(
    json.dumps(
        shelley.get_utxos(rewards_addr),
        indent=4,
        sort_keys=True
    )
)

Register the Staking Addresses

With some funds in the wallets, we can register the staking addresses.

# ...using some variables created previously...

# Register the associated staking addresses on the blockchain.
shelley.register_stake_address(
    owner1_addr,
    working_dir / "owner1_stake.vkey",
    working_dir / "owner1_stake.skey",
    working_dir / "owner1.skey",
    cleanup=True
)
shelley.register_stake_address(
    owner2_addr,
    working_dir / "owner2_stake.vkey",
    working_dir / "owner2_stake.skey",
    working_dir / "owner2.skey",
    cleanup=True
)
shelley.register_stake_address(
    rewards_addr,
    working_dir / "rewards_stake.vkey",
    working_dir / "rewards_stake.skey",
    working_dir / "rewards.skey",
    cleanup=True
)

Once the staking addresses are registered on the blockchain, we can register the stake pool.

Register the Stake Pool

Registering a stake pool will be divided into three steps: metadata (for wallets), stake pool key generation, and finally stake pool certificate registration.

Stake Pool Metadata

In order for the pool to be included in wallets, pool metadata must be generated and hosted at a static web address. The hash of the file must also be saved or re-calculated for inclusion during pool registration. The website, pooltool.io can be used to generate and host this metadata, but we will generate it manually in this tutorial.

# ...using the ShelleyTools object created previously...

metadata = {
    "name": "My Stake Pool Name",
    "description": "My pool description--its awesome!.",
    "ticker": "TICKR",
    "homepage": "https://yourstakingbusinesswebsite.com/"
}

# Create the Metadata JSON file
metadata_hash = shelley.create_metadata_file(metadata) 
print(metadata_hash)

Files created:

  • TICKR_metadata.json

Upload the created file to your website.

Generate Pool Keys

A rather extensive set of pool keys must be generated before operating the stake pool. Luckily, Cardano-Tools provides an easy interface for this.

# ...using the ShelleyTools object created previously...

# Generate the pool keys.
pool_id = shelley.create_block_producing_keys(genesis_file, pool_name="TICKR")
print(f"TICKR Stake Pool ID: {pool_id}")

Files created:

  • TICKR.cert
  • TICKR.id
  • TICKR_cold.counter
  • TICKR_cold.skey
  • TICKR_cold.vkey
  • TICKR_vrf.skey
  • TICKR_vrf.vkey
  • TICKR_kes.skey
  • TICKR_kes.vkey

Note: It is recommended that these keys be generated offline if possible. However, the next step assumes access to a connected node to send a transaction. Do not store private keys on a network-connected server.

Register the Pool

Finally, the pool must be registered on the blockchain. The registration requires creating a pool certificate (containing all of the pool’s information), which must be signed and submitted to the blockchain as a transaction.

# ...using the ShelleyTools object created previously...

# Stakepool registration inputs
working_dir = Path("/home/lovelace/cardano-node/")
genesis_file = working_dir / "mainnet-shelley-genesis.json"
rewards_addr = open(working_dir / "rewards.addr", 'r').read()
pledge_ada = 10_000  # will convert to lovelace below
cost_ada = 500  # minimum is 340
margin = 3  # pool margin in percent
args = {
    "pool_name": "TICKR",
    "pool_pledge": pledge_ada*1_000_000,
    "pool_cost": cost_ada*1_000_000,
    "pool_margin": margin,
    "pool_cold_vkey": working_dir / "TICKR_cold.vkey",
    "pool_cold_skey": working_dir / "TICKR_cold.skey",
    "pool_vrf_key": working_dir / "TICKR_vrf.vkey",
    "pool_reward_vkey": working_dir / "rewards_stake.vkey",
    "owner_stake_vkeys": [
        working_dir / "owner1_stake.vkey",
        working_dir / "owner2_stake.vkey"
    ],
    "owner_stake_skeys": [
        working_dir / "owner1_stake.skey",
        working_dir / "owner2_stake.skey"
    ],
    "payment_addr": rewards_addr,
    "payment_skey": working_dir / "rewards.skey",
    "genesis_file": genesis_file,
    "pool_relays": [
        {
            "port": "3001",
            "host": "relay1.yourstakingbusinesswebsit.com",
            "host-type": "single"
        }
    ],
    "pool_metadata_url": "https://yourstakingbusinesswebsit/TICKR_metadata.json",
    "folder": working_dir
}

# Resister the stakepool on the blockchain
shelley.register_stake_pool(**args)

As in the above example, when the metadata file hash is not provided, the code will pull down the file from the URL and compute the hash on the fly.

Updating the Pool Registration

Inevitably, someone will need to update the information in their pool registration. To do this, we simply update the pool registration dictionary as above, only this time we use it with the update_stake_pool_registration function. We use this function because it does not pay the registration deposit of 500 ADA, which is only required during initial registration.

# ...using previously defined values....

# Update the stakepool resistration on the blockchain
shelley.update_stake_pool_registration(**args)

Summary

In this tutorial, we walked through the process of registering a stake pool using the Cardano-Tools library from beginning to end. As always, if you have questions, please contact us in our Telegram channel.