Skip to main content
In Railnet smart contracts, an Allocation Strategy is implemented as a MultiVehicle. See Glossary for all terminology.
This guide covers how to authorize yield sources, configure allocation queues, and run day-to-day operations for your Allocation Strategy — moving assets between sectors, dispatching to yield sources, rebalancing allocations, handling redemptions, and integrating with keepers for automation.

Prerequisites

  • A deployed Allocation Strategy ecosystem (see Create an Allocation Strategy)
  • Your External Access Control (EAC) contract address
  • At least one authorized yield source
  • The following roles on your EAC, scoped to the Sector Accounting Engine:
    • MULTI_VEHICLE_MOVE_ASSETS
    • MULTI_VEHICLE_DISPATCH
    • MULTI_VEHICLE_REBALANCE (for rebalancing operations)

Operating a platform-owned Allocation Strategy

If a Conduit owner deployed the Allocation Strategy and invited you to manage it, be aware of the guardrails:
  • Yield source authorization is controlled by the owner. You manage allocations within the set of sources they have authorized.
  • Fee structure is set by the owner. You earn through the configured performance fee share.
  • Admin access remains with the owner. You cannot grant roles to others or change the access control configuration.
Your operational roles are scoped to specific contracts — you can allocate, rebalance, and manage queues, but cannot change the strategic boundaries. See Guardrails for details.

Authorize yield sources

Before your Allocation Strategy can allocate assets to a yield source, you must authorize that source in the Vehicle Registry.
The Vehicle Registry validates that yield sources use the same base asset as the Allocation Strategy and implement the STEAM standard as SingleAsset vehicles.
Requires: MULTI_VEHICLE_SET_VEHICLE_AUTHORIZATION role scoped to the Sector Accounting Engine.
1

Find your Sector Accounting Engine address

Yield source authorization is performed on the Sector Accounting Engine. You can find its address from the MultiVehicle contract or via the Railnet subgraph.
ISectorAccountingEngine accounting = MultiVehicle(multiVehicle).accountingEngine();
IVehicleRegistry registry = accounting.vehicleRegistry();
2

Authorize a yield source

Call syncVehicleActivationStatus on the Sector Accounting Engine to authorize a yield source.
// Authorize an Aave V3 vehicle
address aaveVehicle = 0x...; // The sub-vehicle address
sectorAccountingEngine.syncVehicleActivationStatus(aaveVehicle, true);

// Authorize a Compound V3 vehicle
address compoundVehicle = 0x...;
sectorAccountingEngine.syncVehicleActivationStatus(compoundVehicle, true);
3

Unauthorize a yield source

To remove a yield source, first rebalance or redeem all assets from it, then unauthorize it.
// Unauthorize a vehicle (ensure no funds remain allocated)
sectorAccountingEngine.syncVehicleActivationStatus(vehicleToRemove, false);
Unauthorizing a yield source does not automatically redeem existing positions. Make sure to withdraw all funds from the source before removing it.

Configure allocation queues

The Queue Strategy Engine determines how assets are distributed across authorized yield sources using deposit and redeem queues.

Target semantics

In the deposit queue, target acts as a ceiling — the maximum shares to allocate to a yield source before moving to the next entry.
  • The queue processes in order: the first entry is filled first, up to its target, then the second entry, and so on.
  • type(uint256).max means no limit (allocate all available to this source).
  • The queue does not enforce ongoing ratios. If yields diverge across sources, allocations will drift.
Example: With deposit queue [{Aave, target: 80000e18}, {Compound, target: 20000e18}]:
  1. First 80,000 shares go to Aave
  2. Next 20,000 shares go to Compound
  3. Any additional shares overflow to subsequent entries

Set queues

Requires: MULTI_VEHICLE_SET_QUEUES role scoped to the Queue Strategy Engine.
IQueueStrategyEngine strategy = MultiVehicle(multiVehicle).accountingEngine().strategyEngine();

// Define deposit queue: Aave first (up to 80,000 shares), then Compound (unlimited)
IQueueStrategyEngine.QueueEntry[] memory depositQueue = new IQueueStrategyEngine.QueueEntry[](2);
depositQueue[0] = IQueueStrategyEngine.QueueEntry({
    vehicle: aaveVehicle,
    target: Target({ value: 80_000e18, mode: TargetMode.Absolute, threshold: 0 })
});
depositQueue[1] = IQueueStrategyEngine.QueueEntry({
    vehicle: compoundVehicle,
    target: TargetLib.unlimited()
});

