Guard controller
The GuardController class provides type-safe access to Bloxchain GuardController contracts for execution control, target whitelisting, and function schema management.
🎯 Overview
GuardController extends BaseStateMachine with execution control features:
Generic contract delegation with full EngineBlox workflows
Target whitelist management via batch configuration
Function schema registration for runtime function management
Time-locked execution with approval workflows
Meta-transaction support for all operations
Native token transfer support
🚀 Quick Start
import { GuardController } from '@bloxchain/sdk/typescript'
import { createPublicClient, createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
// Initialize clients
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
const walletClient = createWalletClient({
account: privateKeyToAccount('0x...'),
chain: mainnet,
transport: http()
})
// Create GuardController instance
const guardController = new GuardController(
publicClient,
walletClient,
'0x...', // contract address
mainnet
)📖 Core Features
1. Execution Functions
Request Time-Locked Execution
Approve Time-Locked Transaction
Request and Approve in One Step (Meta-Transaction)
2. Guard Configuration Batch
GuardController uses batch configuration for whitelist management and function schema registration.
Guard Configuration Actions
Register Function Schema
Add Target to Whitelist
Batch Multiple Operations
3. Whitelist Management
Get Function Whitelist Targets
Note: The whitelist is per-function-selector. Multiple functions can have different whitelists.
Pending transactions and delisting: The core engine re-validates the stored execution target against the whitelist when cancelling or completing a pending tx. If you REMOVE_TARGET_FROM_WHITELIST while matching txs are still PENDING, cancel and finalize paths revert until that target is whitelisted again. Operational mitigation: (1) clear or cancel pendings before delisting, or (2) temporarily ADD_TARGET_TO_WHITELIST the same target, cancel or run the approve/execute path to completion, then remove the target again.
Pending transactions and UNREGISTER_FUNCTION: Unregistering removes the execution selector from the supported-function set. Pending txs that still reference that selector then fail _validateTargetWhitelist with ResourceNotFound on cancel/complete until the function is registered again (registerFunction) and whitelist policy is restored as needed. Prefer no pending txs for that selector before unregister, or use re-register → cancel/complete → unregister.
4. Function Schema Management
Function schema registration is now handled by GuardController (moved from RuntimeRBAC).
Get Function Schema
🔄 Complete Workflow Example
Setup: Register Function and Whitelist Target
Execute: Call Whitelisted Function
📡 Event Monitoring
Listen for Guard Configuration Events
Contracts emit ComponentEvent(bytes4 functionSelector, bytes data). For guard config, filter by the executeGuardConfigBatch selector and decode data as (GuardConfigActionType, bytes4 functionSelector, address target). See generated contract API.
🛡️ Security Features
1. Whitelist Validation
For external contract targets, the address must be on the per-function-selector whitelist (unless the engine rule below applies):
target == address(this) (engine vs GuardController):
EngineBlox._validateTargetWhitelist: Once the execution selector is registered,TxParams.target == address(this)is always allowed and does not requireaddress(this)to appear infunctionTargetWhitelist[executionSelector]. That supports internal / composed execution (macros, vault logic on the same contract) without bloating every whitelist with the state machine’s own address. RBAC (who may request/approve/execute) still applies; the whitelist is not the only control plane.Attached payouts: Recipient and ERC20 token contracts are still validated under
ATTACHED_PAYMENT_RECIPIENT_SELECTOR/ERC20_TRANSFER_SELECTORwhen amounts are non-zero.GuardControllerpublic entrypoints: In addition to the engine,_validateNotInternalFunctionblockstarget == address(this)unless the execution selector is an allowed system macro selector (e.g. native transfer–class flows). So arbitrary “call any internal selector on self” is not exposed through the standardGuardControllerexecute paths, while the engine’s whitelist rule remains as documented above for other integrations.
Attached payments (executeWithPayment): payout policy uses two extra whitelist keys (registered when GuardController definitions load—same ADD_TARGET_TO_WHITELIST batch flow). Base-only state machines that use attached payments without GuardController must register these schemas separately (see PaymentTestHelper in tests). The core engine reverts (ResourceNotFound) if whitelist validation runs for a selector that is not yet in the supported-function set—there is no silent skip.
Selector (see EngineBlox SDK / Solidity)
What to whitelist
ATTACHED_PAYMENT_RECIPIENT_SELECTOR
payment.recipient for native and ERC20 attached payouts
ERC20_TRANSFER_SELECTOR (transfer(address,uint256))
payment.erc20TokenAddress — token contracts the vault may pay out via safeTransfer
ERC20 token model (attached payouts): The engine transfers the nominal erc20TokenAmount via safeTransfer and does not verify recipient credits against balance deltas. Fee-on-transfer, deflationary, rebasing, or otherwise non-standard ERC20 tokens are not supported for attached payments—use conventional tokens only, or you risk accounting mismatch and disputes.
Primary execution still uses TxParams.target whitelisted under the execution function selector (e.g. mint, NATIVE_TRANSFER_SELECTOR).
2. Function Schema Protection
Protected function schemas cannot be unregistered:
3. Role-Based Permissions
Combine with RuntimeRBAC for role-based access control:
🔧 Advanced Usage
Native Token Transfers
GuardController supports native token (ETH) transfers:
Design note:
NATIVE_TRANSFER_SELECTORis a convenience path for ETH-only transfers (empty params). Non-native selectors may still intentionally forward ETH to payable targets for edge-case workflows; default practice is to keepvalue = 0for standard function calls.
Time-Locked Operations
For operations requiring additional security:
🧪 Testing
Unit Testing
Integration Testing
🚨 Common Issues
Issue: "Target not whitelisted"
Solution: Add the target to the whitelist using ADD_TARGET_TO_WHITELIST action in a guard config batch.
Issue: "Function schema not found"
Solution: Register the function schema using REGISTER_FUNCTION action via GuardController (not RuntimeRBAC).
Issue: "Transaction execution failed"
Solution: Ensure:
Function schema is registered
Target is whitelisted for the function
Caller has appropriate role permissions (via RuntimeRBAC)
Gas limit is sufficient (a
gasLimitof 0 inTxParamsmeans "forward all remaining gas" — equivalent togasleft(). Set a positive value for a strict upper bound.)
Issue: "Handler selector mismatch"
Solution: Ensure the function selector in the execution params matches the registered function schema.
Issue: OWNER cannot call executeWithPayment
Solution: Default GuardControllerDefinitions registers the schema for executeWithPayment (EXECUTE_WITH_PAYMENT_SELECTOR in contracts/core/execution/lib/definitions/GuardControllerDefinitions.sol) but intentionally does not grant an OWNER_ROLE FunctionPermission for it—the permission surface is kept minimal out of the box.
You cannot fix this with guard-config: getGuardConfigActionSpecs() / GuardConfigActionType only cover whitelist add/remove, REGISTER_FUNCTION, and UNREGISTER_FUNCTION—there is no guard-config action to attach a selector to a role.
Add the grant via RBAC (role-config) after init: use roleConfigBatchRequestAndApprove → executeRoleConfigBatch with RoleConfigActionType.ADD_FUNCTION_TO_ROLE, following the encoders and batch ordering constraints in contracts/core/access/lib/definitions/RuntimeRBACDefinitions.sol, and satisfy schema / handler rules from GuardControllerDefinitions.sol (e.g. handlerForSelectors for executeWithPayment must match what addFunctionToRole enforces against that schema).
Issue: NATIVE_TRANSFER_SELECTOR not found / native ETH flows revert
Solution: GuardControllerDefinitions already registers the NATIVE_TRANSFER_SELECTOR pseudo-schema (plus ATTACHED_PAYMENT_RECIPIENT_SELECTOR and ERC20_TRANSFER_SELECTOR). If your contract inherits from a base-only state machine without loading GuardControllerDefinitions, you must register these schemas manually via a guard-config or definition bundle.
Issue: Unregistered function still has whitelist entries / hooks
Solution: EngineBlox.unregisterFunction removes the schema and supported-function membership but does not sweep functionTargetWhitelist or functionTargetHooks for that selector. Stale rows are inert while the selector is absent (whitelist checks revert ResourceNotFound), but they reappear if the selector is re-registered. Clear them explicitly with REMOVE_TARGET_FROM_WHITELIST / clearHook before or after unregistering.
Issue: Payment failure rolls back the entire execution
Solution: This is intentional all-or-nothing atomicity. If the main call succeeds but executeAttachedPayment reverts (e.g. insufficient balance, whitelist mismatch), the whole approval/execute transaction reverts including the main effect. Ensure payment prerequisites (balance, whitelist) are met before submitting.
📚 Related Documentation
RuntimeRBAC Guide - Role and permission management
API Reference - Complete API documentation
State Machine Engine - State machine architecture
Best Practices - Development guidelines
Last updated
Was this helpful?