Mesh LogoMesh

Governance Transactions

Participate in Cardano's on-chain governance through DRep delegation, registration, and voting.

Overview

Cardano's on-chain governance system (CIP-1694) enables the community to vote on proposals and protocol updates. You can delegate your voting power to Decentralized Representatives (DReps), register as a DRep yourself, and vote on governance actions.

When to use this:

  • Delegating your voting power to a DRep
  • Registering as a DRep to represent other voters
  • Voting on governance actions and protocol changes
  • Building governance dashboards and tools
  • Creating DRep management applications

Quick Start

import { MeshTxBuilder, BlockfrostProvider } from "@meshsdk/core";

// Initialize
const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

// Get wallet data
const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();
const rewardAddresses = await wallet.getRewardAddresses();
const rewardAddress = rewardAddresses[0]!;

// Delegate to a DRep
txBuilder
  .voteDelegationCertificate(
    { dRepId: "drep1yv4uesaj92wk8ljlsh4p7jzndnzrflchaz5fzug3zxg4naqkpeas3" },
    rewardAddress
  )
  .changeAddress(changeAddress)
  .selectUtxosFrom(utxos);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

API Reference

voteDelegationCertificate()

Delegate voting power to a DRep.

.voteDelegationCertificate(drep: DRepId, rewardAddress: string)
ParameterTypeDescription
drepDRepIdDRep identifier object
rewardAddressstringYour stake/reward address

The DRepId object can specify:

  • { dRepId: "drep1..." } - Delegate to a specific DRep
  • { alwaysAbstain: true } - Always abstain from voting
  • { alwaysNoConfidence: true } - Always vote no confidence

drepRegistrationCertificate()

Register as a DRep.

.drepRegistrationCertificate(dRepId: string, anchor?: Anchor)
ParameterTypeDescription
dRepIdstringYour DRep ID (from wallet)
anchorAnchorOptional metadata anchor

The Anchor object:

{
  anchorUrl: string;      // URL to metadata JSON
  anchorDataHash: string; // Hash of metadata content
}

drepUpdateCertificate()

Update DRep metadata.

.drepUpdateCertificate(dRepId: string, anchor?: Anchor)
ParameterTypeDescription
dRepIdstringYour DRep ID
anchorAnchorNew metadata anchor

drepDeregistrationCertificate()

Retire as a DRep and reclaim deposit.

.drepDeregistrationCertificate(dRepId: string)
ParameterTypeDescription
dRepIdstringYour DRep ID to retire

vote()

Cast a vote on a governance action.

.vote(voter: Voter, govActionId: GovActionId, votingProcedure: VotingProcedure)
ParameterTypeDescription
voterVoterVoter identity
govActionIdGovActionIdGovernance action reference
votingProcedureVotingProcedureVote details

The Voter object:

// DRep voter
{ type: "DRep", drepId: "drep1..." }

// Stake pool operator
{ type: "StakePool", poolId: "pool1..." }

// Constitutional committee member
{ type: "ConstitutionalCommittee", hotCred: "..." }

The VotingProcedure object:

{
  voteKind: "Yes" | "No" | "Abstain";
  anchor?: {
    anchorUrl: string;
    anchorDataHash: string;
  };
}

votePlutusScriptV3()

Indicate voting with a Plutus script.

.votePlutusScriptV3()

voteScript()

Provide the voting script.

.voteScript(scriptCbor: string)

voteRedeemerValue()

Provide the redeemer for script-based voting.

.voteRedeemerValue(redeemer: Data | object | string, type?: "Mesh" | "CBOR" | "JSON", exUnits?: Budget)

Common Patterns

Delegate to a DRep

Delegate your voting power to a registered DRep:

import { MeshTxBuilder, BlockfrostProvider } from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();
const rewardAddresses = await wallet.getRewardAddresses();
const rewardAddress = rewardAddresses[0]!;

const dRepId = "drep1yv4uesaj92wk8ljlsh4p7jzndnzrflchaz5fzug3zxg4naqkpeas3";

txBuilder
  .voteDelegationCertificate({ dRepId }, rewardAddress)
  .changeAddress(changeAddress)
  .selectUtxosFrom(utxos);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

console.log(`Delegated to DRep: ${txHash}`);

Delegate to Always Abstain

Configure your stake to always abstain from voting:

import { MeshTxBuilder, BlockfrostProvider } from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();
const rewardAddresses = await wallet.getRewardAddresses();
const rewardAddress = rewardAddresses[0]!;

txBuilder
  .voteDelegationCertificate({ alwaysAbstain: true }, rewardAddress)
  .changeAddress(changeAddress)
  .selectUtxosFrom(utxos);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

Register as a DRep

Register yourself as a DRep with metadata:

import { MeshTxBuilder, BlockfrostProvider, hashDrepAnchor } from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

// Get DRep ID from wallet
const dRep = await wallet.getDRep();
const dRepId = dRep.dRepIDCip105;

