Skip to content
3F Docs

Retargetter

A Retargetter is a small orchestrator that brings a Position Manager back to its target LTV. A leveraged position drifts away from its target as interest accrues on the debt, as the collateral yields, and as deposits and withdrawals land at an LTV that is not exactly the target. Restoring the target means changing collateral and debt together, and on an asynchronous asset the collateral leg does not settle in the same transaction as the debt leg.

The Retargetter is what bridges that gap. It holds a short-term loan for exactly as long as the collateral leg takes to settle, folds the result into the position, and repays the loan.

Each instance is deployed behind a beacon proxy and bound to one (collateralAsset, debtAsset) pair at initialization. It runs one operation at a time against one owner-curated Position Manager on that pair.

The Retargetter exposes two ways to run an operation, one per settlement model of the fund on the other side.

Entry point Bridge capital Duration Fund requirement
startSyncRetargetting A flash loan through an IFlashLoanModule One transaction The fund must settle synchronously
startRetargetting A bridge loan on a freshly deployed Request The fund’s settlement window (days) Any fund

Both are described step by step, in both directions, on the flow pages:

The Retargetter does not implement leverage mechanics of its own. It calls into contracts that already exist, and its value is the guardrails it applies around those calls:

  • Request (async only), deployed fresh per operation through the audited RequestFactory. The Retargetter becomes its owner, puller and consumer. Lenders fund the Request against signed offers and receive PT and YT tokens.
  • Fund, one of the owner-whitelisted Fund wrappers on the bound pair. The Retargetter creates, commits, unlocks, cancels and recovers a single order at a time.
  • Position Manager, through rebalance, using the rebalancer role. This is where collateral and debt actually move.
  • Flash-loan module (sync only), an owner-whitelisted adapter over a flash-loan provider.

The Retargetter holds no value at rest. Both the end of a sync window and resolve on an async operation require its balances of the two bound assets to be within a configured residual tolerance, exact zero by default.

An operation never declares whether it is increasing or decreasing leverage. The direction is read from live state: the quoter compares debt / collateralQuoted against the Position Manager’s target and routes accordingly. Below target the position needs more collateral (subscribe into the fund), above target it needs less debt (redeem out of the fund).

flowchart TD
    S["Current LTV vs target"] -->|"below target"| U["LTV up: borrow, subscribe,<br/>supply the settled shares"]
    S -->|"above target"| D["LTV down: repay and free collateral,<br/>redeem it for the debt asset"]
    S -->|"at target"| Z["Principal cap is zero,<br/>no operation can start"]

The owner is fully trusted. The rebalancer role is semi-trusted: it drives operations but is boxed in by checks it cannot move.

maxPrincipal() is recomputed from live Position Manager state on every start and on every capital commitment. It is the quoter’s sizing formula, plus a configured buffer of at most 20%, and below target it is further bounded by the one-trip repayment bound: the largest principal whose worst permitted repayment can still be borrowed back at target once the subscription has landed as collateral, in a single settlement trip.

Because the cap is a live read rather than a stored number, it self-corrects. Once a rebalance moves the position to target, the cap collapses toward zero and further capital entry is blocked.

After every rebalance call driven by the rebalancer (the owner bypasses this), the Retargetter compares the post-call state against a snapshot taken before it:

  • The aggregate LTV may end above target only if it strictly improved on its snapshot.
  • A single borrow module may end above target only if it did not worsen, so a module the operation never touched cannot block the other legs.
  • No module may end with debt against zero collateral.

While the operation’s bridge loan is outstanding (the Request exists and has not been marked repaid), the position’s net value, quoted collateral minus debt across every module, must not grow across a rebalance call. This holds for every caller including the owner. Bridge capital may enter the position only against equivalent value flowing back out in the same call, which is what stops a bridge loan from being quietly converted into position equity.

The deadline does not disarm this gate. A Request that auto-expires past its repayment deadline still counts as outstanding, so pulled principal cannot be folded into the position; the owner delivers it late through forceRepay instead.

Every operation carries an effective yield cap, min(config cap, caller cap), snapshotted at start. It bounds the ratio of yield tokens to principal tokens a lender can be granted, at most 50% by configuration. A later setConfig does not reprice an operation that already started.

