Governance Guide

The Voltaire era introduces a decentralized governance system to Cardano, allowing the community to shape the future of the platform. PyCardano provides a complete set of tools to participate in this on-chain governance, from registering as a Delegate Representative (DRep) to proposing and voting on governance actions.

This guide will walk you through a practical example of the governance lifecycle.

Key Concepts

Before diving into the code, let’s review the core components of Cardano’s governance model that we’ll be interacting with:

  • Delegate Representative (DRep): An on-chain identity that can receive delegated voting power from other ada holders. DReps are responsible for voting on governance actions on behalf of their delegators.

  • Vote Delegation: The process by which ada holders assign their voting power to a DRep. This allows them to participate in governance without having to vote on every proposal themselves.

  • Governance Actions: Proposals that can be submitted to the chain for a vote. These can range from protocol parameter changes to treasury withdrawals. This guide uses a simple InfoAction, which has no on-chain effect but is useful for demonstrating the proposal and voting mechanism.

  • Voting Procedures: The act of a DRep (or other voter type) casting a vote (Yes, No, or Abstain) on a specific governance action.

Governance Tutorial: A Walkthrough

This tutorial demonstrates a complete governance flow using the example script found at examples/more_examples/13_governance.py. We will:

  1. Create and register a DRep.

  2. Delegate voting power from a separate stake key to that DRep.

  3. Propose a new InfoAction to the blockchain.

  4. Have the DRep vote on the action.

Prerequisites

To follow along, you will need:

  • A BlockFrostChainContext, which requires a Blockfrost Project ID. See the Transaction Guide guide for setup instructions.

  • A funded wallet with a local payment.skey file. The script assumes this key will fund the creation of new addresses required for the tutorial.

  • An environment configured to point to your keys and Blockfrost project ID.

Step 0: Setup and Initialization

First, we set up our environment. This includes initializing a BlockFrostChainContext to interact with the blockchain and loading our main payment key, which will be used to fund the transactions.

from pycardano import (
    # Core
    Network,
    BlockFrostChainContext,
    TransactionBuilder,
    # Keys & Addresses
    PaymentSigningKey,
    StakeKeyPair,
    Address,
    # Certificates
    RegDRepCert,
    StakeRegistrationAndVoteDelegation,
    DRepCredential,
    StakeCredential,
    DRep,
    DRepKind,
    # Governance
    GovActionId,
    InfoAction,
    Voter,
    VoterType,
    Vote,
    Anchor,
    AnchorDataHash,
)

# Initialize chain context
network = Network.TESTNET
context = BlockFrostChainContext("your_blockfrost_project_id", base_url=ApiUrls.preprod.value)

# Load main payment key and derive address
main_payment_skey = PaymentSigningKey.load("path/to/payment.skey")
main_payment_vkey = main_payment_skey.to_verification_key()
main_address = Address(main_payment_vkey.hash(), network=network)

Step 1: Creating and Registering a DRep

Our first on-chain action is to register a new DRep. This requires a new stake key pair that will represent the DRep’s identity.

The registration is done via a RegDRepCert certificate, which includes the DRep’s credential (the hash of its stake verification key), a deposit, and an optional Anchor for off-chain metadata.

# 1.1 Generate or load DRep stake key pair
drep_stake_key_pair = StakeKeyPair.generate()

# 1.2 Create DRep address
drep_address = Address(
    payment_part=main_payment_vkey.hash(),
    staking_part=drep_stake_key_pair.verification_key.hash(),
    network=network,
)
# The drep_address must be funded before it can submit the registration.

# 1.4 Create DRep registration certificate
drep_credential = DRepCredential(drep_stake_key_pair.verification_key.hash())
anchor = Anchor(
    url="https://pycardano.drep.example.com/info",
    data_hash=AnchorDataHash(bytes.fromhex("00" * 32)),
)
drep_registration_cert = RegDRepCert(
    drep_credential=drep_credential,
    coin=500_000_000, # 500 ADA deposit
    anchor=anchor,
)

# 1.5 Build and submit transaction
builder = TransactionBuilder(context)
builder.add_input_address(str(drep_address))
builder.certificates = [drep_registration_cert]

# The transaction must be signed by the payment key controlling the UTxOs
# and the DRep's stake key to authorize the registration.
signed_tx = builder.build_and_sign(
    signing_keys=[main_payment_skey, drep_stake_key_pair.signing_key],
    change_address=drep_address,
)

context.submit_tx(signed_tx)

Step 2: Delegating Voting Power to the DRep

