Skip to main content

Governance (Conway Era)

Complete support for Cardano's Conway-era governance features including DRep registration, voting, and proposal creation.

Overview

Cardano's Conway era introduces on-chain governance with:

  • DReps (Delegated Representatives) - Elected representatives who vote on governance actions
  • Voting - Cast votes on governance actions
  • Delegation - Delegate your voting power to DReps
  • Proposals - Submit governance actions for voting

Installation

import {
// DRep Metadata (CIP-119)
createDRepMetadata,
validateDRepMetadata,
// DRep Registration
registerDRep,
updateDRep,
unregisterDRep,
// Voting
castVote,
castMultipleVotes,
// Delegation
delegateVotingPower,
delegateStakeAndVotingPower,
// Governance Actions
proposeTreasuryWithdrawal,
proposeInfoAction,
proposeNoConfidence,
proposeCommitteeUpdate,
proposeParameterChange,
// Query Utilities
formatGovernanceActionId,
parseGovernanceActionId,
formatVoteTally,
calculateVotePercentages,
formatGovernanceActionType,
isValidGovernanceActionId,
// Anchor Utilities
validateAnchor,
createAnchor,
// Voting Power
formatVotingPower,
// Proposal Builder
ProposalBuilder,
createProposalBuilder,
// Constants
DEFAULT_DREP_DEPOSIT,
DEFAULT_GOV_ACTION_DEPOSIT,
DEFAULT_DREP_ACTIVITY,
MAX_ANCHOR_URL_LENGTH,
ANCHOR_DATA_HASH_LENGTH,
// Types
type GovernanceActionType,
type VoteOption,
type DRepType,
type DRepConfig,
type DRepMetadata,
type GovernanceActionId,
} from 'cardano-devkit';

DRep Registration

Creating DRep Metadata (CIP-119)

import { createDRepMetadata, validateDRepMetadata } from 'cardano-devkit';

const metadata = createDRepMetadata({
// Required fields
givenName: "Alice the DRep",

// Optional but recommended
bio: "Passionate about Cardano governance and decentralization.",
motivations: "To help guide Cardano's development with community interests in mind.",
qualifications: [
"Software engineer with 10 years experience",
"Active Cardano community member since 2018",
"Contributor to multiple Cardano projects",
],

// Contact/Social
email: "alice@example.com",
paymentAddress: "addr1...",
references: [
{ label: "Twitter", uri: "https://twitter.com/alice" },
{ label: "GitHub", uri: "https://github.com/alice" },
],

// Additional
imageUrl: "ipfs://Qm.../profile.jpg",
objectives: "Focus on treasury management and technical improvements",
});

// Validate metadata
const validation = validateDRepMetadata(metadata);
if (!validation.valid) {
console.error("Invalid metadata:", validation.errors);
}

Registering as a DRep

import { registerDRep } from 'cardano-devkit';

// Register with metadata anchor
const registerTx = await registerDRep(lucid, {
anchorUrl: "https://example.com/drep-metadata.json",
anchorHash: "abc123...", // Hash of the metadata JSON
});

// Sign and submit
const signedTx = await registerTx.sign.withWallet().complete();
const txHash = await signedTx.submit();
console.log("DRep registered:", txHash);

Updating DRep Info

import { updateDRep } from 'cardano-devkit';

const updateTx = await updateDRep(lucid, {
anchorUrl: "https://example.com/drep-metadata-v2.json",
anchorHash: "def456...",
});

const signedTx = await updateTx.sign.withWallet().complete();
await signedTx.submit();

Unregistering as DRep

import { unregisterDRep } from 'cardano-devkit';

const unregisterTx = await unregisterDRep(lucid);
const signedTx = await unregisterTx.sign.withWallet().complete();
await signedTx.submit();

Voting

Casting a Vote

import { castVote } from 'cardano-devkit';

// Vote on a governance action
const voteTx = await castVote(lucid, {
actionId: {
txHash: "abc123...",
index: 0,
},
vote: "yes", // "yes" | "no" | "abstain"
anchor: {
url: "https://example.com/vote-rationale.json",
hash: "xyz789...",
},
});

const signedTx = await voteTx.sign.withWallet().complete();
await signedTx.submit();

Vote Options

type VoteOption = "yes" | "no" | "abstain";