Funds and flash-loan modules are owner-whitelisted. Whitelisting a fund checks that it is a contract and that its tokens match the bound pair. The Position Manager is not a call argument at all: it is owner-curated on the instance, so every start gate and principal-cap read trusts a curated address rather than a caller-supplied one.

An async operation’s bridge loan is priced in ticks, not per second.

The loan clock starts at the first capital commitment (the first consume, or the first nonzero mint authorization), not at operation start. From the first second the borrower owes one full tick. The count promotes to the next tick each time the elapsed time passes a whole number of ticks plus a grace threshold.

owed = ptSupply + ceil(ytSupply * paidDuration / horizon)

where paidDuration is the elapsed time quantized to whole ticks and capped at the horizon. ptSupply + ytSupply is therefore an absolute maximum: a delayed repayment can never draw more than the approved yield. The yield term rounds up, so lenders are never underpaid by a wei.

The horizon is the annualization basis, between 90 and 366 days. The tick duration is the repayment granularity, at most 30 days. Both are snapshotted at operation start.

Capital can only enter an operation while the consumption window is open, and the window is deliberately narrow:

  • It opens at the loan clock origin and closes one tick threshold later.
  • It closes early and permanently at the first pullRequestFunds call, which also revokes every pending mint authorization.
  • The clock can only start while at least 80 days remain before the Request’s 90-day repayment deadline.

The reason is that repayment prices every yield token from the origin. Capital arriving later would be paid for time it never covered and, on a default, would dilute the recovery of the lenders who were there first.

Set by the owner through setConfig or setEstimates:

Field Meaning Bounds
horizon Yield annualization basis 90 to 366 days
tickDuration Repayment granularity 1 second to 30 days
tickThreshold Grace before the next tick, and the consumption window length Strictly below tickDuration
maxYieldBps Ceiling on per-operation yield caps At most 5000 (50%)
principalBufferBps Headroom over the computed principal cap At most 2000 (20%)
collateralResidualExponent / debtResidualExponent Settlement dust tolerance per asset: balances strictly below 2^exponent pass the residual gate Zero keeps the gate exact
estimates Expected request yield rate, venue borrow rate, collateral yield rate, subscription duration and redemption duration Feed the sizing formulas

The estimates only size operations. They never price a repayment: repayment is computed from the actual PT and YT supplies on the Request.

The sync path borrows through an owner-whitelisted module implementing IFlashLoanModule:

function flashLoan(address token, uint256 amount, bytes calldata data) external;

The module transfers the funds to the caller, invokes IFlashLoanReceiver.onFlashLoan(amount, data) on it, then pulls the repayment back through an allowance. Providers expose incompatible ABIs, so each module adapts exactly one provider behind this interface.

MorphoFlashLoanAdapter is the first implementation, over Morpho Blue’s zero-fee flash loans. It is stateless and permissionless infrastructure: anyone may call it, and all borrower-side security lives with the borrower. Its address is listed on the Deployments page.

A whitelisted module is still not trusted with the operation. Inside the callback the Retargetter checks that the incoming payload hashes to the digest committed at window open, that the amount matches, and that the debt-asset balance actually grew by the full nominal amount before any step runs. The module holds step authority only while the committed payload executes, never in its own frame.

Role Bit Holder Powers
Owner n/a Facility and Funds Owner multisig Everything below, plus configuration, whitelists, position manager binding, forceRepay, and a bypass of the direction checks
REBALANCER_ROLE 1 << 0 Operations bot Start, pull, order steps, rebalance, repay, resolve
CONSUMER_ROLE 1 << 1 Lender-facing surface consume and authorizeMinting

The REBALANCER_ROLE on a Retargetter is distinct from the Position Manager’s own rebalancer role. The one-operation-at-a-time guarantee is scoped to a single instance, so a Position Manager’s rebalancer role should be granted to at most one Retargetter: two holders could each admit a full principal cap independently. See Roles for the full surface.

View Use
maxPrincipal() The live principal cap, direction auto-detected
operation() The in-flight operation: addresses, clock origin, snapshotted terms, stored order
isActive() Whether an operation is running (a sync window registers here for its transaction)
bridgeOutstanding() Whether the value-conservation gate is armed
owed() The current amount owed on the operation’s Request
authorizedAccounts() Accounts holding a registered mint authorization, at most 16
assets() The bound collateral and debt asset pair

Per-asset Retargetter addresses are listed on each integration’s page under Deployments.