// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "./BandwidthSettlementVaultDeployer.sol"; import "./BandwidthAccessStaking.sol"; import "./FlapVaultFactoryBase.sol"; contract BandwidthSettlementVaultFactory is VaultFactoryBaseV2 { event VaultCreated(address indexed taxToken, address indexed vault, address indexed creator, address staking); BandwidthSettlementVaultDeployer private immutable vaultDeployer; address private immutable defaultSafeWallet; uint256 public constant minimumStakeTokens = 500_000; mapping(address => address) public vaultAccessStaking; struct VaultConfig { address operator; address guardian; address safeWallet; uint16 contributorTaxBps; uint16 advanceTaxBps; uint16 gasTaxBps; uint16 reserveTaxBps; uint16 opsTaxBps; uint16 externalContributorBps; uint16 externalAdvanceBps; uint16 externalGasBps; uint16 externalReserveBps; uint16 externalOpsBps; } constructor(address defaultSafeWallet_) { require(defaultSafeWallet_ != address(0), "SAFE_WALLET_REQUIRED"); defaultSafeWallet = defaultSafeWallet_; vaultDeployer = new BandwidthSettlementVaultDeployer(); } function description() external pure returns (string memory) { return unicode"RelayVault 带宽结算金库工厂|每个金库自动绑定当前税收代币的共享带宽访问质押,最低 500,000 枚;按已确认的小时周期向带宽贡献者结算。"; } function vaultDataSchema() public pure override returns (VaultDataSchema memory schema) { schema.description = unicode"创建 RelayVault 带宽结算金库。安全钱包字段只接受受控部署脚本编码,不在公开 Flap 表单显示。"; schema.isArray = false; schema.fields = new FieldDescriptor[](2); schema.fields[0] = FieldDescriptor("operator", "address", unicode"项目运营钱包;留空使用创建者。", 0); schema.fields[1] = FieldDescriptor("guardian", "address", unicode"项目守护钱包;留空使用运营钱包。", 0); } function vaultFactoryPolicy() external pure returns (FactoryPolicy[] memory policy) { policy = tokenCreationPolicies(); } function tokenCreationPolicies() public pure override returns (FactoryPolicy[] memory policies) { policies = new FactoryPolicy[](2); policies[0] = FactoryPolicy({ target: "quoteToken", operator: "eq", value: abi.encode(address(0)), description: "Quote token must be native BNB." }); policies[1] = FactoryPolicy({ target: "mktBps", operator: "gt", value: abi.encode(uint16(0)), description: "Market allocation must be greater than zero so tax revenue can flow to the vault." }); } function _validateBeforeLaunch(IVaultFactoryValidationV2.LaunchValidationDataV1 memory data) internal view override returns (bool success, string memory reason) { if (block.chainid != 56 && block.chainid != 97) return (false, "BSC_ONLY"); data.tokenVersion; if (data.quoteToken != address(0)) return (false, "QUOTE_TOKEN_NOT_SUPPORTED"); if (data.vaultBps == 0) return (false, "VAULT_BPS_REQUIRED"); if (data.buyTaxRate == 0 && data.sellTaxRate == 0) return (false, "TAX_RATE_REQUIRED"); return (true, ""); } function isQuoteTokenSupported(address quoteToken) external pure override returns (bool) { return quoteToken == address(0); } function newVault(address taxToken, address quoteToken, address creator, bytes calldata vaultData) external override returns (address vaultAddress) { require(block.chainid == 56 || block.chainid == 97, "BSC_ONLY"); require(_isSupportedVaultPortalCaller(), "ONLY_VAULT_PORTAL"); require(quoteToken == address(0), "QUOTE_TOKEN_NOT_SUPPORTED"); require(taxToken != address(0), "TAX_TOKEN_REQUIRED"); require(creator != address(0), "CREATOR_REQUIRED"); VaultConfig memory cfg = _decodeConfig(creator, vaultData); _validateConfig(cfg); // Flap passes the CREATE2-predicted tax-token address before the token // bytecode exists. Tax Token V1/V2/V3 use 18 decimals, so bind the // access stake in base units without calling the not-yet-deployed token. address staking = address(new BandwidthAccessStaking(taxToken, minimumStakeTokens * 1 ether)); vaultAddress = vaultDeployer.deploy(taxToken, staking, abi.encode(cfg)); vaultAccessStaking[vaultAddress] = staking; emit VaultCreated(taxToken, vaultAddress, creator, staking); } function _decodeConfig(address creator, bytes calldata vaultData) internal view returns (VaultConfig memory cfg) { if (vaultData.length == 0) return _defaultConfig(creator, creator, defaultSafeWallet); if (vaultData.length == 64) { (address operator, address guardian) = abi.decode(vaultData, (address, address)); if (operator == address(0)) operator = creator; if (guardian == address(0)) guardian = operator; return _defaultConfig(operator, guardian, defaultSafeWallet); } if (vaultData.length == 96) { (address operator, address guardian, address safeWallet) = abi.decode(vaultData, (address, address, address)); if (operator == address(0)) operator = creator; if (guardian == address(0)) guardian = operator; if (safeWallet == address(0)) safeWallet = defaultSafeWallet; return _defaultConfig(operator, guardian, safeWallet); } cfg = abi.decode(vaultData, (VaultConfig)); if (cfg.operator == address(0)) cfg.operator = creator; if (cfg.guardian == address(0)) cfg.guardian = cfg.operator; if (cfg.safeWallet == address(0)) cfg.safeWallet = defaultSafeWallet; if (_taxSplitSum(cfg) != 10_000 || _externalSplitSum(cfg) != 10_000) { return _defaultConfig(cfg.operator, cfg.guardian, cfg.safeWallet); } } function _validateConfig(VaultConfig memory cfg) internal pure { require(cfg.operator != address(0) && cfg.safeWallet != address(0), "AUTHORITY_REQUIRED"); require(_taxSplitSum(cfg) == 10_000, "INVALID_TAX_SPLIT"); require(_externalSplitSum(cfg) == 10_000, "INVALID_EXTERNAL_SPLIT"); } function _defaultConfig(address operator, address guardian, address safeWallet) internal pure returns (VaultConfig memory) { return VaultConfig({ operator: operator, guardian: guardian, safeWallet: safeWallet, contributorTaxBps: 7000, advanceTaxBps: 2000, gasTaxBps: 500, reserveTaxBps: 300, opsTaxBps: 200, externalContributorBps: 8500, externalAdvanceBps: 1000, externalGasBps: 0, externalReserveBps: 300, externalOpsBps: 200 }); } function _taxSplitSum(VaultConfig memory cfg) internal pure returns (uint256) { return uint256(cfg.contributorTaxBps) + uint256(cfg.advanceTaxBps) + uint256(cfg.gasTaxBps) + uint256(cfg.reserveTaxBps) + uint256(cfg.opsTaxBps); } function _externalSplitSum(VaultConfig memory cfg) internal pure returns (uint256) { return uint256(cfg.externalContributorBps) + uint256(cfg.externalAdvanceBps) + uint256(cfg.externalGasBps) + uint256(cfg.externalReserveBps) + uint256(cfg.externalOpsBps); } }