// Define redeem queue: Compound first, then Aave
IQueueStrategyEngine.QueueEntry[] memory redeemQueue = new IQueueStrategyEngine.QueueEntry[](2);
redeemQueue[0] = IQueueStrategyEngine.QueueEntry({
    vehicle: compoundVehicle,
    target: TargetLib.zero()
});
redeemQueue[1] = IQueueStrategyEngine.QueueEntry({
    vehicle: aaveVehicle,
    target: TargetLib.zero()
});

strategy.setQueues(depositQueue, redeemQueue);
A common pattern is to set the redeem queue in reverse order of the deposit queue. This ensures that the last yield source to receive deposits is the first to be drained during redemptions.

How asset flow works

Assets in an Allocation Strategy flow through distinct sectors tracked by the Sector Accounting Engine:
  1. Deposit sector — idle liquidity waiting to be allocated
  2. Vehicle pending sector — assets earmarked for a specific yield source but not yet deposited
  3. Vehicle active sector — assets deposited and earning yield in a yield source
  4. Redeem sector — assets withdrawn from yield sources and awaiting user claims
The typical operator workflow is: move assets from idle to a yield source sector, then dispatch to execute the deposit into the yield source.

View holdings and allocations

Before operating, check the current state of your Allocation Strategy’s sectors and allocations.
ISectorAccountingEngine accounting = MultiVehicle(multiVehicle).accountingEngine();

// Check total assets across all sectors
uint256 totalAssets = MultiVehicle(multiVehicle).totalAssets();

// Check holdings for a specific yield source
(
    uint256 shares,
    uint256 assets,
    uint256 pendingDeposit,
    uint256 pendingRedeem,
    ,
    ,
) = accounting.vehicleHoldings(aaveVehicle);
You can also query the Railnet subgraph:
query SectorBalances($address: String!) {
  SectorBalance(
    where: {
      sector: {
        accountingEngine: {
          multiVehicle: { id: { _ilike: $address } }
        }
      }
    }
  ) {
    asset
    value
    sector {
      name
      sectorId
    }
  }
}

Move assets to a yield source

Use moveAssets to transfer the deposit asset (e.g. USDC) from the idle deposit sector to a yield source’s pending sector. This stages the assets for dispatch.
Moving assets does not deposit them into the yield source yet. You must call dispatch afterward to execute the deposit.
ISectorAccountingEngine accounting = MultiVehicle(multiVehicle).accountingEngine();

// Sector IDs
bytes32 depositSector = 0x6465706f73697400000000000000000000000000000000000000000000000000;
bytes32 vehicleSector = bytes32(uint256(uint160(targetVehicle)));

// Move 10,000 USDC from idle to vehicle pending sector
uint256 amount = 10_000e6; // USDC has 6 decimals
accounting.moveAssets(depositSector, vehicleSector, amount);

Dispatch assets to a yield source

After moving assets to a yield source’s pending sector, call dispatch to create a deposit query on the yield source. This executes the actual deposit.
ISectorAccountingEngine accounting = MultiVehicle(multiVehicle).accountingEngine();

// Dispatch pending assets to the target vehicle
address targetVehicle = 0x...; // The authorized sub-vehicle
accounting.dispatch(targetVehicle);
You can batch moveAssets and dispatch operations. Move assets to multiple yield source sectors first, then dispatch to each source in sequence.

Rebalance between yield sources

Rebalancing moves assets directly from one yield source to another in a single atomic transaction. The Sector Accounting Engine handles the redeem from the source and deposit into the destination.
ISectorAccountingEngine accounting = MultiVehicle(multiVehicle).accountingEngine();

address sourceVehicle = 0x...; // Vehicle to withdraw from
address targetVehicle = 0x...; // Vehicle to deposit into
uint256 shareAmount = 5_000e18; // Amount of shares to move

// Atomic rebalance: redeem from source, deposit into destination
accounting.rebalance(sourceVehicle, targetVehicle, shareAmount);
If the source yield source’s redemption is asynchronous, the assets are staged in the destination sector until the redemption settles. The deposit into the destination happens automatically once the assets arrive.

Handle user deposits

When users deposit into the Allocation Strategy, assets flow through the STEAM lifecycle. With allocation queues configured, deposits are automatically distributed to yield sources based on the deposit queue priority.
// User creates a deposit query
Query memory query = Query({
    owner: depositor,
    receiver: depositor,
    mode: Mode.DEPOSIT,
    input: [Asset({ asset: address(usdc), value: 500e6 })],
    output: [Asset({ asset: address(multiVehicle), value: 0 })],
    salt: keccak256("deposit-001"),
    data: ""
});

// STEAM lifecycle: create → unlock
multiVehicle.create(query);
multiVehicle.unlock(query);

Handle redemptions