Casting Multiple Votes

import { castMultipleVotes } from 'cardano-devkit';

const votes = [
{
actionId: { txHash: "abc...", index: 0 },
vote: "yes" as const,
},
{
actionId: { txHash: "def...", index: 1 },
vote: "no" as const,
},
];

const voteTx = await castMultipleVotes(lucid, votes);

Delegation

Delegate Voting Power to DRep

import { delegateVotingPower } from 'cardano-devkit';

// Delegate to a specific DRep
const delegateTx = await delegateVotingPower(lucid, {
type: "drep",
drepId: "drep1abc123...",
});

// Delegate to "Always Abstain"
const abstainTx = await delegateVotingPower(lucid, {
type: "alwaysAbstain",
});

// Delegate to "Always No Confidence"
const noConfidenceTx = await delegateVotingPower(lucid, {
type: "alwaysNoConfidence",
});

Delegate Stake and Voting Together

import { delegateStakeAndVotingPower } from 'cardano-devkit';

// Delegate both stake and voting in one transaction
const tx = await delegateStakeAndVotingPower(lucid, {
poolId: "pool1...",
votingDelegation: {
type: "drep",
drepId: "drep1...",
},
});

Governance Proposals

Using ProposalBuilder

import { ProposalBuilder, createProposalBuilder } from 'cardano-devkit';

const proposal = createProposalBuilder()
.setProposer("addr_test1...")
.setAnchor(
"https://example.com/proposal.json",
"abc123..." // Hash of proposal document
)
.addTreasuryWithdrawal("addr_test1treasury...", 1_000_000_000n)
.setReturnAddress("addr_test1return...")
.build();

Treasury Withdrawal

import { proposeTreasuryWithdrawal } from 'cardano-devkit';

const withdrawalTx = await proposeTreasuryWithdrawal(lucid, {
amount: 1_000_000_000n, // 1000 ADA
recipient: "addr1...",
anchor: {
url: "https://example.com/treasury-proposal.json",
hash: "...",
},
returnAddress: "addr1...", // Where to return deposit if rejected
});

Info Action (Non-Binding)

import { proposeInfoAction } from 'cardano-devkit';

// Submit a non-binding info action for community sentiment
const infoTx = await proposeInfoAction(lucid, {
anchor: {
url: "https://example.com/info-proposal.json",
hash: "...",
},
returnAddress: "addr1...",
});

Motion of No Confidence

import { proposeNoConfidence } from 'cardano-devkit';

const noConfidenceTx = await proposeNoConfidence(lucid, {
anchor: {
url: "https://example.com/no-confidence.json",
hash: "...",
},
returnAddress: "addr1...",
});

Committee Update

import { proposeCommitteeUpdate } from 'cardano-devkit';

const committeeTx = await proposeCommitteeUpdate(lucid, {
membersToAdd: [
{ coldKey: "key1...", hotKey: "key2...", term: 365 },
],
membersToRemove: ["key3..."],
threshold: { numerator: 2, denominator: 3 }, // 2/3 majority
anchor: {
url: "https://example.com/committee-proposal.json",
hash: "...",
},
returnAddress: "addr1...",
});

Parameter Change

import { proposeParameterChange } from 'cardano-devkit';

const paramTx = await proposeParameterChange(lucid, {
parameters: {
minPoolCost: 170_000_000n,
// Other protocol parameters...
},
anchor: {
url: "https://example.com/param-change.json",
hash: "...",
},
returnAddress: "addr1...",
});

Query Utilities

Format Governance Action ID

import { formatGovernanceActionId } from 'cardano-devkit';

const actionId = { txHash: "abc123...", index: 0 };
const formatted = formatGovernanceActionId(actionId);
// "abc123...#0"

Parse Governance Action ID

import { parseGovernanceActionId } from 'cardano-devkit';

const parsed = parseGovernanceActionId("abc123...#0");
// { txHash: "abc123...", index: 0 }

Format Vote Tally

import { formatVoteTally } from 'cardano-devkit';

const tally = {
yes: 1_000_000_000_000n,
no: 500_000_000_000n,
abstain: 200_000_000_000n,
};

const formatted = formatVoteTally(tally);
// "Yes: 1,000,000 ADA | No: 500,000 ADA | Abstain: 200,000 ADA"

