Tenzro Testnet is live —request testnet TNZO
← Back to Tutorials

On-Chain Governance: Propose and Vote

GovernanceIntermediate25 min

Tenzro's GovernanceEngine is stake-weighted and deposit-gated. Any address can create a proposal by posting a 10,000 TNZO deposit, voting weight comes from direct TNZO stake plus inbound delegations, and a proposal passes once it meets quorum (20% participation, 50% approval, 1,000 TNZO minimum). This tutorial walks the full lifecycle.

What You'll Build

  • A parameter-change proposal with a 7-day voting period
  • Voting power lookup against the on-chain StakingManager
  • Casting For / Against / Abstain votes
  • Delegating voting power to another address

Proposal Types

Supported proposal types

  parameter_change   tune on-chain parameters; requires `parameter` and `new_value` fields
  treasury_grant     spend from NetworkTreasury; requires `grant_amount` field
  protocol_upgrade   schedule a binary upgrade; requires `version` and 32-byte `code_hash` (SHA-256 hex)
  text               non-binding signalling vote (default if proposal_type omitted)

Tally Rules

Tally rules (GovernanceEngine defaults  QuorumRequirements)

  minimum_participation = 20% of total stake-weighted supply
  minimum_approval      = 50% of (votes_for + votes_against)
  minimum_votes         = 1000 TNZO absolute floor
  proposer_stake (CLI minimum) = 10,000 TNZO

When the voting period ends and the tally meets all three thresholds,
the proposal transitions to Passed. ParameterChange / TreasuryGrant /
ProtocolUpgrade payloads are recorded on the proposal; node operators
and downstream subsystems consume them  there is no public
"executeProposal" RPC.

Sources of voting weight on a vote:
  * direct TNZO stake via StakingManager.get_stake(voter)
  * delegations received from other addresses (recorded via
    tenzro_delegateVotingPower)

Step 1: Create a Proposal

tenzro_createProposal({
  "title":         "Raise network fee from 0.5% to 0.75%",
  "description":   "Rationale: validator yield has compressed below target...",
  "proposer":      "0xabc123...",                  // Ed25519 address (proposer)
  "proposal_type": "parameter_change",             // parameter_change | treasury_grant | protocol_upgrade | text
  "parameter":     "settlement.network_fee_bps",
  "new_value":     "75",
  "voting_duration_ms": 604800000,                 // 7 days
  "proposer_stake":     10000                      // TNZO deposit
})
  -> {
    proposal_id: "prop-01HR...",
    title:       "Raise network fee from 0.5% to 0.75%",
    description: "Rationale...",
    proposer:    "0xabc123...",
    status:      "Active"
  }

Step 2: List Proposals

tenzro_listProposals({})
  -> [
    {
      proposal_id: "prop-01HR...",
      title:       "Raise network fee from 0.5% to 0.75%",
      type:        "parameter_change",
      status:      "Active",         // Pending | Active | Passed | Rejected | Executed | Expired
      proposer:    "0xabc123...",
      votes_for:     "4521000",
      votes_against: "1803000",
      ends_at:     "2026-04-27T12:00:00Z"
    }
  ]

Step 3: Check Voting Power

tenzro_getVotingPower({
  "address": "0xabc123..."           // Ed25519 address
})
  -> {
    address:      "0xabc123...",
    voting_power: "152340500000000000000000"   // raw u128 wei (TNZO * 1e18)
  }

# Voting power is read from the StakingManager  sum of bonded TNZO stake
# at the queried address. Delegated voting power (set via
# tenzro_delegateVotingPower) is accounted for during tally inside the
# GovernanceEngine.

Step 4: Cast a Vote

Each address gets one vote per proposal as long as it's still Active. Voting power is captured from your stake at the time the vote is cast:

tenzro_vote({
  "proposal_id": "prop-01HR...",
  "voter":       "0xabc123...",      // Ed25519 address
  "vote":        "For"               // For | Against | Abstain
})
  -> {
    success:      true,
    proposal_id:  "prop-01HR...",
    voter:        "0xabc123...",
    voting_power: "152340500000000000000000"
  }

# Alias: tenzro_voteOnProposal accepts the same params.

Step 5: Delegate (Optional)

If you don't want to vote directly, delegate to a trusted address. The delegatee's effective voting power on subsequent votes increases by the delegated amount:

tenzro_delegateVotingPower({
  "delegator":    "0xabc123...",     // your address
  "delegatee":    "0xdef456...",     // who receives the delegation
  "voting_power": "100000000000000000000000"   // u128 wei (100,000 TNZO)
})
  -> { success: true, delegator: "0xabc123...", delegatee: "0xdef456..." }

Step 6: CLI

# Create a proposal (default deposit 10,000 TNZO, 14-day voting period)
tenzro governance propose \
  "Raise network fee from 0.5% to 0.75%" \
  "Rationale: validator yield has compressed below target..." \
  --type parameter_change \
  --duration-days 7 \
  --deposit 10000

# List all proposals (table view)
tenzro governance list

# List with details
tenzro governance list --detailed

# Vote on a proposal (yes / no / abstain)
tenzro governance vote prop-01HR... yes --reason "Restores validator yield."

# Vote (alias)
tenzro governance vote-on prop-01HR... no

Step 7: TypeScript SDK

import { TenzroClient } from "@tenzro/sdk";

const client = new TenzroClient();

// 1. Check voting power before casting
const power = await client.governance.getVotingPower({
  address: "0xabc123...",
});
console.log("voting power (wei):", power.voting_power);

// 2. Walk all proposals
const proposals = await client.governance.listProposals({});
for (const p of proposals) {
  console.log(`[${p.proposal_id}] ${p.title} — for ${p.votes_for} / against ${p.votes_against} (${p.status})`);
}

// 3. Vote
await client.governance.vote({
  proposal_id: proposals[0].proposal_id,
  voter:       "0xabc123...",
  vote:        "For",
});

// 4. Delegate voting power to a trusted address
await client.governance.delegateVotingPower({
  delegator:    "0xabc123...",
  delegatee:    "0xdef456...",
  voting_power: "100000000000000000000000",   // 100,000 TNZO in wei
});

What happens after a proposal passes? The GovernanceEngine records the proposal's Passed status and persists the typed payload (parameter name + new value, treasury recipient + amount, protocol version + code hash). Parameter and treasury changes are picked up by the relevant subsystem the next time it reads its config; protocol upgrades coordinate node restarts via the code_hash. Tally and status transitions are observable via tenzro_listProposals.

What You Learned

Next Steps