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
- Host metadata reliably - DRep metadata should be on IPFS or a reliable server
- Provide rationale - Always include anchors with vote rationale
- Verify hashes - Ensure anchor hashes match the content
- Stay active - DReps must vote regularly to remain active
- Test on testnet - Always test governance actions on Preprod/Preview first
- Document proposals - Provide clear documentation for governance actions