本記事は下記の翻訳となります。
『Creating a Governance Proposal for Berachain Reward Vaults』
Berachain の Proof-of-Liquidity コンセンサス メカニズムの最大の利点の 1 つは、プロトコルが Berachain Governance Tokens($BGT
)の発行を通じて、自身の流動性をブートストラップできることです。この$BGT
の蓄積プロセスは、RewardsVaults
スマートコントラクトで表現される Reward Vaults を通じて行われます。
*注意:** 私たちのテストネットで、Berps Rewards Vaultスマートコントラクトがどのように機能しているかを確認できます。*
Reward Vaults に$BGT
が蓄積されるプロセスには、バリデーターがブロックを提案するために選ばれ、選ばれた時に$BGT
を報酬として受け取り、その$BGT
の大部分を BeraChef と呼ばれる分配コントラクトを通じて 1 つまたは複数の Reward Vaults に分配することが含まれます。バリデーターの報酬は、ブロックを提案する時点で彼らに委任されている$BGT
の量によって影響を受けます。
$BGT でできること
$BGT
には主に 3 つの機能があります:
-
$BERA
を得るためにバーンする - バリデーターに委任して報酬を増やす
- ガバナンスで提案を行ったり投票したりするために使用する
BEX の Rewards Vault 例
BEXは AMM プロトコルであり、Berachain ネイティブの PoL dApp の 1 つで、複数の Reward Vaults を持っています。ユーザーがホワイトリストに登録されたプールに流動性を提供するたびに、彼らの総貢献度に比例した LP トークンを受け取ります。
例えば、ユーザーがHONEY <> WBTC Poolに流動性を追加すると、その見返りとして **$HONEY-WBTC LP トークン** を受け取ります。
その後、LP トークンを BEX の$HONEY-WBTC Rewards Vault にステーキングすることができます。LP トークンがステーキングされている間、バリデーターが Rewards Vault に向けて$BGT
の発行を行うと、時間の経過とともに$BGT
が蓄積されていきます。これが BEX がユーザーに対して、プロトコルに流動性を提供するよう促すインセンティブの仕組みです。
プロトコルのトークン(ここでは$BBT
、Black Bera Token とします)の取引流動性に BEX を活用する場合、$WBERA
とペアにすると、ユーザーは流動性を提供する際に$BBT-WBERA-LP
トークンを受け取ります。この LP トークンがホワイトリストに登録されている場合(つまり、そのトークンをステーキング用に受け入れる Rewards Vault がある場合)、$BGT
報酬の対象となり、Proof of Liquidity を通じてプロトコルの流動性をブートストラップできます。
これらの Rewards Vault はRewardsVault Factoryによって作成されます。これは Berachain 上のスマートコントラクトで、Rewards Vault の作成を担当します。
自分で RewardsVault を作成することはパーミッションレスであり誰でも可能ですが、バリデーターがあなたの Rewards Vault に$BGT
を発行するためには、まずガバナンスプロポーザルとして提出し、承認を受けてBGT Stationに Rewards Vault を追加する必要があります。プロポーザルが承認されると、あなたの Rewards Vault は「friendOfTheChef
」となり、バリデーターが$BGT
を発行できる適格な Rewards Vault のコレクションに承認されます。このプロセスは「ホワイトリスト登録」とも呼ばれます。
Berachain のホワイトリスト登録のためのガバナンスプロセスを理解する
これから構築するものを理解するために、reward vault の作成、プロポーザルの作成、プロポーザルへの投票、reward vault の有効化というガバナンスプロセスについて説明しましょう。
ガバナンスの要件
成功するプロポーザルを作成する前に、考慮すべきいくつかの要件とステップがあります。
-
min 1000
$BGT
- すべてのプロポーザルには最低 1000$BGT
が必要です。これは直接獲得するか、他の人に提案者へ$BGT
を委任してもらうことで満たすことができます。 -
Delegated/Self-Delegated Voting Power (委任された/自己委任された voting power) - 最低
$BGT
が満たされている場合、個人所有の$BGT
で提案したり既存のプロポーザルに投票したりするには、それを提案者(提案者自身の場合も含む)に委任する必要があります。 -
Quorum Met(定足数の達成) - 「賛成」票の過半数が 20 億の定足数を満たす必要があります。これはメインネットに近づくにつれて変更される可能性がありますが、テストネットでは、Berachain チームに定足数達成の支援を求めることができます。
ガバナンスのライフサイクル
以下は、ガバナンスプロセスのタイムライン全体を通じてプロポーザルが経る異なる状態を表しています。
Lifecycle of a Berachain Governance Proposal
1. プロポーザル作成(保留状態)
- 待機期間:投票開始まで 3 時間
- この状態ではプロポーザルをキャンセルすることも可能
2. 投票開始(アクティブ状態)
- 期間:3 時間
- BGT 保有者が投票を行う
- 定足数を満たす必要あり:最低 20 億 BGT
3. 投票終了
- プロポーザルは成功または否決される
- 否決された場合:そのように記録され、懸念事項に対処した後に新たなプロポーザルを作成可能
4. タイムロック(成功した場合)
- 3 時間のタイムロック遅延を伴うキューに入る
5. 実行または期限切れ
- 実行:Berachain に承認されたガバナンス EOA のみが実行可能
- 期限切れ:期間内に実行されなかった場合
注意: 時間枠はプレースホルダーであり、メインネットローンチに近づくにつれて変更される可能性があります
reward vault 用の BEX プールの作成
このチュートリアルでは、ユーザーがステーキングすることで$BGT
の発行を受け取る資格を得られる LP トークンを受け入れる reward vault を作成します。この reward vault を LP トークンと共に作成したら、ガバナンスに提出して Berachef の「フレンド」として追加することができます。
まず、BEXプールとそのステーキングトークンを作成します。
Example of Pool Creation on BEX
新しいプールを作成するには、あなたのプロトコルトークン(この場合は新しく作成された$BBT
というトークン)と$WBERA
トークンを提供して、BEX 上にカスタムプールを作成する必要があります。
プールが作成され、預入が行われると、LP トークンを受け取るはずです。さらに預入を行うと、より多くの LP トークンが発行されます。
デプロイされたプールと LP トークンの例
reward vault とガバナンスプロポーザル作成のためのプロジェクトセットアップ
$BBT
<> $WBERA
BEX プールとそれに対応する LP トークン($BBT-WBERA-LP
)ができたので、次は Berachef に reward vault を追加するガバナンスプロポーザルを作成する前に、reward vault を作成する必要があります。
以下の作業を行うスクリプトを実行するプロジェクトを作成します:
- LP トークン用の reward vault を作成する
- 新しい reward vault を Berachef に追加するためのプロポーザルを Berachain ガバナンスに提出する
- reward vault に対して投票を行う
- reward vault を Berachef に追加して有効化する
プロジェクトの要件
スクリプトを構築するために、以下のものがコンピューターにインストールされているか、これらの要件の一部を満たしていることを確認してください。
- NVM または NodeJS
v20.16.0
以上 -
$BGT
が蓄積されているウォレット - トランザクションを処理するための
$BERA
トークン
1. 新しい JavaScript プロジェクトの初期化
LP トークン用の reward vault を作成する前に、開発環境をセットアップしましょう。ターミナルを開き、以下の手順に従って始めてください:
mkdir berachain-rewards-vault
cd berachain-rewards-vault
npm init -y
2. 依存関係をインストールする
# FROM: ./berachain-rewards-vault
npm install ethers dotenv yargs
3. .gitignore
ファイルを作成する
# FROM: ./berachain-rewards-vault
echo "node_modules\n.env" > .gitignore
4. ABI ファイルのセットアップ
プロジェクトのルートディレクトリに abi
フォルダを作成してください:
# FROM: ./berachain-rewards-vault
mkdir abi
以下の JSON ファイルをダウンロードし、このフォルダに追加する必要があります:
-
ERC20.json
- ABI ファイル:LP トークンの ABI です。これは単なる一般的な ERC20 トークンです。 -
BerachainRewardsVaultFactory.json
- ABI ファイル:Berachain reward vault ファクトリーコントラクトの ABI です。 - BerachainGovernance.json - ABI ファイル:Berachain ガバナンスコントラクトの ABI です。これは、ガバナンスプロポーザルの提出、投票、実行を行うメインのコントラクトです。
- BGT.json - ABI ファイル:
$BGT
の ABI です。 - BeraChef.json - ABI ファイル:BeraChef の ABI です。これは
$BBT
<>$WBERA
LP トークンの reward vault をホワイトリストに登録するコントラクトです。 - BerachainRewardsVault.json - ABI ファイル:reward vault の ABI です。
5. 環境変数のセットアップ
次に、プロジェクトのルートに.env
ファイルを作成し、以下の内容を追加します:
ルートディレクトリから以下のコマンドを実行してください:
cat << EOF > .env
RPC=https://bartio.rpc.berachain.com/
PRIVATE_KEY=your_private_key_here
FACTORY_ADDRESS=0x2B6e40f65D82A0cB98795bC7587a71bfa49fBB2B
LP_TOKEN_ADDRESS=insert_your_lp_token_address_here
GOVERNANCE_ADDRESS=0xE3EDa03401Cf32010a9A9967DaBAEe47ed0E1a0b
BERACHEF_ADDRESS=0xfb81E39E3970076ab2693fA5C45A07Cc724C93c2
BGT_ADDRESS=0xbDa130737BDd9618301681329bF2e46A016ff9Ad
EOF
これらは追加した値です。
-
RPC
- Public RPC または自分の RPC に置き換えてください -
PRIVATE_KEY
- BGT を保有しているあなたのウォレット -
FACTORY_ADDRESS
- bArtio reward vault Factroy のアドレス -
LP_TOKEN_ADDRESS
- あなたの LP トークンのアドレス -
GOVERNANCE_ADDRESS
- Berachain のガバナンスコントラクトのアドレス。BeraChef のupdateFriendsOfTheChef
を呼び出す排他的な権利を持っています -
BERACHEF_ADDRESS
- BeraChef のアドレス。ホワイトリストに登録された reward vault とバリデータの設定を保存するコントラクトです -
BGT_ADDRESS
- Berachain ガバナンストークンのアドレス
PRIVATE_KEY
とLP_TOKEN_ADDRESS
(例:$BBT-WBERA-LP
)を実際の値に置き換えてください。
6. ガバナンススクリプトの作成とセットアップ
プロジェクトのルートにgovernance.js
という新しいファイルを作成し、以下のコードを追加してください:
File: ./governance.js
// Import required libraries
const ethers = require('ethers');
require('dotenv').config();
// Import ABI (Application Binary Interface) for various contracts
const BeraChefABI = require('./abi/BeraChef.json');
const BerachainGovernanceABI = require('./abi/BerachainGovernance.json');
const BGTABI = require('./abi/BGT.json');
const BerachainRewardsVaultABI = require('./abi/BerachainRewardsVault.json');
const ERC20ABI = require('./abi/ERC20.json');
const BerachainRewardsVaultFactoryABI = require('./abi/BerachainRewardsVaultFactory.json');
// Set up the Ethereum provider using the RPC URL from the .env file
const provider = new ethers.JsonRpcProvider(`${process.env.RPC}`, {
chainId: 80084, // Chain ID for Berachain
name: 'Berachain',
ensAddress: null,
});
// Initialize the wallet using the private key from the .env file
let wallet;
try {
wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
} catch (error) {
console.error('Error creating wallet:', error.message);
process.exit(1);
}
// Helper function to create contract instances
function createContract(address, abi, signer) {
return new ethers.Contract(ethers.getAddress(address), abi, signer);
}
// Create instances of various contracts
const governance = createContract(process.env.GOVERNANCE_ADDRESS, BerachainGovernanceABI, wallet);
const beraChef = createContract(process.env.BERACHEF_ADDRESS, BeraChefABI, wallet);
const bgt = createContract(process.env.BGT_ADDRESS, BGTABI, wallet);
const factory = createContract(
process.env.FACTORY_ADDRESS,
BerachainRewardsVaultFactoryABI,
wallet
);
const token = createContract(process.env.LP_TOKEN_ADDRESS, ERC20ABI, wallet);
let rewardsVault; // This will be initialized later when creating or retrieving a vault
7. helper 関数を追加する
次に、helper 関数をコピー&ペーストしてください:
File: ./governance.js
// Function to check the current state of a proposal
async function checkProposalState(proposalId) {
// Get the numerical state of the proposal
const state = await governance.state(proposalId);
// Define an array of state names corresponding to their numerical values
const stateNames = [
'Pending',
'Active',
'Canceled',
'Defeated',
'Succeeded',
'Queued',
'Expired',
'Executed',
];
// Return both the numerical state and its corresponding name
return { state, stateName: stateNames[state] };
}
// Function to determine the next stage in the governance process
async function getNextStage(currentState) {
// Define the order of stages in the governance process
const stageOrder = ['Pending', 'Active', 'Succeeded', 'Queued', 'Executed'];
// Find the index of the current state in the stage order
const currentIndex = stageOrder.indexOf(currentState);
// Return the next stage if it exists, otherwise return 'End'
return currentIndex < stageOrder.length - 1 ? stageOrder[currentIndex + 1] : 'End';
}
// Function to ensure the user has sufficient voting power to create a proposal
async function ensureSufficientVotingPower() {
// Get the user's BGT balance
const balance = await bgt.balanceOf(wallet.address);
console.log('BGT balance:', balance.toString());
// Check who the current delegatee is for the user's BGT
const currentDelegatee = await bgt.delegates(wallet.address);
console.log('Current delegatee:', currentDelegatee);
// Get the user's current voting power
const votingPower = await governance.getVotes(
wallet.address,
(await provider.getBlockNumber()) - 1
);
console.log('Your voting power:', votingPower.toString());
// Get the proposal threshold (minimum voting power required to create a proposal)
const proposalThreshold = await governance.proposalThreshold();
console.log('Proposal threshold:', proposalThreshold.toString());
// If voting power is less than the threshold
if (votingPower < proposalThreshold) {
// If BGT is not self-delegated, delegate it to self
if (currentDelegatee !== wallet.address) {
console.log('Delegating all BGT to self...');
await (await bgt.delegate(wallet.address)).wait();
console.log('Delegation complete');
} else {
// If already self-delegated but still not enough voting power
console.log('Already delegated to self, but still not enough voting power');
console.log('Please acquire more BGT tokens to meet the proposal threshold');
return false;
}
}
// Check updated voting power after potential delegation
const updatedVotingPower = await governance.getVotes(
wallet.address,
(await provider.getBlockNumber()) - 1
);
console.log('Updated voting power:', updatedVotingPower.toString());
// If still not enough voting power, return false
if (updatedVotingPower < proposalThreshold) {
console.log('Voting power is still less than proposal threshold, cannot create proposal');
return false;
}
// Sufficient voting power achieved
return true;
}
// Function to check if a proposal with given parameters already exists
async function checkExistingProposal(targets, values, calldatas, descriptionHash) {
// Generate a proposal ID based on the given parameters
const proposalId = await governance.hashProposal(targets, values, calldatas, descriptionHash);
try {
// Try to get the state of the proposal
const state = await governance.state(proposalId);
// If state is not 3 (Defeated), the proposal exists and is not defeated
return state !== 3;
} catch (error) {
// If the error indicates the proposal doesn't exist, return false
// Otherwise, propagate the error
return error.reason === 'GovernorNonexistentProposal(uint256)' ? false : Promise.reject(error);
}
}
8. Rewards Vault を作成するためのコードを追加する
File: ./governance.js
// Function to get an existing rewards vault or create a new one
async function getOrCreateVault() {
console.log('Checking for existing rewards vault...');
try {
// Check if a vault already exists for the token
const existingVaultAddress = await factory.getVault(process.env.LP_TOKEN_ADDRESS);
// If a vault exists (address is not zero)
if (existingVaultAddress !== ethers.ZeroAddress) {
console.log('A rewards vault already exists for this token.');
console.log(`Existing rewards vault address: ${existingVaultAddress}`);
// Provide instructions to view vault details
console.log('\nTo view details about the existing vault:');
console.log('1. Go to https://bartio.beratrail.io');
console.log(`2. Search for the rewards vault address: ${existingVaultAddress}`);
console.log('3. Look for the "Create Rewards Vault" method in the transaction history');
console.log('\nUsing the existing vault for this operation.');
console.log(
'\nAdd this rewards vault address to your .env file under REWARDS_VAULT_ADDRESS:'
);
console.log(`REWARDS_VAULT_ADDRESS=${existingVaultAddress}`);
// Create a contract instance for the existing vault
rewardsVault = new ethers.Contract(existingVaultAddress, BerachainRewardsVaultABI, wallet);
return existingVaultAddress;
}
// If no existing vault, create a new one
console.log('No existing vault found. Creating new rewards vault...');
const tx = await factory.createRewardsVault(process.env.LP_TOKEN_ADDRESS);
console.log('Transaction sent. Waiting for confirmation...');
const receipt = await tx.wait();
console.log('Rewards vault created. Transaction hash:', receipt.transactionHash);
console.log();
// Get the address of the newly created vault
const newVaultAddress = await factory.getVault(process.env.LP_TOKEN_ADDRESS);
console.log('New rewards vault created at:', newVaultAddress);
// Provide instructions to view new vault details
console.log('\nTo view details about the new vault:');
console.log('1. Go to https://bartio.beratrail.io');
console.log(`2. Search for the rewards vault address: ${newVaultAddress}`);
console.log('3. Look for the "Create Rewards Vault" method in the transaction history');
console.log('\nAdd this rewards vault address to your .env file under REWARDS_VAULT_ADDRESS:');
console.log(`REWARDS_VAULT_ADDRESS=${newVaultAddress}`);
// Create a contract instance for the new vault
rewardsVault = new ethers.Contract(newVaultAddress, BerachainRewardsVaultABI, wallet);
return newVaultAddress;
} catch (error) {
console.error('Error getting or creating rewards vault:', error);
throw error;
}
}
// Main function to handle command-line arguments
async function main() {
const args = process.argv.slice(2);
const flag = args[0];
switch (flag) {
case '--create-vault':
// Call getOrCreateVault when the --create-vault flag is used
await getOrCreateVault();
break;
// ... other cases ...
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
LP_TOKEN_ADDRESS
とPRIVATE_KEY
を.env
ファイルに追加したら、以下のコードをgovernance.js
ファイルに貼り付け、スクリプトを実行して reward vault を作成してください:
# FROM: ./berachain-rewards-vault
node governance.js --create-vault
このコマンドは、私たちの LP トークン用の新しい reward vault を作成し、作成された vault のアドレスをログに記録します。
Output of node governance.js --create-vault command
File: ./.env
この新しい値を .env
ファイルに追加してください:
REWARDS_VAULT_ADDRESS=value from terminal
注意: あなたの reward vault のアドレスは、このスクリーンショットで見られるものとは異なります。
ガバナンスプロポーザルの提出
reward vault を作成したので、今度はそれをホワイトリストに登録するためのガバナンスプロポーザルを提出する時です。このプロセスのために新しいスクリプトを作成します。
1. 環境のセットアップ
このプロセスの時点で、あなたの.env
ファイルには以下の値が含まれているはずです:
File: ./.env
RPC=https://bartio.rpc.berachain.com/
PRIVATE_KEY=your_private_key
FACTORY_ADDRESS=0x2B6e40f65D82A0cB98795bC7587a71bfa49fBB2B
LP_TOKEN_ADDRESS=your_lp_token_address
GOVERNANCE_ADDRESS=0xE3EDa03401Cf32010a9A9967DaBAEe47ed0E1a0b
BERACHEF_ADDRESS=0xfb81E39E3970076ab2693fA5C45A07Cc724C93c2
BGT_ADDRESS=0xbDa130737BDd9618301681329bF2e46A016ff9Ad
REWARDS_VAULT_ADDRESS=your_rewards_vault_address
2. Proposal 関数を作成する
同じファイルgovernance.js
で、関数getOrCreateVault
の下に次の関数を追加します。
File: ./governance.js
async function getOrCreateVault() {
// previous function we implemented
// ...
}
//🚨COPY THIS FUNCTION🚨
async function createProposal(targets, values, calldatas, description) {
// Generate a hash of the proposal description
const hash = ethers.id(description);
// Check if a proposal with these parameters already exists
const proposalExists = await checkExistingProposal(targets, values, calldatas, hash);
if (proposalExists) {
// If the proposal exists, get its ID
const proposalId = await governance.hashProposal(targets, values, calldatas, hash);
// Check the current state of the existing proposal
const { stateName } = await checkProposalState(proposalId);
// Determine the next stage in the proposal process
const nextStage = await getNextStage(stateName);
// Log information about the existing proposal
console.log('\nA proposal with these parameters already exists.');
console.log(`Proposal ID: ${proposalId.toString()}`);
console.log(`Current state: ${stateName}`);
// Inform about the next stage or if it's the final stage
if (nextStage !== 'End') {
console.log(`Next stage: ${nextStage}`);
} else {
console.log('This is the final stage of the proposal.');
}
// Provide instructions to add the proposal ID to the .env file
console.log('\nAdd this proposal ID to your .env file under PROPOSAL_ID:');
console.log(`PROPOSAL_ID=${proposalId.toString()}`);
return proposalId.toString();
}
try {
// If no existing proposal, create a new one
console.log('Creating new proposal...');
const tx = await governance.propose(targets, values, calldatas, description);
const receipt = await tx.wait();
console.log('Proposal transaction confirmed. Transaction hash:', receipt.transactionHash);
console.log();
// Get the ID of the newly created proposal
const proposalId = await governance.hashProposal(targets, values, calldatas, hash);
console.log('New proposal created with ID:', proposalId.toString());
// Provide instructions to add the new proposal ID to the .env file
console.log('\nAdd this proposal ID to your .env file under PROPOSAL_ID:');
console.log(`PROPOSAL_ID=${proposalId.toString()}`);
return proposalId.toString();
} catch (error) {
// Handle any errors that occur during proposal creation
console.error('Error creating proposal:', error);
if (error.error?.data) {
try {
console.error('Decoded error:', governance.interface.parseError(error.error.data));
} catch (parseError) {
console.error('Could not parse error. Raw error data:', error.error.data);
}
}
throw error;
}
}
3. 3. main 関数にコマンドを追加
次に、governance.js
ファイルの main
関数に、以下の switch case を--create-vault
ケースの直後に追加してください。これはガバナンスプロポーザルの作成を処理します:
File: ./governance.js
async function main() {
// Get command-line arguments, skipping the first two (node and script name)
const args = process.argv.slice(2);
// The first argument is our flag/command
const flag = args[0];
switch (flag) {
case '--create-vault':
// If the flag is to create a vault, call the getOrCreateVault function
await getOrCreateVault();
break;
//🚨COPY THIS CASE🚨
case '--create-proposal':
// Check if the user has sufficient voting power to create a proposal
if (!(await ensureSufficientVotingPower())) return;
// Get or create a rewards vault
const vaultAddress = await getOrCreateVault();
// Get the address of the BeraChef contract
const beraChefAddress = await beraChef.getAddress();
// Set up the proposal parameters
const targets = [beraChefAddress]; // The contract to call
const values = [0]; // No BERA being sent with the call
// Encode the function call to updateFriendsOfTheChef
const calldatas = [
beraChef.interface.encodeFunctionData('updateFriendsOfTheChef', [vaultAddress, true]),
];
const description = 'Update friends of the chef'; // Description of the proposal
// Create the proposal with the specified parameters
await createProposal(targets, values, calldatas, description);
break;
//🚨END COPY HERE🚨
}
}
このmain
関数の新しいスイッチケースにより、新しく作成した Rewards Vault を"friends of the chef"に追加するためのガバナンスプロポーザルを作成できるようになります。十分な voting power があるかをチェックし、vault アドレスを取得(または作成)し、必要なパラメータを使ってcreateProposal
関数を呼び出します。
4. create-proposal コマンドを実行する
main
関数に--create-proposal
ケースを追加したので、Rewards Vault を BeraChef に追加するためのガバナンスプロポーザルを作成するコマンドを実行できます。ターミナルで以下を実行してください:
# FROM: ./berachain-rewards-vault
node governance.js --create-proposal
このコマンドは、あなたの Rewards Vault をホワイトリストに追加するための新しいガバナンスプロポーザルを作成します。スクリプトは Proposal ID を出力します。今後のステップのために、この ID を.env
ファイルに追加する必要があります。
コマンドを実行すると、以下のような出力が表示されるはずです:
Output of the node governance.js --create-proposal command
出力の指示に従って、.env
ファイルを更新してください。PROPOSAL_ID
の値を含む新しい行を追加します:
File: ./.env
PROPOSAL_ID=your_proposal_id
このPROPOSAL_ID
は、投票、queueing(キューイング)、提案の実行など、ガバナンスプロセスの後続のステップで使用されます。これは、あなたの Vault を BeraChef に追加するためのガバナンスプロポーザルの ID を表しています。
5. 投票と実行のための関数を作成する
以下の関数をgovernance.js
ファイルのcreateProposal
関数の下に追加してください:
File: ./governance.js
// Function to cast a vote on a proposal
async function castVote(proposalId) {
// Check if the wallet has already voted
const hasVoted = await governance.hasVoted(proposalId, wallet.address);
if (hasVoted) {
console.log('Vote already cast for this proposal. Proceeding to next steps.');
return;
}
console.log('Casting vote...');
try {
// Cast a vote in favor of the proposal (1 = yes)
const voteTx = await governance.castVote(proposalId, 1);
const receipt = await voteTx.wait();
console.log('Vote cast successfully. Transaction hash:', receipt.transactionHash);
} catch (error) {
console.error('Error casting vote:', error);
if (error.error?.data) {
try {
console.error('Decoded error:', governance.interface.parseError(error.error.data));
} catch (parseError) {
console.error('Could not parse error. Raw error data:', error.error.data);
}
}
throw error;
}
}
// Function to execute a queued proposal
async function executeProposal(proposalId) {
console.log('Executing proposal...');
try {
const executeTx = await governance.execute(proposalId);
const receipt = await executeTx.wait();
console.log('Proposal executed successfully. Transaction hash:', receipt.transactionHash);
} catch (error) {
console.error('Error executing proposal:', error);
throw error;
}
}
// Function to cancel a proposal
async function cancelProposal(proposalId) {
console.log('Cancelling proposal...');
try {
const cancelTx = await governance.cancel(proposalId);
const receipt = await cancelTx.wait();
console.log('Proposal cancelled successfully. Transaction hash:', receipt.transactionHash);
} catch (error) {
console.error('Error cancelling proposal:', error);
if (error.error?.data) {
try {
console.error('Decoded error:', governance.interface.parseError(error.error.data));
} catch (parseError) {
console.error('Could not parse error. Raw error data:', error.error.data);
}
}
throw error;
}
}
これらの関数をガバナンスプロセスの異なる段階で処理します:
-
castVote
: この関数を使用してプロポーザルに投票できます。まず、すでに投票したかどうかをチェックし、まだ投票していない場合は「賛成」票(1
で表される)を投じます。 -
executeProposal
: プロポーザルがキューに入れられ、タイムロック期間が経過した後、この関数を呼び出してプロポーザルを実行できます。これは、ガバナンスプロセスで提案された変更を実施する最終ステップです。 -
cancelProposal
: プロポーザルが実行のためにキューに入れられる前にキャンセルすることができます。これは、間違ったアドレスや説明を入力してしまった場合や、新しいプロポーザルを作成したい場合に使用します。
次に、governance.js
ファイルのmain
関数を以下のコードに置き換えてください:
File: ./governance.js
async function main() {
// Get command-line arguments, skipping the first two (node and script name)
const args = process.argv.slice(2);
// The first argument is our flag/command
const flag = args[0];
// Get the proposal ID from the environment variables
const proposalId = process.env.PROPOSAL_ID;
switch (flag) {
case '--create-vault':
// Create or retrieve an existing rewards vault
await getOrCreateVault();
break;
case '--create-proposal':
// Check if there's an existing proposal
if (proposalId) {
const { stateName } = await checkProposalState(proposalId);
// Only allow creating a new proposal if the current one is defeated
if (stateName !== 'Defeated') {
console.log(
`A proposal (ID: ${proposalId}) already exists and is in ${stateName} state.`
);
console.log('You can only create a new proposal if the current one is defeated.');
return;
}
}
// Ensure the user has enough voting power to create a proposal
if (!(await ensureSufficientVotingPower())) return;
// Get or create a rewards vault
const vaultAddress = await getOrCreateVault();
// Get the BeraChef contract address
const beraChefAddress = await beraChef.getAddress();
// Set up proposal parameters
const targets = [beraChefAddress];
const values = [0];
const calldatas = [
beraChef.interface.encodeFunctionData('updateFriendsOfTheChef', [vaultAddress, true]),
];
const description = 'Update friends of the chef';
// Create the proposal
await createProposal(targets, values, calldatas, description);
break;
case '--vote':
// Ensure a proposal ID is set
if (!proposalId) {
console.error('Please set the PROPOSAL_ID in your .env file');
return;
}
// Check the current state of the proposal
const voteState = await checkProposalState(proposalId);
// Only allow voting if the proposal is in the Active state
if (voteState.stateName !== 'Active') {
console.log(
`Proposal is in ${voteState.stateName} state. Please wait until it reaches Active state to vote.`
);
return;
}
// Cast a vote on the proposal
await castVote(proposalId);
break;
case '--execute':
// Ensure a proposal ID is set
if (!proposalId) {
console.error('Please set the PROPOSAL_ID in your .env file');
return;
}
// Check the current state of the proposal
const executeState = await checkProposalState(proposalId);
// Only allow execution if the proposal is queued
if (executeState.stateName !== 'Queued') {
console.log(
`Proposal is in ${executeState.stateName} state. Please wait until it reaches Queued state to execute.`
);
return;
}
// Execute the proposal
await executeProposal(proposalId);
break;
case '--check-state':
// Ensure a proposal ID is set
if (!proposalId) {
console.error('Please set the PROPOSAL_ID in your .env file');
return;
}
// Check and display the current state of the proposal
const { stateName } = await checkProposalState(proposalId);
console.log(`Current proposal state: ${stateName}`);
// Get and display the next stage of the proposal
const nextStage = await getNextStage(stateName);
if (nextStage !== 'End') {
console.log(`Next stage: ${nextStage}`);
} else {
console.log('This is the final stage of the proposal.');
}
break;
case '--cancel':
// Ensure a proposal ID is set
if (!proposalId) {
console.error('Please set the PROPOSAL_ID in your .env file');
return;
}
// Check the current state of the proposal
const cancelState = await checkProposalState(proposalId);
// Allow cancellation if the proposal is in a cancellable state
if (!['Pending', 'Active', 'Succeeded'].includes(cancelState.stateName)) {
console.log(`Proposal is in ${cancelState.stateName} state and cannot be cancelled.`);
return;
}
// Cancel the proposal
await cancelProposal(proposalId);
break;
default:
// If an invalid flag is provided, show usage instructions
console.log('Please provide a valid flag:');
console.log('--create-vault: Create a new rewards vault');
console.log('--create-proposal: Create a new governance proposal');
console.log('--vote: Vote on the proposal specified in .env');
console.log('--queue: Queue the proposal specified in .env');
console.log('--execute: Execute the proposal specified in .env');
console.log('--check-state: Check the current state of the proposal');
}
}
// Run the main function and catch any errors
main().catch((error) => {
console.error(error);
process.exit(1);
});
main
関数のこれらの新しいケースにより、.env
ファイルのPROPOSAL_ID
を使用してガバナンスプロポーザルに投票し、実行することができます。各ケースは、それぞれのアクションを実行する前にPROPOSAL_ID
が設定されているかどうかをチェックするので、.env
が更新されていることを確認してください。
プロポーザルの進捗をモニタリングする
プロポーザルを作成したら、その進捗をモニタリングし、適切なタイミングで行動を起こす必要があります。以下のコマンドを使用して、プロポーザルの現在の状態を確認してください:
# FROM: ./berachain-rewards-vault
node governance.js --check-state
プロポーザルの状態に応じて、コマンドを実行するか、プロセスの次の段階を待つ必要があります。
-
Pending
: This is the initial state. This stage takes three hours and you cannot vote or execute your proposal in this stage. -
Active
: This is the voting period. Cast your vote by running: -
Pending
(保留中): これは初期状態です。この段階は 3 時間続き、この間はプロポーザルに投票したり実行したりすることはできません。 -
Active
(アクティブ): これは投票期間です。以下のコマンドを実行して投票してください:
# FROM: ./berachain-rewards-vault
node governance.js --vote
-
Succeeded
(成功): プロポーザルが投票期間を通過しました。これで実行のためにキューに入れられました。 -
Queued
(キュー済み): プロポーザルはタイムロック期間にあります。この期間が終了すると、プロポーザルを実行できます。この時点で、あなたの LP Rewards Vault が Berachef に正式に追加されます:
# FROM: ./berachain-rewards-vault
node governance.js --execute
-
Executed
(実行済み): プロポーザルが正常に実施されました。これ以上のアクションは必要ありません。
プロポーザルの進捗を監視するために、--check-state
コマンドを頻繁に使用してください。
⚠️ 警告: ガバナンスプロセスに含まれる待機期間のため、このプロセスには数時間かかる場合があります。辛抱強く待ち、状態を確認し続けてください。
もしプロポーザルがDefeated
(否決)またはExpired
(期限切れ)の状態になった場合は、以下のコマンドを実行して新しいプロポーザルを作成する必要があります:
# FROM: ./berachain-rewards-vault
node governance.js --create-proposal
新しいプロポーザルを作成する場合は、.env
ファイルのPROPOSAL_ID
を削除し、新しいものを追加することを忘れないでください。
プロポーザルのキャンセル
プロセスのどの段階でも、以下のコマンドを実行することでプロポーザルをキャンセルすることができます。
# FROM: ./berachain-rewards-vault
node governance.js --cancel
これは取り消しできないアクションですが、新しい ID でガバナンスプロポーザルを作成することはできます。
完全な GitHub コードリポジトリ
最終的なコードを確認したり、他のガイドを見たい場合は、Berachain Guides GitHub Repositoryをチェックしてください。
以下のコマンドを実行してください:
git clone https://github.com/berachain/guides.git
cd guides/apps/berachain-governance-proposal
npm install
README
に記載されている指示に従って、このスクリプトをダウンロードし実行してください。
次のステップは?
🛠️ もっとビルドしたいですか?
Berachain 上でさらに開発を進めたり、より多くの例を見たい場合は、Berachain GitHub Guides Repo をご覧ください。NextJS、Hardhat、Viem、Foundry など、様々な実装例が幅広く用意されています。
開発者サポートをお探しですか?
質問をするために、Berachain Discordに参加し、開発者チャンネルをチェックしてください。
【Sunrise とは】
Sunrise は Proof of Liquidity(PoL)と Fee Abstraction(手数料抽象化)を備えたデータ可用性レイヤーです。 私たちは DA の体験を再構築し、多様なエコシステムからのモジュラー型流動性を活用してロールアップを立ち上げています。
【Social Links】
【お問合せ】
Sunrise へのお問い合わせはこちらから 👉 Google Form