Skip to content
    hazBasehazBaseDocs
    IMPLEMENTATION PATTERNS

    Community & governance

    Design proposals, voting and execution around participants and permissions.

    All implementation patterns
    EXAMPLE

    Connect community budget proposals, voting and execution

    A facility community proposes allocating 100 budget-token units to an activity coordinator. Members vote on executable recipient and amount data as well as the proposal text, then execute it after approval and a waiting period.

    Keep the proposal and execution aligned, with a record of votes, approval and the eventual allocation.

    Architecture and responsibilities

    1. Proposal and voting interface

      Collect target, amount and dates and display a readable view of the execution data.

      Web UI / Wallet
    2. Voting power and rules

      Configure voting tokens, delegation, thresholds and quorum.

      IVotes / Weight strategy
    3. Proposal lifecycle

      Manage proposal IDs, voting windows, results and state.

      GenericGovernorHelper
    4. Delayed execution

      Timelock calls the target contract after the delay.

      TimelockController / Target contract

    Example data model

    Map application records to the data managed by the chain or APIs. Field names and values below illustrate the application model for this architecture.

    Field and exampleStored inPurpose and mapping
    proposalIduint256 as stringChain and application DBProposal ID returned by Governor. Store it as a string rather than converting to a JavaScript number.
    targets / values / calldatastoken / 0 / transfer(...)ChainMatching indices define one call. values contains native-currency amounts.
    descriptionCommunity activity grantChain and application DBText used in proposal ID calculation. Keep it associated with the executable data.
    startTs / endTsUNIX secondsChainVoting start and end timestamps. Do not pass browser milliseconds directly.
    vote / supportproposalId / 1Chain0 is Against, 1 For and 2 Abstain. Voting power follows the configured strategy.
    executionHash / operationId0x… / timelock IDChain and application DBDistinguish proposal ID, Timelock operation ID and execution transaction hash.

    Processing flow

    1. Prepare governance and the budget

      Connect Governor, Timelock, an IVotes token and a weight strategy. The executing Timelock must hold the budget tokens and any required permissions.

      GenericGovernor / TimelockController / IVotes
    2. Include the exact action in the proposal

      Encode the recipient and 100 units as an ERC-20 transfer, then propose it with a voting window and description. Display the token, recipient and display amount, not only encoded bytes.

      Interface.encodeFunctionData → governor.propose
    3. Collect votes

      Eligible members vote while the proposal is Active. For ERC20Votes configurations, check delegation and historical voting power; current holdings alone may not establish voting power.

      state(proposalId) / castVote(proposalId, 1)
    4. Queue a successful proposal

      Queue after voting ends successfully. Approval, Queued and Executed are distinct states. Enable actions based on state, including defeated or canceled outcomes.

      queue(proposalId) → Timelock delay
    5. Execute after the delay

      Once the Timelock operation is Ready, an authorized executor calls execute. After confirmation, check the recipient’s balance and execution event and update the proposal view.

      Timelock.isOperationReady / governor.execute(proposalId)

    Before using the code

    • Deploy Governor, Timelock, voting token and weight strategy, then configure roles for proposals, queueing and execution.
    • Timelock must hold the budget tokens. Transfers must satisfy transferability, pause and allowlist conditions.
    • Use UNIX seconds based on the target chain’s clock. The end must follow the start and respect the allowed voting window.
    Install
    npm install --save-exact @hazbase/kit@0.9.0 ethers@6.16.0

    The TypeScript functions below are called from your application with the appropriate connections, permissions and data. Integrate the UI, persistence and recovery using the architecture above.

    Propose a 100-unit allocation

    Pass units: "100" to encode the allocation using the budget token’s decimals. This function creates the proposal; voting, queueing and execution are separate operations at later states.

    pattern-governance.ts
    import { GenericGovernorHelper, FlexibleTokenHelper } from '@hazbase/kit';
    import { Interface, parseUnits } from 'ethers';
    type Signer = Parameters<typeof GenericGovernorHelper.deploy>[1];
    
    export async function proposeCommunityGrant(input: {
      governorAddress: string;
      budgetTokenAddress: string;
      recipient: string;
      units: string;
      description: string;
      startTs: bigint;
      endTs: bigint;
      proposer: Signer;
    }) {
      const governor = GenericGovernorHelper.attach(input.governorAddress, input.proposer);
      const token = FlexibleTokenHelper.attach(input.budgetTokenAddress, input.proposer);
      const amount = parseUnits(input.units, await token.decimals());
      if (amount <= 0n) throw new Error('Grant must be positive.');
      const erc20 = new Interface(['function transfer(address to, uint256 amount)']);
    
      // At execution, Timelock must hold the budget tokens being transferred.
      const proposalId = await governor.propose({
        proposer: await input.proposer.getAddress(),
        targets: [input.budgetTokenAddress],
        values: [0n],
        calldatas: [erc20.encodeFunctionData('transfer', [input.recipient, amount])],
        description: input.description,
        startTs: input.startTs,
        endTs: input.endTs,
      });
    
      // Voting, queueing and execution are separate actions at later states.
      return { proposalId: proposalId.toString(), state: await governor.state(proposalId) };
    }
    
    Download the example

    Result and application integration

    Store proposalId as a string and use state to track progress. Creating the proposal does not transfer tokens; allocation happens on execute after successful voting and the delay.

    Implementation considerations

    Description and action mismatch

    Decode targets, values and calldatas for the user instead of relying on the description alone. Make recipient and token mismatches visible before submission.

    Who holds the budget

    Tokens in the proposer’s wallet do not fund a Timelock transfer. Check the executor’s balance and target-contract permissions.

    Execution failure after approval

    Insufficient funds, a pause or transfer restrictions can cause execution to fail after approval. Distinguish approved from executed and inspect operation state before retrying.

    Extending the architecture

    Other governed actions

    Other actions, such as contract configuration changes, can be proposed when Timelock is authorized to execute them. Display the required permissions and effects for each function.

    Different voting-power models

    Use IVotes and a weight strategy for membership- or role-based voting. Define thresholds, quorum and reference time as well as the difference from balance-based voting.