You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Balancer池用户授权方法及BAL#401错误解决咨询

解决Balancer池授权问题(BAL#401错误)

问题根源

BAL#401错误说明你的BalancerOperator合约没有被授权调用Balancer Vault的joinPool、exitPool、swap等核心操作。Balancer V2的权限由TimelockAuthorizer合约统一管理,必须为你的Operator合约配置对应操作的权限才能执行交易。

授权步骤与核心函数

你使用的主网Authorizer地址为0xa331d84ec860bf466b4cdccfb4ac09a1b43f3ae6,授权需分两步完成:

1. 生成操作权限标识符(Action ID)

每个Vault操作对应唯一的Action ID,通过对函数签名做keccak256哈希生成:

  • 加入流动性(joinPool):
    bytes32 joinPoolActionId = keccak256("joinPool(bytes32,address,address,(address[],uint256[],bytes,bool))");
    
  • 退出流动性(exitPool):
    bytes32 exitPoolActionId = keccak256("exitPool(bytes32,address,address,(address[],uint256[],bytes,bool))");
    
  • 代币交换(swap):
    bytes32 swapActionId = keccak256("swap((bytes32,enum(SwapKind),address,address,uint256,bytes),(address,bool,address,bool),uint256,uint256)");
    

2. 调用Authorizer的授权函数

在Hardhat分叉环境中,你需要模拟Authorizer管理员账户(主网多签账户),调用grantPermission函数为你的Operator合约授权:

function grantPermission(bytes32 actionId, address account, address target) external;

参数说明:

  • actionId:对应操作的权限标识符
  • account:需要授权的地址(你的BalancerOperator合约地址)
  • target:被授权操作的目标合约(Vault地址0xBA12222222228d8Ba445958a75a0704d566BF2C8)

Hardhat测试脚本示例

const { ethers } = require("hardhat");

async function main() {
  // 模拟主网管理员账户(分叉环境中使用)
  const adminAddr = "0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f";
  await hre.network.provider.request({
    method: "hardhat_impersonateAccount",
    params: [adminAddr],
  });
  const adminSigner = await ethers.getSigner(adminAddr);

  // 加载Authorizer合约
  const authorizer = await ethers.getContractAt(
    "IAuthorizer",
    "0xa331d84ec860bf466b4cdccfb4ac09a1b43f3ae6",
    adminSigner
  );

  const vaultAddr = "0xBA12222222228d8Ba445958a75a0704d566BF2C8";
  const operatorAddr = "你的BalancerOperator合约部署地址";

  // 授权joinPool权限
  const joinPoolActionId = ethers.utils.keccak256(
    ethers.utils.toUtf8Bytes("joinPool(bytes32,address,address,(address[],uint256[],bytes,bool))")
  );
  await authorizer.grantPermission(joinPoolActionId, operatorAddr, vaultAddr);

  // 授权exitPool权限
  const exitPoolActionId = ethers.utils.keccak256(
    ethers.utils.toUtf8Bytes("exitPool(bytes32,address,address,(address[],uint256[],bytes,bool))")
  );
  await authorizer.grantPermission(exitPoolActionId, operatorAddr, vaultAddr);

  // 授权swap权限
  const swapActionId = ethers.utils.keccak256(
    ethers.utils.toUtf8Bytes("swap((bytes32,enum(SwapKind),address,address,uint256,bytes),(address,bool,address,bool),uint256,uint256)")
  );
  await authorizer.grantPermission(swapActionId, operatorAddr, vaultAddr);

  console.log("权限授权完成");
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

关键注意事项

  • 每个Vault操作需要单独授权,不能一次性批量授权所有权限。
  • 分叉环境中必须模拟管理员账户才能调用grantPermission,主网环境需通过Balancer多签账户执行授权。

合约优化建议

你的BalancerOperator无需继承IVault,直接通过接口调用更简洁:

contract BalancerOperator {
    IVault private immutable _vault;

    constructor(address vaultAddr) {
        _vault = IVault(vaultAddr);
    }

    // 加入流动性函数
    function addLiquidity(
        bytes32 poolId,
        address[] calldata assets,
        uint256[] calldata maxAmountsIn
    ) external {
        bytes memory userData = abi.encode(1, maxAmountsIn, 1);
        IVault.JoinPoolRequest memory request = IVault.JoinPoolRequest(assets, maxAmountsIn, userData, false);
        _vault.joinPool(poolId, msg.sender, msg.sender, request);
    }

    // 其余函数逻辑保持不变,调用时直接使用_vault.exitPool(...)、_vault.swap(...)
}

内容的提问来源于stack exchange,提问作者Ignacio Ceaglio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 12:48:18