// Prepare metadata anchor
const anchorUrl = "https://example.com/drep-metadata.jsonld";
const anchorMetadata = {
  "@context": {
    "@language": "en",
    "CIP100": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md",
    "CIP119": "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0119/README.md",
    "hashAlgorithm": "CIP100:hashAlgorithm",
    "body": { "@id": "CIP119:body" },
    "givenName": "CIP119:givenName"
  },
  "hashAlgorithm": "blake2b-256",
  "body": {
    "givenName": "My DRep Name"
  }
};
const anchorHash = hashDrepAnchor(anchorMetadata);

// Get wallet data
const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();

txBuilder
  .drepRegistrationCertificate(dRepId, {
    anchorUrl,
    anchorDataHash: anchorHash,
  })
  .changeAddress(changeAddress)
  .selectUtxosFrom(utxos);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

console.log(`DRep registered: ${txHash}`);

Update DRep Metadata

Update your DRep profile information:

import { MeshTxBuilder, BlockfrostProvider, hashDrepAnchor } from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

const dRep = await wallet.getDRep();
const dRepId = dRep.dRepIDCip105;

// New metadata
const anchorUrl = "https://example.com/drep-metadata-v2.jsonld";
const anchorMetadata = { /* updated metadata */ };
const anchorHash = hashDrepAnchor(anchorMetadata);

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();

txBuilder
  .drepUpdateCertificate(dRepId, {
    anchorUrl,
    anchorDataHash: anchorHash,
  })
  .changeAddress(changeAddress)
  .selectUtxosFrom(utxos);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

console.log(`DRep updated: ${txHash}`);

Retire as DRep

Retire from being a DRep and reclaim your deposit:

import { MeshTxBuilder, BlockfrostProvider } from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

const dRep = await wallet.getDRep();
const dRepId = dRep.dRepIDCip105;

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();

txBuilder
  .drepDeregistrationCertificate(dRepId)
  .selectUtxosFrom(utxos)
  .changeAddress(changeAddress);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

console.log(`DRep retired: ${txHash}`);

Vote on Governance Action

Cast a vote as a DRep:

import { MeshTxBuilder, BlockfrostProvider } from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

const dRep = await wallet.getDRep();
const dRepId = dRep.dRepIDCip105;

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();

// Governance action to vote on
const govActionTxHash = "aff2909f8175ee02a8c1bf96ff516685d25bf0c6b95aac91f4dfd53a5c0867cc";
const govActionIndex = 0;

txBuilder
  .vote(
    {
      type: "DRep",
      drepId: dRepId,
    },
    {
      txHash: govActionTxHash,
      txIndex: govActionIndex,
    },
    {
      voteKind: "Yes",
    }
  )
  .selectUtxosFrom(utxos)
  .changeAddress(changeAddress);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

console.log(`Vote cast: ${txHash}`);

Vote with Rationale

Include reasoning for your vote:

import { MeshTxBuilder, BlockfrostProvider, hashDrepAnchor } from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

const dRep = await wallet.getDRep();
const dRepId = dRep.dRepIDCip105;

const utxos = await wallet.getUtxos();
const changeAddress = await wallet.getChangeAddress();

// Prepare vote rationale
const rationaleUrl = "https://example.com/vote-rationale.jsonld";
const rationaleContent = {
  rationale: "I support this proposal because...",
  references: ["https://example.com/supporting-doc"],
};
const rationaleHash = hashDrepAnchor(rationaleContent);

txBuilder
  .vote(
    {
      type: "DRep",
      drepId: dRepId,
    },
    {
      txHash: "abc123...",
      txIndex: 0,
    },
    {
      voteKind: "Yes",
      anchor: {
        anchorUrl: rationaleUrl,
        anchorDataHash: rationaleHash,
      },
    }
  )
  .selectUtxosFrom(utxos)
  .changeAddress(changeAddress);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);

Vote with Plutus Script

Vote using a Plutus script DRep:

import {
  MeshTxBuilder,
  BlockfrostProvider,
  resolveScriptHash,
  resolveScriptHashDRepId,
  applyCborEncoding
} from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");
const txBuilder = new MeshTxBuilder({
  fetcher: provider,
  verbose: true,
});

const utxos = await wallet.getUtxos();
const collateral = await wallet.getCollateral();
const changeAddress = await wallet.getChangeAddress();

// Your voting script
const votingScriptCbor = applyCborEncoding("your-voting-script-cbor");
const scriptHash = resolveScriptHash(votingScriptCbor, "V3");
const scriptDRepId = resolveScriptHashDRepId(scriptHash);

txBuilder
  .txIn(
    utxos[0]!.input.txHash,
    utxos[0]!.input.outputIndex,
    utxos[0]!.output.amount,
    utxos[0]!.output.address
  )
  .txInCollateral(
    collateral[0]!.input.txHash,
    collateral[0]!.input.outputIndex,
    collateral[0]!.output.amount,
    collateral[0]!.output.address
  )
  .votePlutusScriptV3()
  .vote(
    {
      type: "DRep",
      drepId: scriptDRepId,
    },
    {
      txHash: "abc123...",
      txIndex: 0,
    },
    {
      voteKind: "Yes",
    }
  )
  .voteScript(votingScriptCbor)
  .voteRedeemerValue("")
  .changeAddress(changeAddress);