Calculate Vote Percentages

import { calculateVotePercentages } from 'cardano-devkit';

const percentages = calculateVotePercentages(tally);
// { yes: 58.82, no: 29.41, abstain: 11.76 }

Validate Governance Action ID

import { isValidGovernanceActionId } from 'cardano-devkit';

isValidGovernanceActionId("abc123...#0"); // true
isValidGovernanceActionId("invalid"); // false

Anchor Utilities

Validate Anchor

import { validateAnchor } from 'cardano-devkit';

const validation = validateAnchor({
url: "https://example.com/metadata.json",
hash: "abc123...",
});

if (!validation.valid) {
console.error("Invalid anchor:", validation.errors);
}

Create Anchor

import { createAnchor } from 'cardano-devkit';

// Create anchor from URL and content
const anchor = await createAnchor(
"https://example.com/metadata.json",
metadataContent // JSON object
);

// anchor.url and anchor.hash are ready to use

Voting Power

Format Voting Power

import { formatVotingPower } from 'cardano-devkit';

const power = 5_000_000_000_000n; // 5 million ADA
const formatted = formatVotingPower(power);
// "5,000,000 ADA"

Types

GovernanceActionType

type GovernanceActionType =
| "TreasuryWithdrawal"
| "ParameterChange"
| "HardForkInitiation"
| "NoConfidence"
| "NewCommittee"
| "NewConstitution"
| "InfoAction";

DRepType

type DRepType =
| "drep"
| "alwaysAbstain"
| "alwaysNoConfidence";

DRepConfig

interface DRepConfig {
type: DRepType;
drepId?: string; // Required when type is "drep"
}

GovernanceActionId

interface GovernanceActionId {
txHash: string;
index: number;
}

VoteInfo

interface VoteInfo {
actionId: GovernanceActionId;
vote: VoteOption;
anchor?: {
url: string;
hash: string;
};
}

Constants

// Default deposits
const DEFAULT_DREP_DEPOSIT = 500_000_000n; // 500 ADA
const DEFAULT_GOV_ACTION_DEPOSIT = 100_000_000_000n; // 100,000 ADA

// DRep activity period
const DEFAULT_DREP_ACTIVITY = 20; // epochs

// Anchor limits
const MAX_ANCHOR_URL_LENGTH = 128;
const ANCHOR_DATA_HASH_LENGTH = 32; // bytes

Complete Example

import {
createDevKit,
createDRepMetadata,
registerDRep,
castVote,
delegateVotingPower,
ProposalBuilder,
} from 'cardano-devkit';

async function governanceWorkflow() {
const devKit = createDevKit({ network: 'Preprod' });
const lucid = await devKit.init();

// Step 1: Create DRep metadata
const metadata = createDRepMetadata({
givenName: "Community DRep",
bio: "Representing the community's interests",
motivations: "Transparent and accountable governance",
});

// Upload metadata to IPFS and get hash
const metadataUrl = "ipfs://Qm.../metadata.json";
const metadataHash = "abc123...";

// Step 2: Register as DRep
const regTx = await registerDRep(lucid, {
anchorUrl: metadataUrl,
anchorHash: metadataHash,
});
await regTx.sign.withWallet().complete().then(tx => tx.submit());

// Step 3: Vote on proposals
const voteTx = await castVote(lucid, {
actionId: { txHash: "proposal-tx-hash", index: 0 },
vote: "yes",
anchor: {
url: "https://example.com/vote-rationale.json",
hash: "rationale-hash",
},
});
await voteTx.sign.withWallet().complete().then(tx => tx.submit());

// Step 4: Users can delegate to this DRep
// (from another wallet)
const delegateTx = await delegateVotingPower(lucid, {
type: "drep",
drepId: "drep1...",
});
await delegateTx.sign.withWallet().complete().then(tx => tx.submit());
}

Best Practices

  1. Host metadata reliably - DRep metadata should be on IPFS or a reliable server
  2. Provide rationale - Always include anchors with vote rationale
  3. Verify hashes - Ensure anchor hashes match the content
  4. Stay active - DReps must vote regularly to remain active
  5. Test on testnet - Always test governance actions on Preprod/Preview first
  6. Document proposals - Provide clear documentation for governance actions