Roles
The Grunt protocol uses Solady’s OwnableRoles for role-based access control. Each contract defines a set of roles as bitmask flags. An address can hold multiple roles, and each function is gated by one or more required roles.
The numeric bit value of every role, per contract, is listed on the Deployments API page and published in /deployments.json.
Role Overview
Section titled “Role Overview”| Contract | Role | Typical Holder | Permissions |
|---|---|---|---|
| Facility | Owner | Protocol Admin | Create intents, update target asset, set descriptor |
| Facility | Facilitator | Operations Bot | Create intents, lock, resolve, set caps, set fund/request, all fund/request/PM/swap operations |
| Facility | Guardian | Signers (EOA) | Sign privileged-action authorizations (multi-sig for quorum) |
| Facility | Pauser | Emergency Admin | Pause/unpause facility |
| Request | Owner | Protocol Admin | Mark loan as repaid, authorize minting |
| Request | Puller | Facility | Pull bridge loan funds, repay funds |
| Request | Consumer | Protocol Admin | Consume signed offers |
| Fund | Depositor | Facility | Create/cancel/commit/unlock/recover orders |
| Fund | Settler | Operations Bot | Settle fund state after external operations |
| PositionManager | Owner | Protocol Admin | Add modules, set LLTV, set fees |
| PositionManager | Minter | Facility | Deposit, withdraw, burn shares |
| PositionManager | Curator | Operations Bot | Set supply/withdrawal queues |
| PositionManager | Rebalancer | Rebalancer Contract | Execute rebalancing operations |
| BorrowPosition | Owner | PositionManager | All borrow/supply operations |
| Retargetter | Owner | Protocol Admin | Configuration, whitelists, position manager binding, force repay |
| Retargetter | Rebalancer | Operations Bot | Start, fund, order steps, rebalance, repay and resolve an operation |
| Retargetter | Consumer | Lender-facing surface | Consume signed offers, authorize minting |
| BorrowOffersRegistry | Owner | Protocol Admin | Grant offer roles, set per-collateral timelock and bonus floor |
| BorrowOffersRegistry | Proposer | Offer proposer | Post offers on any borrow position |
| BorrowOffersRegistry | Guardian | Emergency Admin | Revoke any offer on any borrow position |
| TransferGuard | Owner | Protocol Admin | Set token config, grant roles |
| TransferGuard | Pauser | Emergency Admin | Pause/unpause tokens |
| TransferGuard | Compliance | Compliance Bot | Set address blocklist/whitelist status |
Facility Roles
Section titled “Facility Roles”The Facility is the central hub and has the most granular role system.
The protocol administrator who configures intents and global settings.
// Create a new intentfacility.createIntent( depositAsset, targetAsset, depositCap, guardKey, resolveStart, quorum, transferableIntent);
// Update the target asset (only during DEPOSITING)facility.updateTarget(intentId, newTargetAsset);
// Set the intent descriptor for ERC-6909 metadatafacility.setDescriptor(descriptorAddress);Facilitator
Section titled “Facilitator”The operations role that drives the intent lifecycle forward. This is the most broadly permissioned role and is typically held by an automated operations bot.
// Intent lifecyclefacility.createIntent(...); // Also available to Ownerfacility.setDepositCap(id, newCap); // Adjust deposit capfacility.lock(id); // Close deposits, enter RESOLVINGfacility.resolve(id); // Enable claims, enter RESOLVED
// Bind external contracts to an intentfacility.setFund(id, fundAddress);facility.setRequest(id, requestAddress);
// Fund operations (delegated to IFund)facility.createFundOrder(id, mode, amount);facility.commitFundOrder(id);facility.unlockFundOrder(id, receiver);facility.recoverFundOrder(id, receiver);facility.cancelFundOrder(id);
// Request operationsfacility.pullFromRequest(id, amount, data);facility.repayRequest(id, amount);
// Position Manager operationsfacility.depositToPositionManager(id, pm, collateral, debt);facility.withdrawFromPositionManager(id, pm, collateral, debt);facility.burnPositionManagerShares(id, pm, shares);
// Guardian-gated swapfacility.swap(id, swapData, guardianSignatures);Guardian
Section titled “Guardian”Guardians are EOA signers whose EIP-712 signatures authorize privileged Facility actions — binding a Request to a Facility, binding a Fund to a Facility, and Position Manager swaps. These actions require guardian signatures when the configured quorum is nonzero, forming a multi-signature authorization scheme. Signatures can be assembled from a quorum out of band or produced automatically by the guardian coordinator.
// Guardian signs an EIP-712 typed data message off-chain// The swap function verifies the quorum is metfacility.swap( intentId, swapData, guardianSignatures // Must meet the intent's quorum requirement);Pauser
Section titled “Pauser”Emergency role that can halt all Facility operations.
// Pause the entire Facilityfacility.pause();
// Resume operationsfacility.unpause();Request Roles
Section titled “Request Roles”Manages the Request lifecycle and authorizes funding.
// Mark the bridge loan as repaid, enabling PT/YT redemptionsrequest.setRepaid();
// Authorize a funder to mint PT/YT directlyrequest.authorizeMinting(funder, principalAmount, yieldAmount);
// Consume a signed offer (also available to Consumer)request.consume(offer, signature, amount);Consumer
Section titled “Consumer”Can consume signed offers and authorize minting, enabling delegation of offer processing.
// Consume a bridge facilitator's signed offerrequest.consume(offer, signature, amount);
// Authorize direct mintingrequest.authorizeMinting(funder, principalAmount, yieldAmount);Puller
Section titled “Puller”The address authorized to draw down bridge loan capital. This is always the Facility contract and is set at deployment.
// Pull funds from the Request (called by Facility)request.pullFunds(amount, data);
// Repay funds to the Requestrequest.repay(amount);Fund Roles
Section titled “Fund Roles”Depositor
Section titled “Depositor”Drives the Fund’s order state machine. This is typically the Facility contract.
// Create a new orderfund.create(mode, amount); // mode: DEPOSIT or REDEEM
// Cancel an accepted orderfund.cancel();
// Commit assets to the orderfund.commit();
// Unlock resulting assets after processingfund.unlock(receiver);
// Recover assets if processing failedfund.recover(receiver);Settler
Section titled “Settler”Updates fund state after off-chain or async operations complete.
// Settle the fund state (e.g., after USCC minting completes)fund.settle(settlementData);Position Manager Roles
Section titled “Position Manager Roles”Configures the Position Manager’s modules, risk parameters, and fees.
// Add a borrow position as a modulepositionManager.addBorrowModule(borrowPositionAddress);
// Set the target LTV of the position managerpositionManager.setLtv(0.5e18); // 50%, a 2x leverage tier
// Configure fees in one call// `managementFee` is the base fee — see the Fees page for the namingpositionManager.setFeeData( feeRecipientAddress, 100, // 1% annual base fee (in basis points) 1000 // 10% performance fee (in basis points));
// Maximum allowed loss during rebalancing, and the cooldown between rebalancespositionManager.setRebalanceConfig(200, 0); // bps, seconds
// Set the transfer guard for compliancepositionManager.setTransferGuard(guardAddress);Minter
Section titled “Minter”Can deposit collateral, withdraw collateral, and burn shares. This is the Facility contract.
// Deposit collateral and optionally borrowint256 shares = positionManager.deposit(collateralAmount, debtAmount);
// Withdraw collateral by repaying debtint256 shares = positionManager.withdraw(collateralAmount, debtAmount);
// Burn shares for pro-rata exit(uint256 collateral, uint256 debt) = positionManager.burn(shareAmount);Curator
Section titled “Curator”Manages the ordering of positions for deposits and withdrawals.
// Set the supply queue (determines deposit routing order)PositionManager.SupplyQueueEntry[] memory queue = new PositionManager.SupplyQueueEntry[](2);queue[0] = PositionManager.SupplyQueueEntry({ position: positionA, maxBorrow: 2_000_000e6});queue[1] = PositionManager.SupplyQueueEntry({ position: positionB, maxBorrow: 5_000_000e6});positionManager.setSupplyQueue(queue);
// Set the withdrawal queue (determines withdrawal routing order)address[] memory withdrawQueue = new address[](2);withdrawQueue[0] = positionA;withdrawQueue[1] = positionB;positionManager.setWithdrawalQueue(withdrawQueue);Rebalancer
Section titled “Rebalancer”Executes rebalancing operations to redistribute collateral and debt across positions without affecting shares.
PositionManager.RebalancingData memory data = PositionManager.RebalancingData({ collateral: 0, debt: 0, operations: new PositionManager.RebalancingOperation[](2)});
// Withdraw from position Adata.operations[0] = PositionManager.RebalancingOperation({ position: positionA, operationType: PositionManager.RebalancingOperationType.WITHDRAW, amount: 500_000e18});
// Supply to position Bdata.operations[1] = PositionManager.RebalancingOperation({ position: positionB, operationType: PositionManager.RebalancingOperationType.SUPPLY, amount: 500_000e18});
positionManager.rebalance(data);Borrow Position Roles
Section titled “Borrow Position Roles”Owner (Position Manager)
Section titled “Owner (Position Manager)”The Position Manager is the sole owner of each borrow position and controls all lending operations.
// Supply collateralborrowPosition.supplyCollateral(amount);
// Withdraw collateral (enforces custom LLTV)borrowPosition.withdrawCollateral(amount);
// Borrow against collateral (enforces custom LLTV)borrowPosition.borrow(amount);
// Repay debtborrowPosition.repay(amount);Retargetter Roles
Section titled “Retargetter Roles”Each Retargetter instance is bound to one asset pair and runs one retargeting operation at a time against one owner-curated Position Manager.
Fully trusted. Holds every power below, plus the configuration surface, and bypasses the direction checks on rebalance.
// Sizing and repayment configuration (horizon, ticks, caps, residual tolerance)retargetter.setConfig(config);retargetter.setEstimates(estimates);
// Whitelists: only whitelisted funds and flash-loan modules can be usedretargetter.setFund(fundAddress, true);retargetter.setFlashLoanModule(moduleAddress, true);
// Rotate the bound position manager (same asset pair), or unbind with address(0).// Only possible while no operation is active.retargetter.setPositionManager(positionManagerAddress);
// Settle a Request outside the trustless formula, with explicit balance boundsretargetter.forceRepay(amount, minBalance, maxBalance);Rebalancer
Section titled “Rebalancer”Semi-trusted operator that drives operations. Boxed in by the principal cap, the direction checks, the value-conservation gate and the whitelists.
// Asynchronous: deploy a Request and run across the fund's settlement windowaddress request = retargetter.startRetargetting( principal, maxYieldBps, fund, requestName, requestSymbol);retargetter.pullRequestFunds(amount); // closes the funding roundretargetter.create(order);retargetter.commit();retargetter.unlock();retargetter.rebalance(rebalancingData);retargetter.repay();retargetter.resolve();
// Synchronous: the whole operation inside one flash loanretargetter.startSyncRetargetting(flashLoanModule, flashLoanAmount, fund, steps);This role is not the same as the Position Manager’s REBALANCER_ROLE (index 2). A Retargetter needs that one on the Position Manager to call rebalance there, and because the one-operation-at-a-time guarantee is per instance, the Position Manager’s rebalancer role should be held by at most one Retargetter.
Consumer
Section titled “Consumer”The lender-facing surface. Brings capital into an asynchronous operation.
// Consume a lender's signed EIP-712 offer against the operation's Requestuint256 ytAmount = retargetter.consume(offer, signature, ptAmount);
// Register (or revoke, with zero amounts) a mint authorization for a brokerretargetter.authorizeMinting(brokerAddress, ptAmount, ytAmount);Both are gated on the live principal cap and the operation’s yield cap, and both only work while the consumption window is open. The consumer role must never overlap with the Position Manager’s minters: the principal-cap read trusts them.
Borrow Offers Registry Roles
Section titled “Borrow Offers Registry Roles”The BorrowOffersRegistry is a single protocol-wide contract holding the offer roles and the per-collateral offer configuration for every Borrow Position. Roles are global: a proposer can post offers on any position.
Manages roles and configuration, and is always authorized by the registry’s own check functions, so an ownership handover never leaves the new owner without powers.
registry.setProposer(account, true);registry.setGuardian(account, true);
// Per-collateral configuration. The timelock change is itself timelocked.registry.setOfferTimelock(collateralToken, 1 days);registry.setMinOfferBonus(collateralToken, 150); // 1.5% floor, in basis pointsProposer
Section titled “Proposer”Posts offers on any borrow position, and may revoke its own.
uint8 id = borrowPosition.proposeOffer(collateralAmount, debtShares, expiresAt);Guardian
Section titled “Guardian”The veto inside an offer’s timelock window, and the kill switch for a standing offer that should no longer be consumable.
uint8[] memory ids = new uint8[](1);ids[0] = offerId;borrowPosition.revokeOffers(ids);See Offer Pre-Liquidation for the mechanism these roles operate.
Transfer Guard Roles
Section titled “Transfer Guard Roles”Configures token compliance modes and manages roles.
// Set token to whitelist mode (paused = false, whitelist = true)transferGuard.setTokenConfig(tokenAddress, false, true);
// Set token to blocklist mode (paused = false, whitelist = false)transferGuard.setTokenConfig(tokenAddress, false, false);
// Pause all transfers for a tokentransferGuard.setTokenConfig(tokenAddress, true, false);Pauser
Section titled “Pauser”Can pause and unpause specific tokens in an emergency.
// Pause a token (blocks all transfers)transferGuard.pause(tokenAddress);
// Unpause a tokentransferGuard.unpause(tokenAddress);Compliance
Section titled “Compliance”Manages address statuses for blocklist/whitelist enforcement.
// Block a specific addresstransferGuard.setAddressStatus(userAddress, AddressStatus.BLOCKLIST);
// Whitelist an addresstransferGuard.setAddressStatus(userAddress, AddressStatus.WHITELIST);
// Batch updateaddress[] memory accounts = new address[](3);accounts[0] = user1;accounts[1] = user2;accounts[2] = user3;transferGuard.setAddressStatusBatch(accounts, AddressStatus.WHITELIST);Function Access Control Reference
Section titled “Function Access Control Reference”Complete mapping of functions to their required roles and state preconditions.
Facility
Section titled “Facility”| Function | Required Role | Required State |
|---|---|---|
createIntent |
Owner or Facilitator | Any |
updateTarget |
Owner | DEPOSITING |
setDescriptor |
Owner | Any |
setDepositCap |
Facilitator | DEPOSITING |
lock |
Facilitator | DEPOSITING |
resolve |
Facilitator | RESOLVING (no active fund order, request repaid) |
setFund |
Facilitator | RESOLVING |
setRequest |
Facilitator | RESOLVING |
deposit |
Any | DEPOSITING |
withdraw |
Token Owner/Operator | DEPOSITING |
claim |
Token Owner/Operator | RESOLVED |
swap |
Facilitator + Guardian quorum | RESOLVING |
createFundOrder |
Facilitator | RESOLVING |
commitFundOrder |
Facilitator | RESOLVING |
unlockFundOrder |
Facilitator | RESOLVING |
recoverFundOrder |
Facilitator | RESOLVING |
cancelFundOrder |
Facilitator | RESOLVING |
pullFromRequest |
Facilitator | RESOLVING |
repayRequest |
Facilitator | RESOLVING |
depositToPositionManager |
Facilitator | RESOLVING |
withdrawFromPositionManager |
Facilitator | RESOLVING |
burnPositionManagerShares |
Facilitator | RESOLVING |
pause |
Pauser | Any |
unpause |
Pauser | Any |
Request
Section titled “Request”| Function | Required Role | Required State |
|---|---|---|
setRepaid |
Owner | Not yet repaid |
authorizeMinting |
Owner or Consumer | Before funding deadline |
consume |
Owner or Consumer | Before funding deadline |
pullFunds |
Puller | Funded |
repay |
Puller | Funds pulled |
mint |
Authorized funder | Before funding deadline |
redeem |
PT/YT holder | After repaid or repayment deadline |
Fund (IFund)
Section titled “Fund (IFund)”| Function | Required Role | Required State |
|---|---|---|
create |
Depositor | EMPTY |
cancel |
Depositor | ACCEPTED |
commit |
Depositor | ACCEPTED |
unlock |
Depositor | UNLOCKING |
recover |
Depositor | RECOVERING |
settle |
Settler | PROCESSING |
Position Manager
Section titled “Position Manager”| Function | Required Role | Required State |
|---|---|---|
deposit |
Minter | Not paused |
withdraw |
Minter | Not paused |
burn |
Minter | Not paused |
rebalance |
Rebalancer | Not paused |
addBorrowModule |
Owner | Any |
removeBorrowModule |
Owner | Any |
setSupplyQueue |
Curator | Any |
setWithdrawalQueue |
Curator | Any |
setLtv |
Owner | Any |
setFeeData (recipient, base fee, performance fee) |
Owner | Any |
resetPerformanceReference |
Owner | Any |
setRebalanceConfig (max loss, cooldown) |
Owner | Any |
setTransferGuard |
Owner | Any |
Borrow Position
Section titled “Borrow Position”| Function | Required Role | Required State |
|---|---|---|
supplyCollateral |
Owner | Initialized |
withdrawCollateral |
Owner | Healthy after withdrawal |
borrow |
Owner | Healthy after borrow |
repay |
Owner | Has outstanding debt |
preLiquidate |
Any | LTV above the safe LTV (offer band) or above the liquidation LTV (proportional) |
proposeOffer |
Registry Proposer or registry owner | Any |
revokeOffers |
Offer’s proposer, registry Guardian, or registry owner | Offers live |
Retargetter
Section titled “Retargetter”| Function | Required Role | Required State |
|---|---|---|
startRetargetting |
Owner or Rebalancer | Idle, position manager bound, fund whitelisted, within the principal cap |
startSyncRetargetting |
Owner or Rebalancer | Same, plus a whitelisted flash-loan module |
consume |
Owner, Rebalancer or Consumer | Active async operation, consumption window open |
authorizeMinting |
Owner, Rebalancer or Consumer | Same (revocation stays open once the window closes) |
pullRequestFunds |
Owner or Rebalancer | Active async operation |
create / commit / unlock / cancelOrder / recoverOrder |
Owner or Rebalancer | Active operation, order state permitting |
rebalance |
Owner or Rebalancer | Active operation; direction checks and value conservation |
repay |
Owner or Rebalancer | Active async operation, not already repaid |
forceRepay |
Owner | Active async operation |
resolve |
Owner or Rebalancer | Request repaid, no stored order, residual within tolerance |
setConfig / setEstimates / setFund / setFlashLoanModule |
Owner | Any |
setPositionManager |
Owner | No active operation |
Borrow Offers Registry
Section titled “Borrow Offers Registry”| Function | Required Role | Required State |
|---|---|---|
setProposer |
Owner | Any |
setGuardian |
Owner | Any |
setOfferTimelock |
Owner | Value within 15 minutes to 7 days; effective after the current timelock |
setMinOfferBonus |
Owner | At most 1000 bps; effective immediately |
Transfer Guard
Section titled “Transfer Guard”| Function | Required Role | Required State |
|---|---|---|
setTokenConfig |
Owner | Any |
pause |
Owner or Pauser | Any |
unpause |
Owner or Pauser | Any |
setAddressStatus |
Owner or Compliance | Any |
setAddressStatusBatch |
Owner or Compliance | Any |
Granting Roles
Section titled “Granting Roles”Roles are granted using Solady’s OwnableRoles pattern. Only the contract owner can grant and revoke roles:
// Grant the Facilitator roleuint256 FACILITATOR_ROLE = facility.FACILITATOR_ROLE();facility.grantRoles(facilitatorAddress, FACILITATOR_ROLE);
// Grant multiple roles at once (roles are bitmasks)uint256 roles = FACILITATOR_ROLE | PAUSER_ROLE;facility.grantRoles(operatorAddress, roles);
// Revoke a rolefacility.revokeRoles(operatorAddress, FACILITATOR_ROLE);
// Check if an address has a rolebool hasFacilitator = facility.hasAllRoles(operatorAddress, FACILITATOR_ROLE);An address can renounce its own roles:
// Renounce your own rolesfacility.renounceRoles(FACILITATOR_ROLE);