const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);

Complete Example

This example demonstrates a complete DRep lifecycle:

import {
  MeshTxBuilder,
  BlockfrostProvider,
  hashDrepAnchor
} from "@meshsdk/core";

const provider = new BlockfrostProvider("<YOUR_API_KEY>");

function getTxBuilder() {
  return new MeshTxBuilder({
    fetcher: provider,
    verbose: true,
  });
}

// Register as a DRep
async function registerDRep(
  wallet: any,
  name: string,
  metadataUrl: string
) {
  const txBuilder = getTxBuilder();

  const dRep = await wallet.getDRep();
  const dRepId = dRep.dRepIDCip105;

  const metadata = {
    "@context": { /* CIP-119 context */ },
    "body": { "givenName": name }
  };
  const anchorHash = hashDrepAnchor(metadata);

  const utxos = await wallet.getUtxos();
  const changeAddress = await wallet.getChangeAddress();

  txBuilder
    .drepRegistrationCertificate(dRepId, {
      anchorUrl: metadataUrl,
      anchorDataHash: anchorHash,
    })
    .changeAddress(changeAddress)
    .selectUtxosFrom(utxos);

  const unsignedTx = await txBuilder.complete();
  const signedTx = await wallet.signTx(unsignedTx);
  return await wallet.submitTx(signedTx);
}

// Vote on a governance action
async function castVote(
  wallet: any,
  govActionTxHash: string,
  govActionIndex: number,
  voteKind: "Yes" | "No" | "Abstain"
) {
  const txBuilder = getTxBuilder();

  const dRep = await wallet.getDRep();
  const dRepId = dRep.dRepIDCip105;

  const utxos = await wallet.getUtxos();
  const changeAddress = await wallet.getChangeAddress();

  txBuilder
    .vote(
      { type: "DRep", drepId: dRepId },
      { txHash: govActionTxHash, txIndex: govActionIndex },
      { voteKind }
    )
    .selectUtxosFrom(utxos)
    .changeAddress(changeAddress);

  const unsignedTx = await txBuilder.complete();
  const signedTx = await wallet.signTx(unsignedTx);
  return await wallet.submitTx(signedTx);
}

// Delegate to a DRep
async function delegateToDRep(wallet: any, dRepId: string) {
  const txBuilder = getTxBuilder();

  const utxos = await wallet.getUtxos();
  const changeAddress = await wallet.getChangeAddress();
  const rewardAddresses = await wallet.getRewardAddresses();
  const rewardAddress = rewardAddresses[0]!;

  txBuilder
    .voteDelegationCertificate({ dRepId }, rewardAddress)
    .changeAddress(changeAddress)
    .selectUtxosFrom(utxos);

  const unsignedTx = await txBuilder.complete();
  const signedTx = await wallet.signTx(unsignedTx);
  return await wallet.submitTx(signedTx);
}

// Retire as DRep
async function retireDRep(wallet: any) {
  const txBuilder = getTxBuilder();

  const dRep = await wallet.getDRep();
  const dRepId = dRep.dRepIDCip105;

  const utxos = await wallet.getUtxos();
  const changeAddress = await wallet.getChangeAddress();

  txBuilder
    .drepDeregistrationCertificate(dRepId)
    .selectUtxosFrom(utxos)
    .changeAddress(changeAddress);

  const unsignedTx = await txBuilder.complete();
  const signedTx = await wallet.signTx(unsignedTx);
  return await wallet.submitTx(signedTx);
}

// Usage
async function main() {
  // 1. Register as DRep
  const regTx = await registerDRep(
    wallet,
    "Community DRep",
    "https://example.com/metadata.jsonld"
  );
  console.log(`Registered as DRep: ${regTx}`);

  // 2. Vote on proposals
  const voteTx = await castVote(
    wallet,
    "proposal-tx-hash...",
    0,
    "Yes"
  );
  console.log(`Vote cast: ${voteTx}`);

  // 3. Others delegate to you
  const delegateTx = await delegateToDRep(
    otherWallet,
    "drep1..." // Your DRep ID
  );
  console.log(`Delegation received: ${delegateTx}`);

  // 4. Eventually retire
  const retireTx = await retireDRep(wallet);
  console.log(`Retired as DRep: ${retireTx}`);
}

Troubleshooting

"DRep not registered" error

  • You must register as a DRep before voting
  • Use drepRegistrationCertificate() first

"Invalid anchor hash" error

  • The hash must match the content at the URL exactly
  • Use hashDrepAnchor() to compute the correct hash
  • Ensure the metadata file is publicly accessible

"Insufficient deposit" error

  • DRep registration requires a deposit (check current protocol parameters)
  • Ensure wallet has enough ADA for deposit + fees

"DRep inactive" error

  • DReps must vote regularly to stay active
  • Vote on governance actions or submit an update certificate

"Governance action not found" error

  • Verify the transaction hash and index are correct
  • The governance action may have already been resolved

"Invalid voter type" error

  • Ensure the voter type matches your role (DRep, SPO, or CC)
  • Use the correct identifier format for each type

On this page