Skip to content
3F Docs

Asynchronous Retargeting

Asynchronous retargeting is the path for funds that settle in an external window: a subscription or redemption that takes days, not one transaction. The Retargetter borrows the bridge capital from lenders through a freshly deployed Request contract, holds it for the duration of the settlement, folds the result into the position and repays the loan on tick-priced terms.

It is the same shape as the synchronous path with the flash loan replaced by a real loan, which is why the operation gains a clock, a lender-facing surface and a settlement gate.

flowchart TD
    A["startRetargetting(principal, maxYieldBps, fund, name, symbol)"] --> B["consume / authorizeMinting<br/>(lenders commit capital)"]
    B --> C["pullRequestFunds<br/>(closes the funding round)"]
    C --> D["create / commit<br/>(fund order)"]
    D --> E["External settlement window"]
    E --> F["unlock<br/>(settled output)"]
    F --> G["rebalance<br/>(fold into the position)"]
    G --> H["repay<br/>(trustless, tick-priced)"]
    H --> I["resolve<br/>(clear state)"]

startRetargetting deploys a Request through the audited RequestFactory and makes the Retargetter its owner, puller and consumer. The Request gets a 90-day repayment deadline. The operation’s repayment terms (horizon, tick duration, tick threshold) and its effective yield cap are snapshotted at this call, so a later setConfig never reprices an operation that is already running.

Only one operation runs at a time per Retargetter instance.

Lenders enter in one of two ways, both gated on the CONSUMER_ROLE:

  • consume(offer, signature, ptAmount) takes a lender’s signed EIP-712 offer and mints them PT (principal) and YT (yield) tokens on the Request.
  • authorizeMinting(to, ptAmount, ytAmount) registers an authorization that an account, typically a broker, mints against on the Request directly. A nonzero authorization is a firm commitment: it counts toward the live principal cap until it is minted or revoked, and at most 16 accounts can hold one at a time.

Both start the loan clock on first use, and both are checked against the live principal cap after the Request call, once the minted amount is in the PT supply. A maker callback that moves the cap mid-call therefore cannot leave the operation committed above it.

The window then closes quickly: capital entry stays open only through the tick threshold past the clock origin, and pullRequestFunds shuts it permanently while revoking every pending authorization. Repayment prices every yield token from the origin, so late capital would be paid for time it never covered, and on a default would dilute the lenders who were there first.

Revocation stays available at any time, window open or closed, because an unminted authorization can still be minted on the Request directly. A revocation that leaves the operation with no PT, no YT and no pending authorization rewinds the clock to unstarted and reopens the window.

The position is below target. The bridge principal is subscribed into the fund, and when the subscription settles the shares are supplied as collateral and the repayment is borrowed against them.

sequenceDiagram
    participant L as Lenders
    participant RT as Retargetter
    participant RQ as Request
    participant F as Fund
    participant PM as Position Manager

    L->>RQ: 1. Fund the Request (consume)
    RT->>RQ: 2. pullRequestFunds
    RT->>F: 3. create + commit (DEPOSIT)
    Note over F: external settlement
    RT->>F: 4. unlock
    F-->>RT: 5. Shares (collateral)
    RT->>PM: 6. rebalance: SUPPLY all, BORROW owed
    RT->>RQ: 7. repay
    RT->>RT: 8. resolve

The tail is one transaction: supply everything the unlock returned, borrow exactly the amount owed, repay, resolve.

rebalance(RebalancingData{
collateral: FULL_BALANCE_SENTINEL,
debt: 0,
operations: [
{ SUPPLY, FULL_BALANCE_SENTINEL },
{ BORROW, owed } // principal plus the accrued ticks
]
});
repay();
resolve();

Worked example. A position holds 10,000 of quoted collateral against 5,000 of debt, an LTV of 50% against a 70% target. The operation starts with a principal of 6,000 and a 1% yield cap. A lender’s offer for 6,000 principal against 60 of expected return is consumed in full, the principal is pulled and subscribed, and the subscription settles two days later returning 6,000 of shares.

With a one-day tick and a ten-hour threshold, two days have accrued two ticks, so:

owed = 6,000 + ceil(60 * 2 days / 365 days) = 6,000.33 (approx)

The Retargetter supplies the 6,000 of shares, borrows the owed amount, repays the Request, and resolves. The lender then burns their PT and YT against the Request and receives the full owed amount: the principal one to one, and the yield tokens redeeming the excess.

