Skip to main content
This page covers the smart contract implementation details. See Glossary.
This guide covers practical role setup for common scenarios. For the conceptual overview of External Access Control (EAC), role types, and scoping mechanics, see Access control and roles.

Persona-based role sets

Platform owner setup

The platform retains strategic control while delegating operations.
RoleScopePurpose
DEFAULT_ADMIN_ROLEGlobalGrant/revoke any role, admin transfers
MULTI_VEHICLE_SET_VEHICLE_AUTHORIZATIONSector Accounting EngineControl which yield sources the MV can use
FEE_MANAGER_SET_FEESFee ManagerControl fee rates
FEE_MANAGER_SET_FEE_RECIPIENTSFee ManagerControl revenue distribution
VEHICLE_SET_INTERCEPTIONSVehicle/Multi-VehicleControl reward interception rules

Asset manager setup

The AM receives operational roles scoped to the specific Multi-Vehicle they manage.
RoleScopePurpose
MULTI_VEHICLE_DISPATCHSector Accounting EngineSend assets to sub-vehicles
MULTI_VEHICLE_REBALANCESector Accounting EngineMove between vehicles
MULTI_VEHICLE_MOVE_ASSETSSector Accounting EngineMove assets between sectors
MULTI_VEHICLE_MOVE_SHARESSector Accounting EngineMove shares between sectors
MULTI_VEHICLE_SET_QUEUESQueue Strategy EngineConfigure allocation priorities
MULTI_VEHICLE_PROGRESS_QUERYSub Query EngineAdvance async queries
MULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUEMulti-VehicleFeed the redemption queue
MULTI_VEHICLE_RETRIEVE_QUERY_REDEEM_QUEUE_ASSETSMulti-VehicleRetrieve assets from redemption queue

Keeper/automation setup

Keepers automate routine operations like redemption queue processing.
RoleScopePurpose
JOB_LISTING_REGISTERJob ListingRegister automation jobs
JOB_LISTING_EXECUTEJob ListingExecute registered jobs
KEEPER_ON_REPORTKeeperSubmit execution reports
MULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUEMulti-VehicleAutomate redemption queue feeding
MULTI_VEHICLE_RETRIEVE_QUERY_REDEEM_QUEUE_ASSETSMulti-VehicleAutomate asset retrieval

Fee collector setup

For an operator or bot that handles fee collection and distribution.
RoleScopePurpose
FEE_MANAGER_DISPATCH_ERC20Fee ManagerDistribute collected fees to recipients
FEE_MANAGER_REDEEM_VEHICLE_SHARESFee ManagerRedeem fee shares to underlying asset

Role reference matrix

OperationRequired roleTypical holderScope
Deploy via factoryFACTORY_SPAWNPlatformFactory
Authorize vehiclesMULTI_VEHICLE_SET_VEHICLE_AUTHORIZATIONPlatformSector Accounting Engine
Move assets between sectorsMULTI_VEHICLE_MOVE_ASSETSAMSector Accounting Engine
Move shares between sectorsMULTI_VEHICLE_MOVE_SHARESAMSector Accounting Engine
Dispatch to sub-vehiclesMULTI_VEHICLE_DISPATCHAMSector Accounting Engine
Rebalance between vehiclesMULTI_VEHICLE_REBALANCEAMSector Accounting Engine
Deposit into accountingMULTI_VEHICLE_DEPOSITAMSector Accounting Engine
Set allocation queuesMULTI_VEHICLE_SET_QUEUESAMQueue Strategy Engine
Set operational thresholdsMULTI_VEHICLE_SET_THRESHOLDSPlatform / AMSector Accounting Engine
Progress sub-queriesMULTI_VEHICLE_PROGRESS_QUERYAM / KeeperSub Query Engine
Feed redemption queueMULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUEAM / KeeperMulti-Vehicle
Retrieve from redemption queueMULTI_VEHICLE_RETRIEVE_QUERY_REDEEM_QUEUE_ASSETSAM / KeeperMulti-Vehicle
STEAM operations (deposit/redeem)VEHICLE_STEAMUsers (public)Vehicle / Multi-Vehicle
Set reward interceptionsVEHICLE_SET_INTERCEPTIONSPlatformVehicle
Manage modulesMODULE_MANAGERPlatformModules Manager
Execute modulesEXECAM / KeeperModules Manager
Update feesFEE_MANAGER_SET_FEESPlatformFee Manager
Update fee recipientsFEE_MANAGER_SET_FEE_RECIPIENTSPlatformFee Manager
Distribute feesFEE_MANAGER_DISPATCH_ERC20AM / KeeperFee Manager
Redeem fee sharesFEE_MANAGER_REDEEM_VEHICLE_SHARESAM / KeeperFee Manager

Common workflows

Onboard a new asset manager

address am = 0x...; // New asset manager address
ISectorAccountingEngine accounting = MultiVehicle(multiVehicle).accountingEngine();
IQueueStrategyEngine strategy = accounting.strategyEngine();
ISubQueryEngine subQueryEngine = accounting.subQueryEngine();

