コミュニティ・ガバナンス https://docs.hazbase.com/patterns/governance/ IMPLEMENTATION PATTERNS コミュニティ・ガバナンス 提案・投票・実行までを、参加者と権限に合わせて設計する。 ← 実装パターン一覧 具体例 共同施設の予算配分を、提案・投票・実行につなぐ 共同施設の運営コミュニティが、保有する予算トークンから100単位を活動担当者へ配分する例です。提案本文だけでなく、送金先と数量を含む実行データを投票対象にし、可決後に待機期間を経て実行します。 提案内容と実行内容を一致させ、誰が投票し、いつ可決され、いつ配分が実行されたかを追跡できます。 システム構成と役割 0 1 提案・投票画面 対象・数量・期間を入力し、実行データを読める形で表示します。 Web UI / Wallet 0 2 投票権と集計条件 投票権のトークン、委任、閾値や定足数の条件を設定します。 IVotes / Weight strategy 0 3 提案の管理 提案ID、投票期間、投票結果、状態を管理します。 GenericGovernorHelper 0 4 待機後の実行 Timelockが待機期間後に対象コントラクトを呼び出します。 TimelockController / Target contract データモデル例 業務DBに保存する情報と、チェーンやAPIで管理する情報を対応づけます。フィールド名とサンプル値は、この構成で使うアプリケーション側の設計例です。 項目・値の例 管理する場所 役割・対応関係 proposalId uint256 as string チェーン+業務DB Governorが返す提案ID。JavaScriptのnumberへ変換せず文字列で保存します。 targets / values / calldatas token / 0 / transfer(...) チェーン 同じ添字の要素が1つの呼出しを表します。valuesはネイティブ通貨の数量です。 description Community activity grant チェーン+業務DB 提案IDの計算にも使用する本文。実行データと対応づけて保存します。 startTs / endTs UNIX seconds チェーン 投票の開始・終了時刻。ブラウザのミリ秒をそのまま渡しません。 vote / support proposalId / 1 チェーン 0は反対、1は賛成、2は棄権です。投票権は設定された戦略に従います。 executionHash / operationId 0x… / timelock ID チェーン+業務DB 提案IDとTimelockの操作ID、最終実行の取引ハッシュを区別します。 処理の流れ 1 投票基盤と予算を用意する Governor、Timelock、IVotes対応トークン、Weight Strategyを接続します。予算トークンは実行者であるTimelockが保有し、必要な操作権限を持つ構成にします。 GenericGovernor / TimelockController / IVotes 2 実行する処理を提案に含める 送金先と100単位をERC-20のtransfer呼出しデータへ変換し、投票期間と本文を添えてproposeします。画面にはエンコードされた値だけでなく、対象トークン・宛先・表示数量を表示します。 Interface.encodeFunctionData → governor.propose 3 投票を受け付ける Activeの期間に、投票権を持つ利用者が署名して投票します。ERC20Votesを使う構成では、必要な委任や過去時点の投票権を確認します。保有量がそのまま投票可能数になるとは限りません。 state(proposalId) / castVote(proposalId, 1) 4 可決した提案を待機状態へ送る 投票終了と可決を確認してqueueします。可決・Queued・実行済みは別の状態です。否決や取消しを含め、状態に応じて操作ボタンを制御します。 queue(proposalId) → Timelock delay 5 待機期間後に実行する Timelockの操作がReadyになったら、許可された実行者がexecuteします。取引確定後に配分先の残高と実行イベントを確認し、提案画面へ反映します。 Timelock.isOperationReady / governor.execute(proposalId) コードを利用する前の準備 Governor・Timelock・投票トークン・Weight Strategyを配置し、提案・キュー・実行に必要な役割を設定します。 予算トークンはTimelockが保有します。譲渡可能で、Pauseやホワイトリストなどの条件も満たす必要があります。 startTs・endTsには対象チェーンの時刻を基準としたUNIX秒を使用します。終了は開始より後で、設定できる期間の上限に合わせます。 Install npm install --save-exact @hazbase/kit@0.9.0 ethers@6.16.0 以下はアプリケーションから呼び出すTypeScriptの関数です。接続先・権限・対象データを引数として渡します。UI、DBへの保存、処理の再開は、上記の構成に沿って業務側へ組み込めます。 100単位の配分を提案する units: "100"を渡し、予算トークンの小数桁に応じて実行データを生成します。この関数は提案を作成します。投票・queue・executeは各状態に達した後の別操作です。 pattern-governance.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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の組み合わせで設計します。単純な残高投票との違いは、閾値・定足数・基準時点まで含めて定義します。 関連するAPI・SDK仕様 GenericGovernorHelper TimelockControllerHelper FlexibleTokenHelper / IVotes サンプルアプリから始める 導入・運用ガイド