With a registered DRep, other ada holders can now delegate their voting power to it. In this step, we’ll create a new stake identity for our main wallet and delegate its voting power to the DRep.

We use the powerful StakeRegistrationAndVoteDelegation certificate, which handles both stake key registration (and its associated deposit) and vote delegation in a single certificate.

# 2.1 Generate a new stake key for the main account's voting delegation
main_voting_stake_key_pair = StakeKeyPair.generate()

# 2.4 Create StakeRegistrationAndVoteDelegation certificate
stake_credential = StakeCredential(main_voting_stake_key_pair.verification_key.hash())

# Create a DRep object pointing to the DRep we created in Step 2
drep_for_delegation = DRep(
    kind=DRepKind.VERIFICATION_KEY_HASH,
    credential=drep_stake_key_pair.verification_key.hash()
)

delegation_cert = StakeRegistrationAndVoteDelegation(
    stake_credential=stake_credential,
    drep=drep_for_delegation,
    coin=2_000_000, # 2 ADA stake key registration deposit
)

# Build and sign the transaction. The new voting stake key must sign
# to authorize the delegation.
builder = TransactionBuilder(context)
# The address paying for this must contain the new stake part.
# It must be funded first.
main_full_address = Address(
    payment_part=main_payment_vkey.hash(),
    staking_part=main_voting_stake_key_pair.verification_key.hash(),
    network=network,
)
builder.add_input_address(str(main_full_address))
builder.certificates = [delegation_cert]

signed_tx = builder.build_and_sign(
    signing_keys=[main_payment_skey, main_voting_stake_key_pair.signing_key],
    change_address=main_full_address,
)

context.submit_tx(signed_tx)

Step 3: Proposing a Governance Action

Now, let’s propose a governance action. The DRep itself will act as the proposer. Proposing an action requires a deposit, which is returned when the action is finalized.

We use TransactionBuilder.add_proposal to construct the proposal, specifying the deposit, a reward account for the deposit’s return, the governance action itself (InfoAction), and an anchor.

# 3.2 Create InfoAction
info_action = InfoAction()

# 3.3 Define anchor for the proposal
proposal_anchor = Anchor(
    url="https://pycardano.infoaction.example.com/details",
    data_hash=AnchorDataHash(bytes.fromhex("11" * 32)),
)

# 3.4 The reward account for the proposal deposit refund.
reward_account_address = Address(
    staking_part=drep_stake_key_pair.verification_key.hash(),
    network=context.network
)

# 3.5 Build and submit transaction
builder = TransactionBuilder(context)
builder.add_input_address(str(drep_address)) # DRep's address pays the proposal deposit
builder.add_proposal(
    deposit=100_000_000_000, # Example: 100k ADA deposit
    reward_account=bytes(reward_account_address),
    gov_action=info_action,
    anchor=proposal_anchor,
)

# Sign with the payment key and the DRep's stake key as the proposer.
signed_tx = builder.build_and_sign(
    signing_keys=[main_payment_skey, drep_stake_key_pair.signing_key],
    change_address=drep_address,
)
context.submit_tx(signed_tx)

# The GovActionId is derived from the transaction ID and the proposal's index
gov_action_id = GovActionId(transaction_id=signed_tx.id, gov_action_index=0)

Step 4: Voting on a Governance Action

In the final step, our DRep votes on the governance action. A Voter object is created to represent the DRep. The vote is then added to a transaction using TransactionBuilder.add_vote.

# 4.2 Create Voter object for the DRep
drep_voter = Voter(
    credential=drep_stake_key_pair.verification_key.hash(),
    voter_type=VoterType.DREP,
)

# 4.3 Reconstruct GovActionId from the previous step
gov_action_id = GovActionId(
    transaction_id=TransactionId.from_hex("..."), # From Step 4
    gov_action_index=0,
)

# 4.5 Build transaction with the vote
builder = TransactionBuilder(context)
builder.add_input_address(str(drep_address)) # DRep's address pays for the vote tx
builder.add_vote(
    voter=drep_voter,
    gov_action_id=gov_action_id,
    vote=Vote.YES,
    anchor=vote_anchor,
)

# Sign with the payment key and the DRep's stake key to authorize the vote.
signed_tx = builder.build_and_sign(
    signing_keys=[main_payment_skey, drep_stake_key_pair.signing_key],
    change_address=drep_address,
)
context.submit_tx(signed_tx)

This concludes the basic lifecycle of DRep creation, delegation, and governance participation. You can adapt these patterns to handle other governance actions and more complex scenarios.

Full Example

A complete, runnable script demonstrating these steps with helper functions for state management and funding is available at examples/more_examples/13_governance.py.