这篇文章简要介绍了如何在 Berachain 上创建奖励金库并通过治理提案将其列入 BeraChef 白名单。内容包括设置开发环境、提交提案、监控提案进展并进行投票和执行。
导言
本教程由 Black Bera 和 Manny Bera 编写, Rama 翻译。
教程旨在为开发者提供一个清晰的操作指南,帮助大家在 Berachain 上创建并管理奖励金库,推动流动性激励和治理进程。
文章非常详细,不仅涵盖了从创建智能合约到提交治理提案的各个步骤,还详细讲解了如何将激励代币列入白名单,优化用户体验和流动性激励机制。
希望 DAppChaser 编译能让更多中文区的开发者能够快速上手并深入探索 Berachain 的去中心化生态系统。
Rama
Berachain 流动性共识机制的最大优势之一,是协议可以通过获取 Berachain 治理代币 ($BGT) 的发放来启动其流动性。这一累积 $BGT 的过程通过奖励金库(由 RewardsVaults 智能合约代表)完成。
注意:你可以在测试网中看到 Berps 奖励金库智能合约的实际运行情况。
$BGT 在奖励金库中累积的过程涉及选择一个验证人提议一个区块,当该验证人被选中时会获得 $BGT,并通过一个名为 BeraChef 的分发合约将大部分 $BGT 分配到一个或多个奖励金库中。验证人的奖励受他们在提议区块时被委派的 $BGT 数量影响。
$BGT 的用途
$BGT 有三个主要功能,包括:将其销毁以获取 $BERA,将其委派给验证人以提高他们的奖励,或者在治理中使用 $BGT 提出或投票通过提案。
BEX 的奖励金库示例
BEX 是 Berachain 上的一个原生 PoL AMM 协议,同时拥有多个奖励金库。对于用户贡献流动性的每个白名单池,他们会根据其总贡献量获得相应的 LP 代币。
例如,用户可以向 HONEY<>WBTC 池添加流动性,并获得 $HONEY-WBTC LP 代币作为回报。
然后,你可以将 LP 代币质押到 BEX 的 $HONEY-WBTC 奖励金库中。当 LP 代币被质押并且验证人将 $BGT 发放至奖励金库时,LP 代币会随着时间累积 $BGT。这就是 BEX 激励用户为其协议增加流动性的方式。
如果你利用 BEX 为你的协议代币(我们称之为 $BBT,即 Black Bera Token)提供交易流动性,并将其与 $WBERA 配对,用户在提供流动性时会收到 $BBT-WBERA-LP 代币。
如果该 LP 代币被白名单认证,意味着它有一个奖励金库可以接受该代币进行质押,那么它有资格获得 $BGT 奖励,从而通过 PoL 启动你协议的流动性。
这些奖励金库由 RewardsVault Factory 创建,这是 Berachain 上的一个智能合约,负责创建奖励金库。
虽然创建自己的奖励金库是无许可的,但要让验证人将他们的 $BGT 发放到你的奖励金库中,必须首先提交并批准治理提案,将你的奖励金库添加到 BGT Station。一旦提案获得通过,你的奖励金库就会成为 “friendOfTheChef”,被列入允许验证人发放 $BGT 的合格奖励金库集合中。这个过程也被称为“白名单认证”。
理解 Berachain 白名单认证的治理流程
为了了解我们即将构建的内容,首先我们需要了解创建奖励金库、提出提案、对提案进行投票、以及激活奖励金库的治理流程。
治理要求
在成功创建提案之前,有一些要求和步骤需要考虑:
- 最低 1000 $BGT
所有提案都需要至少 1000 $BGT,可以通过直接购买或让他人将他们的 $BGT 委派给提案人来满足这一要求。 - 委派/自委派投票权
如果达到了最低的 $BGT 要求,无论是提出提案还是对现有提案进行投票,所拥有的 $BGT 都需要委派给提案人,甚至可以自我委派。 - 达到法定人数
赞成票必须达到至少 20 亿的法定人数(Quorum),这一数字可能在接近主网时发生变化。不过在测试网上,你可以请求 Berachain 团队帮助你达到这一法定人数。
治理生命周期
以下是提案在整个治理流程中将经历的不同状态。
Berachain 治理提案的生命周期
- 提案创建(待处理状态)
等待期:提案需要等待 3 小时才能进入投票
在此状态下,提案也可以被取消。 - 投票开始(活动状态)
持续时间:3 小时
$BGT 持有者可以进行投票
必须达到法定人数:最低 20 亿 $BGT。 - 投票结束
提案会成功或失败。
如果失败:标记为失败状态,提案人可以在解决问题后重新提交提案。 - 时间锁(如果成功)
提案进入队列,延迟 3 小时时间锁。 - 执行或过期
执行:只能由 Berachain 批准的治理 EOA 执行。
过期:如果在指定时间内未执行则会过期。
*注意:这些时间段可能会在接近主网启动时更改。
创建 BEX 池以用于你的奖励金库
在本教程中,我们将创建一个奖励金库,该金库接受 LP 代币,使质押的用户有资格获得 $BGT 发放。一旦使用我们的 LP 代币创建了这个奖励金库,我们就可以将其提交给治理,以将其添加为 Berachef 的 “Friend”。
首先,我们将创建 BEX 池及其质押代币。
要创建一个新的池子,你需要提供你的协议代币,在此例中为新创建的代币 $BBT,以及 $WBERA 代币,以在 BEX 上创建一个自定义池子。
一旦池子创建并完成存款后,你将收到 LP 代币。任何后续存款都会生成更多的 LP 代币。
示例部署的池子和 LP 代币
创建奖励金库和治理提案的项目设置
现在我们已经有了 $BBT <> $WBERA 的 BEX 池及其相应的 LP 代币 ($BBT-WBERA-LP),我们需要先创建一个奖励金库,才能提交治理提案,将我们的奖励金库添加到 BeraChef。
我们将通过创建一个项目,运行脚本来完成以下操作:
- 为我们的 LP 代币创建一个奖励金库
- 向 Berachain 治理提交提案,将我们的新奖励金库添加到 BeraChef
- 对我们的奖励金库进行投票
- 通过将奖励金库添加到 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 文件
# 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,或替换为你自己的 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. 添加创建奖励金库的代码
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 代币创建一个新的奖励金库,并记录创建的金库地址。
文件路径:./.env
将这个新值添加到你的 .env
文件中
REWARDS_VAULT_ADDRESS=value from terminal
注意:你的奖励金库地址将与此截图中的地址不同。
提交你的治理提案
现在我们已经创建了奖励金库,接下来是提交治理提案,将其列入白名单。我们将为此过程创建一个新的脚本。
1.设置环境
在此步骤中,你的 .env
文件应包含以下值:
文件路径:./.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. 创建提案函数
现在,在同一个文件 governance.js
中,在 getOrCreateVault
函数的下方添加以下函数。
文件路径:./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. 将命令添加到主函数
接下来,在你的 governance.js
文件中的主函数中,--create-vault
之后添加以下 switch case
。这将用于处理治理提案的创建:
文件路径:./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🚨
}
}
这个主函数中的新 switch case
允许你创建一个治理提案,将你新创建的奖励金库更新为 “friends of the chef”。它会检查是否有足够的投票权,获取(或创建)金库地址,然后调用 createProposal
函数并传递必要的参数。
4.运行 create-proposal
命令
现在你已经将 --create-proposal
case 添加到主函数中,你可以运行命令来创建治理提案,将你的奖励金库添加到 BeraChef。 在终端中执行以下命令:
# FROM: ./berachain-rewards-vault
node governance.js --create-proposal
该命令将创建一个新的治理提案,以将你的奖励金库列入白名单。脚本将输出一个提案 ID。你需要将此 ID 添加到你的 .env
文件中,以便后续步骤使用。
运行该命令后,你应该会看到类似以下的输出结果:
按照输出中的指示更新你的 .env
文件。添加一行带有 PROPOSAL_ID
值的内容:
文件路径:./.env
PROPOSAL_ID=your_proposal_id
此 PROPOSAL_ID
将在治理流程的后续步骤中使用,例如投票、排队和执行提案。它代表了你将奖励金库添加到 BeraChef 的治理提案的 ID。
5.创建投票和执行的函数
在 createProposal
函数下方,将这些函数添加到你的 governance.js
文件中:
文件路径:./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;
}
}
async function whitelistIncentiveToken(tokenAddress, minIncentiveRate) {
console.log('Whitelisting incentive token...');
try {
if (!process.env.REWARDS_VAULT_ADDRESS) {
console.error('Please set the REWARDS_VAULT_ADDRESS in your .env file');
return;
}
const vaultAddress = process.env.REWARDS_VAULT_ADDRESS;
rewardsVault = new ethers.Contract(vaultAddress, BerachainRewardsVaultABI, wallet);
const targets = [vaultAddress];
const values = [0];
const calldatas = [rewardsVault.interface.encodeFunctionData('whitelistIncentiveToken', [tokenAddress, minIncentiveRate])];
const description = `Whitelist incentive token ${tokenAddress}`;
await createProposal(targets, values, calldatas, description);
} catch (error) {
console.error('Error whitelisting incentive token:', error);
throw error;
}
}
这些函数处理治理流程的不同阶段:
- castVote:此函数允许你对提案进行投票。它首先检查你是否已经投票,如果没有,则会投出 “yes” 票(以 1 表示)。
- executeProposal:一旦提案被排队并且时间锁期已过,可以调用此函数来执行提案。这是实施治理流程中提案变更的最后一步。
- cancelProposal:在提案被排队执行之前,你可以取消提案,例如如果你填写了错误的地址或描述,并希望创建一个新的提案。
现在,将 governance.js
文件中的主函数替换为以下代码:
文件路径:./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;
case '--whitelist-incentive':
if (!process.env.INCENTIVE_TOKEN) {
console.error('Please set the INCENTIVE_TOKEN address in your .env file');
return;
}
if (!process.env.REWARDS_VAULT_ADDRESS) {
console.error('Please set the REWARDS_VAULT_ADDRESS in your .env file');
return;
}
// Ensure the user has enough voting power to create a proposal
if (!(await ensureSufficientVotingPower())) return;
// Set a default minIncentiveRate (you may want to make this configurable)
const minIncentiveRate = ethers.parseUnits('0.01', 18); // 0.01 token per BGT emission
await whitelistIncentiveToken(process.env.INCENTIVE_TOKEN, minIncentiveRate);
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);
});
这些主函数中的新 case
语句允许你使用 .env
文件中的 PROPOSAL_ID
来对治理提案进行投票和执行。每个 case
在执行相应操作之前都会检查 PROPOSAL_ID
是否已设置,因此请确保你的 .env
文件已更新。
监控提案进展
现在你已经创建了提案,你需要监控其进展并在适当的时间采取行动。使用以下命令来检查你的提案当前的状态:
# FROM: ./berachain-rewards-vault
node governance.js --check-state
根据提案的状态,你可能需要运行一个命令,或等待流程的下一个阶段:
- Pending(待处理):这是初始状态。此阶段持续三个小时,在此期间你无法投票或执行提案。
- Active(活跃):这是投票阶段。运行以下命令来投票:
# FROM: ./berachain-rewards-vault
node governance.js --vote
- Succeeded(通过):提案已通过投票阶段。现在已进入排队等待执行阶段。
- Queued(排队中):提案处于时间锁阶段。一旦该阶段结束,你可以执行提案,此时你的 LP 奖励金库将正式添加到 BeraChef。运行以下命令:
node governance.js --execute
# 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
并添加新的提案 ID。
取消提案
你也可以在流程的任何阶段通过运行以下命令取消提案。
# FROM: ./berachain-rewards-vault
node governance.js --cancel
这是一个不可逆的操作,不过你可以使用新的 ID 创建一个治理提案。
将激励代币列入白名单4o
在创建奖励金库并通过治理获得批准后,你可以将额外的代币列入白名单作为激励。将激励代币列入白名单允许你在奖励金库中与 $BGT 发放一起分配该代币。
要将代币列入白名单,你需要创建另一个治理提案,指定代币地址和最低激励比率。最低激励比率确保分配的激励代币数量相对于 $BGT 发放有一定的意义。4o
要控制列入白名单代币的最低激励比率,你可以在 governance.js
脚本中修改 whitelistIncentiveToken
函数。查找以下这行代码:
const minIncentiveRate = ethers.parseUnits('0.01', 18); // 0.01 token per BGT emission
这一行代码将最低激励比率设置为每次 BGT 发放 0.01 个代币。你可以根据需要调整此值。例如,要将其设置为每次 BGT 发放 0.1 个代币,你可以将其更改为:
const minIncentiveRate = ethers.parseUnits('0.1', 18); // 0.1 token per BGT emission
在调整好激励比率后,若要发起列入激励代币白名单的治理提案,你可以使用以下命令:
node governance.js --whitelist-incentive
在运行此命令之前,确保你的 .env
文件中更新了以下信息:
- INCENTIVE_TOKEN:将其设置为你想要列入白名单作为激励的代币地址。
- REWARDS_VAULT_ADDRESS:确保此值设置为你的奖励金库地址。
例如,你的 .env
文件应包含以下内容:
INCENTIVE_TOKEN=0x1234...5678
REWARDS_VAULT_ADDRESS=0xabcd...efgh
运行该命令将创建一个新的治理提案,以将你的激励代币列入白名单。按照之前的流程监控、投票并执行该提案,以完成白名单认证过程。
完整的 GitHub 代码仓库
如果你想查看最终代码,请参考以下链接中的 Berachain Guides GitHub 仓库。
运行以下命令:
git clone https://github.com/berachain/guides.git
cd guides/apps/berachain-governance-proposal
npm install
按照附带的 README
指南下载并运行此脚本。
接下来怎么做?
🛠️ 想 BUIDL 更多?
如果你想在 Berachain 上构建更多项目并查看更多示例,请查看 Berachain GitHub Guides Repo , 里面有各种教程。
如果你想深入了解详情,请查看 Berachain Docs.
寻求开发支持?
确保加入 Berachain Discord ,查看开发者频道,向社区和 DevRels 提问并获取支持。
❤️ 别忘了为这篇文章点个赞 👏🏼
原创文章,作者:Rama Ai,如若转载,请注明出处:https://www.dappchaser.com/creating-a-governance-proposal-for-berachain-reward-vaults/