本記事は下記の翻訳となります。
『Creating a Governance Proposal for Berachain Reward Vaults』
Berachain の Proof of Liquidity コンセンサスメカニズムの最大の利点の 1 つは、プロトコルが Berachain ガバナンストークン($BGT
)の発行を通じて流動性を確保できることです。この $BGT
を蓄積するプロセスは、RewardsVaults
スマートコントラクトで表現されるリワードボールトを通じて行われます。
注意: テストネット上でBerps リワードボールトスマートコントラクトの動作を確認できます。
リワードボールトに $BGT
が蓄積されるプロセスは、バリデータがブロック提案者に選ばれ、選ばれた際に $BGT
を受け取り、その大半を BeraChef と呼ばれる配布コントラクトを通じて 1 つ以上のリワードボールトに分配することで行われます。バリデータの報酬は、ブロックを提案する時点で彼らに委任されている $BGT
の量によって影響を受けます。
$BGT で何ができるか?
$BGT
には主に 3 つの機能があります。それは、$BERA
に焼却すること、バリデータに委任して報酬を増やすこと、またはガバナンスで提案や投票に使用することです。
BEX のリワードボールト例
BEXは AMM プロトコルで、Berachain のネイティブな Proof of Liquidity dApp の 1 つであり、複数のリワードボールトを持っています。ユーザーがホワイトリストに登録されたプールに流動性を提供すると、その総貢献度に比例した LP トークンを受け取ります。
例えば、ユーザーはHONEY <> WBTC プールに流動性を追加し、その見返りとして$HONEY-WBTC LP トークンを受け取ることができます。
その後、LP トークンを取得し、BEX の$HONEY-WBTCリワードボールトにステーキングすることができます。LP トークンがステーキングされ、バリデータがリワードボールトに向けて$BGT
の発行を行っている間、時間とともに$BGT
が蓄積されていきます。これが BEX がユーザーにプロトコルへの流動性提供を促すやり方です。
あなたのプロトコルのトークン(仮に$BBT
、Black Bera Token とします)の取引流動性のために BEX を活用する場合、それを$WBERA
とペアにすると、ユーザーは流動性を提供する際に$BBT-WBERA-LP
トークンを受け取ります。
LP トークンがホワイトリストに登録されている場合、つまりそのトークンをステーキングのために受け入れることができるリワードボールトがある場合、$BGT
報酬の対象となり、Proof of Liquidity を通じてあなたのプロトコルの流動性を確保することができます。
これらのリワードボールトは、Berachain 上のスマートコントラクトであるリワードボールトファクトリーによって作成されます。このファクトリーがリワードボールトの作成を担当します。
自分でリワードボールトを作成することは誰でも可能ですが、バリデータがあなたのリワードボールトに$BGT
を発行するためには、まずガバナンス提案として提出し、承認を得て、BGT ステーションにリワードボールトを追加する必要があります。提案が承認されると、あなたのリワードボールトは「friendOfTheChef
」となり、バリデータが$BGT
を発行できる適格なリワードボールトのコレクションに承認されます。このプロセスは「ホワイトリスト登録」とも呼ばれています。
Berachain のホワイトリスト登録のためのガバナンスプロセスを理解する
これから構築するものを理解するために、リワードボールトの作成、提案の作成、提案への投票、リワードボールトの有効化というガバナンスプロセスについて説明しましょう。
ガバナンスの要件
成功する提案を作成する前に、考慮すべきいくつかの要件とステップがあります。
-
最低 1000
$BGT
- すべての提案には最低 1000$BGT
が必要です。これは直接獲得するか、他の人に提案者へ$BGT
を委任してもらうことで満たすことができます。 -
委任された/自己委任された投票力 - 最低
$BGT
が満たされている場合、個人所有の$BGT
で提案したり既存の提案に投票したりするには、それを提案者(提案者自身の場合も含む)に委任する必要があります。 -
定足数の達成 - 「賛成」票の過半数が 20 億の定足数を満たす必要があります。これはメインネットに近づくにつれて変更される可能性がありますが、テストネットでは、Berachain チームに定足数達成の支援を求めることができます。
ガバナンスのライフサイクル
以下は、ガバナンスプロセスのタイムライン全体を通じて提案が経る異なる状態を表しています。
Lifecycle of a Berachain Governance Proposal
1. 提案作成(保留状態)
- 待機期間:投票開始まで 3 時間
- この状態では提案をキャンセルすることも可能
2. 投票開始(アクティブ状態)
- 期間:3 時間
- BGT 保有者が投票を行う
- 定足数を満たす必要あり:最低 20 億 BGT
3. 投票終了
- 提案は成功または否決される
- 否決された場合:そのように記録され、懸念事項に対処した後に新たな提案を作成可能
4. タイムロック(成功した場合)
- 3 時間のタイムロック遅延を伴うキューに入る
5. 実行または期限切れ
- 実行:Berachain に承認されたガバナンス EOA のみが実行可能
- 期限切れ:期間内に実行されなかった場合
注意: 時間枠はプレースホルダーであり、メインネットローンチに近づくにつれて変更される可能性があります
リワードボールト用の BEX プールの作成
このチュートリアルでは、ユーザーがステーキングすることで$BGT
の発行を受け取る資格を得られる LP トークンを受け入れるリワードボールトを作成します。このリワードボールトを LP トークンと共に作成したら、ガバナンスに提出して Berachef の「フレンド」として追加することができます。
まず、BEXプールとそのステーキングトークンを作成します。
Example of Pool Creation on BEX
新しいプールを作成するには、あなたのプロトコルトークン(この場合は新しく作成された$BBT
というトークン)と$WBERA
トークンを提供して、BEX 上にカスタムプールを作成する必要があります。
プールが作成され、預入が行われると、LP トークンを受け取るはずです。さらに預入を行うと、より多くの LP トークンが発行されます。
デプロイされたプールと LP トークンの例
リワードボールトとガバナンス提案作成のためのプロジェクトセットアップ
$BBT
<> $WBERA
BEX プールとそれに対応する LP トークン($BBT-WBERA-LP
)ができたので、次は Berachef にリワードボールトを追加するガバナンス提案を作成する前に、リワードボールトを作成する必要があります。
以下の作業を行うスクリプトを実行するプロジェクトを作成します:
- LP トークン用のリワードボールトを作成する
- 新しいリワードボールトを Berachef に追加するための提案を Berachain ガバナンスに提出する
- リワードボールトに対して投票を行う
- リワードボールトを Berachef に追加して有効化する
プロジェクトの要件
スクリプトを構築するために、以下のものがコンピューターにインストールされているか、これらの要件の一部を満たしていることを確認してください。
- NVM または NodeJS
v20.16.0
以上 -
$BGT
が蓄積されているウォレット - トランザクションを処理するための
$BERA
トークン
1. 新しい JavaScript プロジェクトの初期化
LP トークン用のリワードボールトを作成する前に、開発環境をセットアップしましょう。ターミナルを開き、以下の手順に従って始めてください:
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 リワードボールトファクトリーコントラクトの ABI です。 - BerachainGovernance.json - ABI ファイル:Berachain ガバナンスコントラクトの ABI です。これは、ガバナンス提案の提出、投票、実行を行うメインのコントラクトです。
- BGT.json - ABI ファイル:
$BGT
の ABI です。 - BeraChef.json - ABI ファイル:BeraChef の ABI です。これは
$BBT
<>$WBERA
LP トークンのリワードボールトをホワイトリストに登録するコントラクトです。 - BerachainRewardsVault.json - ABI ファイル:リワードボールトの 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
- 公開 RPC または自分のものに置き換えてください -
PRIVATE_KEY
- BGT を保有しているあなたのウォレット -
FACTORY_ADDRESS
- bArtio リワードボールトファクトリーのアドレス -
LP_TOKEN_ADDRESS
- あなたの LP トークンのアドレス -
GOVERNANCE_ADDRESS
- Berachain のガバナンスコントラクトのアドレス。BeraChef のupdateFriendsOfTheChef
を呼び出す排他的な権利を持っています -
BERACHEF_ADDRESS
- BeraChef のアドレス。ホワイトリストに登録されたリワードボールトとバリデータの設定を保存するコントラクトです -
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. ヘルパー関数を追加する
次に、ヘルパー関数をコピー&ペーストしてください:
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
ファイルに貼り付け、スクリプトを実行してリワードボールトを作成してください:
# FROM: ./berachain-rewards-vault
node governance.js --create-vault
このコマンドは、私たちの LP トークン用の新しいリワードボールトを作成し、作成されたボールトのアドレスをログに記録します。
Output of node governance.js --create-vault command
File: ./.env
この新しい値を .env
ファイルに追加してください:
REWARDS_VAULT_ADDRESS=value from terminal
注意: あなたのリワードボールトのアドレスは、このスクリーンショットで見られるものとは異なります。
ガバナンス提案の提出
リワードボールトを作成したので、今度はそれをホワイトリストに登録するためのガバナンス提案を提出する時です。このプロセスのために新しいスクリプトを作成します。
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. メイン関数にコマンドを追加
次に、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"に追加するためのガバナンス提案を作成できるようになります。十分な投票力があるかをチェックし、ボールトアドレスを取得(または作成)し、必要なパラメータを使ってcreateProposal
関数を呼び出します。
4. create-proposal コマンドを実行する
main
関数に--create-proposal
ケースを追加したので、Rewards Vault を BeraChef に追加するためのガバナンス提案を作成するコマンドを実行できます。ターミナルで以下を実行してください:
# FROM: ./berachain-rewards-vault
node governance.js --create-proposal
このコマンドは、あなたの Rewards Vault をホワイトリストに追加するための新しいガバナンス提案を作成します。スクリプトは提案 ID を出力します。今後のステップのために、この ID を.env
ファイルに追加する必要があります。
コマンドを実行すると、以下のような出力が表示されるはずです:
Output of the node governance.js --create-proposal command
出力の指示に従って、.env
ファイルを更新してください。PROPOSAL_ID
の値を含む新しい行を追加します:
File: ./.env
PROPOSAL_ID=your_proposal_id
このPROPOSAL_ID
は、投票、キューイング、提案の実行など、ガバナンスプロセスの後続のステップで使用されます。これは、あなたの 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 ドキュメントをご覧ください。
開発者サポートをお探しですか?
質問をするために、Berachain Discordサーバーに参加し、開発者チャンネルをチェックしてください。
【Sunrise とは】
Sunrise は Proof of Liquidity(PoL)と Fee Abstraction(手数料抽象化)を備えたデータ可用性レイヤーです。 私たちは DA の体験を再構築し、多様なエコシステムからのモジュラー型流動性を活用してロールアップを立ち上げています。
【Social Links】
【お問合せ】
Sunrise へのお問い合わせはこちらから 👉 Google Form