// Core operational roles
eac.grantScopedRole(keccak256("MULTI_VEHICLE_DISPATCH"), address(accounting), am);
eac.grantScopedRole(keccak256("MULTI_VEHICLE_REBALANCE"), address(accounting), am);
eac.grantScopedRole(keccak256("MULTI_VEHICLE_MOVE_ASSETS"), address(accounting), am);
eac.grantScopedRole(keccak256("MULTI_VEHICLE_MOVE_SHARES"), address(accounting), am);
eac.grantScopedRole(keccak256("MULTI_VEHICLE_SET_QUEUES"), address(strategy), am);
eac.grantScopedRole(keccak256("MULTI_VEHICLE_PROGRESS_QUERY"), address(subQueryEngine), am);
eac.grantScopedRole(keccak256("MULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUE"), address(multiVehicle), am);
eac.grantScopedRole(keccak256("MULTI_VEHICLE_RETRIEVE_QUERY_REDEEM_QUEUE_ASSETS"), address(multiVehicle), am);

Offboard an asset manager

address am = 0x...; // Asset manager to remove

eac.revokeScopedRole(keccak256("MULTI_VEHICLE_DISPATCH"), address(accounting), am);
eac.revokeScopedRole(keccak256("MULTI_VEHICLE_REBALANCE"), address(accounting), am);
eac.revokeScopedRole(keccak256("MULTI_VEHICLE_MOVE_ASSETS"), address(accounting), am);
eac.revokeScopedRole(keccak256("MULTI_VEHICLE_MOVE_SHARES"), address(accounting), am);
eac.revokeScopedRole(keccak256("MULTI_VEHICLE_SET_QUEUES"), address(strategy), am);
eac.revokeScopedRole(keccak256("MULTI_VEHICLE_PROGRESS_QUERY"), address(subQueryEngine), am);
eac.revokeScopedRole(keccak256("MULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUE"), address(multiVehicle), am);
eac.revokeScopedRole(keccak256("MULTI_VEHICLE_RETRIEVE_QUERY_REDEEM_QUEUE_ASSETS"), address(multiVehicle), am);
Check for in-progress queries before offboarding. Pending async operations may require the AM’s roles to complete.

Make VEHICLE_STEAM public

Allow anyone to deposit and redeem on a Vehicle or Multi-Vehicle:
// Open deposits and redeems to all users
eac.setScopedRolePublic(keccak256("VEHICLE_STEAM"), address(multiVehicle), true);
Making VEHICLE_STEAM public is typical for vaults open to all users. This only controls who can create STEAM queries (deposits/redeems) — it does not affect operational roles.

Rotate DEFAULT_ADMIN_ROLE

Transfer admin control using the time-delayed mechanism:
1

Initiate transfer

eac.beginDefaultAdminTransfer(newAdmin);
2

Wait for the configured delay

The delay (set during EAC deployment) must pass before the transfer can complete.
3

New admin accepts

// Called by the new admin
eac.acceptDefaultAdminTransfer();

Decision guidance

Scoped vs global roles

Use scoped roles whenUse global roles when
You manage multiple MVs with different operatorsA single operator manages everything
You want to limit blast radius of compromised keysConvenience outweighs granularity
Different teams manage different vehiclesYou are the sole operator of a personal vault
Default recommendation: Always use scoped roles unless you have a specific reason not to.

Public roles

ScenarioMake public?Consideration
User-facing vault (open deposits)Yes — VEHICLE_STEAMRequired for any user to deposit/redeem
Internal strategy (restricted access)NoOnly whitelisted addresses can interact
Keeper automationNoGrant JOB_LISTING_EXECUTE to specific keeper addresses

Multisig for admin

Use a multisig wallet for DEFAULT_ADMIN_ROLE in production. A compromised admin key can grant itself any role and drain the vault.

Security checklist

  • Verify scope addresses before granting — wrong scope means the role won’t work as intended
  • Use multisig for DEFAULT_ADMIN_ROLE in production
  • Set non-zero initialDelay on the EAC to protect admin transfers
  • Regularly audit active roles via the subgraph or on-chain queries
  • Plan for offboarding — document which roles each operator holds
  • Never grant DEFAULT_ADMIN_ROLE to an asset manager
  • Keep vehicle authorization with the platform, not the AM

Troubleshooting

The most common cause is an incorrect scope. Each role must be scoped to the correct contract:
  • Most MV operational roles → scope to the Sector Accounting Engine (not the Multi-Vehicle itself)
  • Queue roles → scope to the Queue Strategy Engine
  • Fee roles → scope to the Fee Manager
  • STEAM roles → scope to the Vehicle or Multi-Vehicle
Use hasRoleOrScopedRole to verify the grant:
bool hasAccess = eac.hasRoleOrScopedRole(role, scope, account);
It depends on the operation:
  • VEHICLE_STEAM is scoped to the Vehicle or Multi-Vehicle contract
  • MULTI_VEHICLE_DISPATCH, MULTI_VEHICLE_MOVE_ASSETS, etc. are scoped to the Sector Accounting Engine
  • MULTI_VEHICLE_SET_QUEUES is scoped to the Queue Strategy Engine
Check the Role reference matrix for the correct scope for each role.
Query the EAC contract directly or use the Railnet subgraph:
// Check a specific role
bool hasRole = eac.hasRoleOrScopedRole(role, scope, account);
query AccountRoles($address: String!) {
  RoleGranted(where: { account: { _ilike: $address } }) {
    role
    scope
    sender
  }
}