Redemptions follow the STEAM lifecycle. The Queue Strategy Engine processes redeems according to the redeem queue priority.
// User creates a redeem query
Query memory query = Query({
    owner: depositor,
    receiver: depositor,
    mode: Mode.REDEEM,
    input: [Asset({ asset: address(multiVehicle), value: 1_000e18 })],
    output: [Asset({ asset: address(usdc), value: 0 })],
    salt: keccak256("redeem-001"),
    data: ""
});

multiVehicle.create(query);

// For async redemptions, the query progresses through the redeem queue
// Once assets are available:
multiVehicle.unlock(query);

Keeper integration

Keepers are off-chain bots that automate routine operations. As an operator, understanding keeper integration helps you decide what to automate vs manage manually.

What keepers automate

OperationDescription
Redemption queue feedingProvides liquidity to the redemption queue when there is pending demand
Asset retrievalRetrieves settled assets from the redemption queue back to accounting
Fee share redemptionProgresses redemption queries for fee shares
Sub-query progressionAdvances async sub-queries (e.g. Ethena withdrawals)

What remains manual

OperationWhy
Capital allocation (moveAssets)Requires strategic judgment
Dispatching to yield sourcesDepends on allocation strategy
RebalancingMarket-driven decision
Queue configuration changesStrategic decision

Check keeper status

If your Allocation Strategy is registered with the keeper system, verify automation is running:
query KeeperJobs($address: String!) {
  Job(where: { target: { _ilike: $address } }) {
    status
    lastExecution
    jobType
  }
}
If keepers are not processing redemptions, you can handle them manually (see Troubleshooting below). For full keeper setup details, see Set up keeper automation.

Update queue priorities

You can change the allocation strategy at any time by updating the deposit and redeem queues.
IQueueStrategyEngine strategy = MultiVehicle(multiVehicle).accountingEngine().strategyEngine();

// New deposit queue: prioritize Compound, then Aave
IQueueStrategyEngine.QueueEntry[] memory newDepositQueue = new IQueueStrategyEngine.QueueEntry[](2);
newDepositQueue[0] = IQueueStrategyEngine.QueueEntry({
    vehicle: compoundVehicle,
    target: TargetLib.unlimited()
});
newDepositQueue[1] = IQueueStrategyEngine.QueueEntry({
    vehicle: aaveVehicle,
    target: TargetLib.unlimited()
});

// New redeem queue: drain Aave first, then Compound
IQueueStrategyEngine.QueueEntry[] memory newRedeemQueue = new IQueueStrategyEngine.QueueEntry[](2);
newRedeemQueue[0] = IQueueStrategyEngine.QueueEntry({
    vehicle: aaveVehicle,
    target: TargetLib.zero()
});
newRedeemQueue[1] = IQueueStrategyEngine.QueueEntry({
    vehicle: compoundVehicle,
    target: TargetLib.zero()
});

strategy.setQueues(newDepositQueue, newRedeemQueue);

Troubleshooting

If redemptions are not progressing (e.g. due to keeper failure or insufficient liquidity):1. Feed the redeem queue — If withdrawable assets are available:
multiVehicle.feedQueryRedeemQueue();
2. Progress stuck sub-queries — If a sub-query is stuck in a non-terminal state:
ISubQueryEngine subQueryEngine = MultiVehicle(multiVehicle).accountingEngine().subQueryEngine();
subQueryEngine.progressQuery(subQuery, query);
3. Dispatch staged assets — If assets are in a yield source sector with no active query:
accounting.dispatch(targetVehicle);
If a deposit query is stuck in PROCESSING state, the yield source may require manual progression (common with async protocols like Ethena):
ISubQueryEngine subQueryEngine = MultiVehicle(multiVehicle).accountingEngine().subQueryEngine();
subQueryEngine.progressQuery(subQuery, query);
Check the yield source’s STEAM state to understand what transition is needed.
If dispatch reverts, common causes include:
  • No pending assets — Check the yield source’s pending sector has a non-zero balance
  • Source not authorized — Verify the yield source is still authorized in the Vehicle Registry
  • Missing role — Confirm you have MULTI_VEHICLE_DISPATCH scoped to the Sector Accounting Engine
  • Source rejection — The yield source’s create function may be reverting (check the underlying protocol’s status)
Remember that queues use ceiling (deposit) and floor (redeem) semantics:
  • Deposit queue targets are maximums, not ratios — allocations will drift with yield
  • If all yield sources have reached their ceiling, new deposits stay in the idle sector
  • Redeem queue targets are minimums — a source won’t be drained below its target
Update queue targets as your strategy evolves. See Configure allocation queues.

Next steps

Risk management

Evaluate yield sources, manage concentration risk, and handle emergencies.

Configure fees

Set up performance, management, and transactional fee structures.