Unlike the sync path, the borrow leg here is sized on owed(), not on the principal, because the bridge loan carries yield that also has to come back out of the position. This is exactly what the one-trip repayment bound sizes the principal cap for.

The position is above target. The bridge principal repays debt first, which frees collateral; the freed collateral is redeemed through the fund, and the proceeds repay the Request. Because the freed collateral is deliberately sized slightly above the principal to cover the yield, the settlement usually leaves a surplus.

sequenceDiagram
    participant RT as Retargetter
    participant RQ as Request
    participant PM as Position Manager
    participant F as Fund

    RT->>RQ: 1. pullRequestFunds (principal)
    RT->>PM: 2. rebalance: REPAY principal, WITHDRAW a bit more
    RT->>F: 3. create + commit (REDEEM)
    Note over F: external settlement
    RT->>F: 4. unlock
    F-->>RT: 5. Proceeds (debt asset)
    RT->>RQ: 6. repay
    RT->>PM: 7. rebalance: REPAY the surplus
    RT->>RT: 8. resolve

Worked example. The same 10,000 over 5,000 position with the target moved down to 30%. With zero rate estimates the quoter reduces to (D - target * K) / (1 - target), so it sizes the principal at (5,000 - 0.3 * 10,000) / 0.7 = 2,857, plus the configured buffer. Nonzero estimates add the accrual terms for the venue borrow rate, the collateral yield and the bridge yield over the expected window. An operation starts at 2,857 with a 1% yield cap, a lender funds it against 28 of expected return, and the principal is pulled.

A single rebalance repays 2,857 of debt and withdraws 2,900 of collateral, the extra 43 being the yield coverage. That 2,900 is redeemed through the fund and settles three days later for 2,900 of the debt asset. Three ticks have accrued:

owed = 2,857 + ceil(28 * 3 days / 365 days) = 2,857.23 (approx)

The Request is repaid that amount, and the surplus of roughly 43 is folded straight back into the position as an extra debt repayment. Below target, folding value in is always allowed. The position ends at or below the new 30% target despite three days of interest accrual on the remaining debt.

The withdraw leg extracting more than the repay leg is a loss at the Position Manager’s own accounting, so this direction needs the Position Manager’s maxRebalanceLoss tolerance to be wide enough to admit it.

The bridge repayment is fixed when the operation is sized, while the redemption settles at the realized price. The quoter’s remediationDelta prices that mismatch against the collateral yield the sizing already assumed, so a price that moves exactly as forecast gives a zero delta:

  • Positive delta (surplus): fold it into position debt, as above.
  • Negative delta (shortfall): the proceeds do not cover the Request. This needs an owner-only borrow top-up, because value conservation blocks the rebalancer from drawing it out of the position while the bridge is outstanding.

repay() is trustless and permissionless within the role: it transfers only the shortfall between the owed amount and whatever the Request already holds, and the owed yield floors at one full tick. An operation whose consumed principal was never pulled still tops up that one tick from position-derived funds.

resolve() then clears the operation, and it only passes when all three hold:

  1. The Request has been marked repaid.
  2. No fund order is stored.
  3. The Retargetter’s balances of both bound assets are within the configured residual tolerance.

Until the Request is marked repaid through repay or forceRepay, the value-conservation gate is armed: the position’s net value must not grow across any rebalance, for every caller including the owner. Bridge capital may enter the position only against equivalent value leaving in the same call.

Situation Handling
The fund order fails or the venue rejects it cancelOrder before commitment, or recoverOrder to take the input back once the fund reports it ended
The redemption settles short (negative drift) Owner tops up through a borrow, then repay
The repayment needs to sit outside the trustless formula Owner calls forceRepay(amount, minBalance, maxBalance), with explicit balance bounds
The Request passes its 90-day deadline It auto-expires: holders redeem what sits in it, and the owner delivers late through forceRepay. Expiry never disarms the value-conservation gate
Nothing was ever consumed repay() owes zero, then resolve() clears the operation

A Request that auto-expires prices redemptions on its live balance, so a holder who burns PT and YT before a late delivery lands crystallizes the shortfall and is not made whole by it. Holders expecting a late delivery should wait for it.

For the one-transaction path, see Synchronous Retargeting.