コミュニティ・ガバナンス
提案・投票・実行までを、参加者と権限に合わせて設計する。
← 実装パターン一覧共同施設の予算配分を、提案・投票・実行につなぐ
共同施設の運営コミュニティが、保有する予算トークンから100単位を活動担当者へ配分する例です。提案本文だけでなく、送金先と数量を含む実行データを投票対象にし、可決後に待機期間を経て実行します。
提案内容と実行内容を一致させ、誰が投票し、いつ可決され、いつ配分が実行されたかを追跡できます。
システム構成と役割
提案・投票画面
対象・数量・期間を入力し、実行データを読める形で表示します。
Web UI / Wallet投票権と集計条件
投票権のトークン、委任、閾値や定足数の条件を設定します。
IVotes / Weight strategy提案の管理
提案ID、投票期間、投票結果、状態を管理します。
GenericGovernorHelper待機後の実行
Timelockが待機期間後に対象コントラクトを呼び出します。
TimelockController / Target contract
データモデル例
業務DBに保存する情報と、チェーンやAPIで管理する情報を対応づけます。フィールド名とサンプル値は、この構成で使うアプリケーション側の設計例です。
| 項目・値の例 | 管理する場所 | 役割・対応関係 |
|---|---|---|
proposalIduint256 as string | チェーン+業務DB | Governorが返す提案ID。JavaScriptのnumberへ変換せず文字列で保存します。 |
targets / values / calldatastoken / 0 / transfer(...) | チェーン | 同じ添字の要素が1つの呼出しを表します。valuesはネイティブ通貨の数量です。 |
descriptionCommunity activity grant | チェーン+業務DB | 提案IDの計算にも使用する本文。実行データと対応づけて保存します。 |
startTs / endTsUNIX seconds | チェーン | 投票の開始・終了時刻。ブラウザのミリ秒をそのまま渡しません。 |
vote / supportproposalId / 1 | チェーン | 0は反対、1は賛成、2は棄権です。投票権は設定された戦略に従います。 |
executionHash / operationId0x… / timelock ID | チェーン+業務DB | 提案IDとTimelockの操作ID、最終実行の取引ハッシュを区別します。 |
処理の流れ
投票基盤と予算を用意する
Governor、Timelock、IVotes対応トークン、Weight Strategyを接続します。予算トークンは実行者であるTimelockが保有し、必要な操作権限を持つ構成にします。
GenericGovernor / TimelockController / IVotes実行する処理を提案に含める
送金先と100単位をERC-20のtransfer呼出しデータへ変換し、投票期間と本文を添えてproposeします。画面にはエンコードされた値だけでなく、対象トークン・宛先・表示数量を表示します。
Interface.encodeFunctionData → governor.propose投票を受け付ける
Activeの期間に、投票権を持つ利用者が署名して投票します。ERC20Votesを使う構成では、必要な委任や過去時点の投票権を確認します。保有量がそのまま投票可能数になるとは限りません。
state(proposalId) / castVote(proposalId, 1)可決した提案を待機状態へ送る
投票終了と可決を確認してqueueします。可決・Queued・実行済みは別の状態です。否決や取消しを含め、状態に応じて操作ボタンを制御します。
queue(proposalId) → Timelock delay待機期間後に実行する
Timelockの操作がReadyになったら、許可された実行者がexecuteします。取引確定後に配分先の残高と実行イベントを確認し、提案画面へ反映します。
Timelock.isOperationReady / governor.execute(proposalId)
コードを利用する前の準備
- Governor・Timelock・投票トークン・Weight Strategyを配置し、提案・キュー・実行に必要な役割を設定します。
- 予算トークンはTimelockが保有します。譲渡可能で、Pauseやホワイトリストなどの条件も満たす必要があります。
- startTs・endTsには対象チェーンの時刻を基準としたUNIX秒を使用します。終了は開始より後で、設定できる期間の上限に合わせます。
npm install --save-exact @hazbase/kit@0.9.0 ethers@6.16.0以下はアプリケーションから呼び出すTypeScriptの関数です。接続先・権限・対象データを引数として渡します。UI、DBへの保存、処理の再開は、上記の構成に沿って業務側へ組み込めます。
100単位の配分を提案する
units: "100"を渡し、予算トークンの小数桁に応じて実行データを生成します。この関数は提案を作成します。投票・queue・executeは各状態に達した後の別操作です。
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) };
}
実行結果と、アプリケーションへの反映
proposalIdを文字列で保存し、stateで進行状況を取得できます。提案の作成時点ではトークンは移転しません。実際の配分は可決と待機期間を経たexecuteで行われます。
実装で押さえておきたい点
提案本文と実行内容の不一致
本文に書かれた数量だけを信頼せず、targets・values・calldatasをデコードして利用者に表示します。宛先やトークンの取り違えを提案前に確認できるようにします。
予算を持つアカウント
提案者のウォレットに残高があっても、Timelockからの送金原資にはなりません。実行主体の残高と、対象コントラクトの権限を確認します。
可決後の実行失敗
可決後に残高不足、Pause、移転制限が生じると実行が失敗します。可決済みと実行済みを区別し、再実行前に対象操作の状態を確認します。
用途に合わせた拡張
予算配分以外の操作
権限を持つTimelockから実行できる操作なら、契約設定の変更なども提案対象にできます。対象関数ごとに必要な権限と影響範囲を表示します。
利用者ごとに異なる投票権
会員資格や役割に応じた投票権は、IVotesとWeight Strategyの組み合わせで設計します。単純な残高投票との違いは、閾値・定足数・基準時点まで含めて定義します。