Skip to main content

Front-end operators

If you're building or running a front end for RAI Dollar, this page is for you. It covers the on-chain pieces of the operator role: registration, kickback economics, and the integration surface for borrowing, redemption, and Stability Pool deposits.

What a front end is

A front end is just an Ethereum address (typically a contract or multisig) that has called registerFrontEnd on at least one branch's StabilityPool. There's no whitelist, no application, no admin approval, anyone can do it.

The protocol uses the address as a tag on Stability Pool deposits. Any depositor passing your address as _frontEndTag becomes a "tagged depositor" of yours, and a fraction of their FEE rewards flows to you instead of to them. The fraction is set by your kickback rate.

Registering

StabilityPool.registerFrontEnd(uint256 _kickbackRate)
  • _kickbackRate is in the range [0, 1e18], where 1e18 represents 1.0 (depositor keeps everything) and 0 means depositor keeps nothing.
  • Registration is permanent. You cannot change _kickbackRate after registering on a given Stability Pool.
  • Registration is per Stability Pool, i.e. per branch. To take deposits on the WETH pool and the wstETH pool, you must register on each.

The event FrontEndRegistered(_frontEnd, _kickbackRate) is emitted. Anyone can read it to verify the rate you advertise.

Picking a kickback rate

The kickback rate is a one-way decision. Real numbers people pick:

RateDepositor shareFront-end shareTypical positioning
1.0100%0%Pure UI, no fees taken
0.9999%1%Standard for thin-margin operators
0.9595%5%Premium UX or insurance offerings
0.5050%50%Boutique / managed-vault style
0.000%100%Custodial / treasury-funded front ends

You cannot register at > 1.0, the protocol enforces _kickbackRate ≤ 1e18.

You cannot reduce your kickback rate later. If you start at 0.99 and want to charge more later, you have to register a new address and migrate users (which requires them to withdraw and redeposit through the new tag).

What flows to you

Only FEE rewards are split via kickback. Specifically:

  • The depositor's RD principal, always theirs.
  • The depositor's collateral gain from liquidations, always theirs.
  • The depositor's FEE reward from the SP issuance curve, split per kickback.

You receive your share of the depositor's FEE accrual as it accrues. The math is integrated into the SP's reward indices; you claim by interacting with the SP from your front-end address.

Reading your performance

Useful getters (per branch, on each StabilityPool):

frontEnds(address)                    // (kickbackRate, registered, …)
frontEndStakes(address) // sum of tagged deposit balances
getDepositorFEEGain(address) // pending FEE for any address (incl. yours)

A front end can also subscribe to the FrontEndTagSet, FrontEndRegistered, and depositor-side events to build off-chain analytics.

Beyond Stability Pool

Front-end tagging is only for SP deposits. There is no front-end tag for:

  • Borrowing operations. Borrowers interact directly with BorrowerOperations.
  • Redemption. Redeemers interact directly with Aggregator.redeemCollateral.
  • FEE / FLX / LP staking. Each is a direct contract interaction.

You can still provide UX, hint construction, and gas estimation for those flows, there's just no on-chain revenue share built into them.

Integration: hints

Both openTrove / adjustTrove / closeTrove and Aggregator.redeemCollateral take sorted-list hints. As a front end, you typically pre-compute hints client-side using GlobalHintHelper:

// For borrower operations on branch i:
GlobalHintHelper.getRedemptionHints(i, RDAmount, price, maxIterations)
returns (firstHint, partialNICR, truncatedRDAmount)

GlobalHintHelper.getApproxHint(i, NICR, numTrials, inputRandomSeed)
returns (hintAddress, diff, latestRandomSeed)

The standard pattern:

  1. Read current branch state (price, accumulated rates, par).
  2. Call getApproxHint with reasonable numTrials (e.g. sqrt(numTroves) × 10).
  3. From the approx hint, refine with SortedTroves.findInsertPosition(NICR, hint, hint) to get the exact upper/lower.
  4. Pass those into the operation.

If your hints are stale by the time the tx lands, the operation still succeeds, it just costs more gas as the sorted list re-walks from the bad hint.

Integration: redemption

Aggregator.redeemCollateral takes arrays of hints, one entry per branch:

function redeemCollateral(
uint256 _RDamount,
address[] calldata _firstRedemptionHint, // length = N branches
address[] calldata _upperPartialRedemptionHint, // length = N branches
address[] calldata _lowerPartialRedemptionHint, // length = N branches
uint256[] calldata _partialRedemptionHintNICR, // length = N branches
uint256 _maxIterations
) external

For each branch i, compute hints as if you were redeeming _RDamount × weight[i] / sumWeights into that branch. weight[i] = Aggregator.unshieldedDebtEma(i).ema. Pass address(0) and 0 for shutdown branches; the Aggregator will skip them.

The redemption tx reverts entirely if any branch reverts. Front ends should sanity-check that each non-zero-weight branch's first-hint trove is actually redeemable before sending.

Integration: borrowing

// Open
BorrowerOperations.openTrove(coll, RDAmount, upperHint, lowerHint, redemptionShield)

// Adjust (combined op)
BorrowerOperations.adjustTrove(
collToAdd, collWithdraw,
debtChange, isDebtIncrease,
toggleShield, upperHint, lowerHint
)

// Wrappers
BorrowerOperations.addColl(amount, upperHint, lowerHint)
BorrowerOperations.withdrawColl(amount, upperHint, lowerHint)
BorrowerOperations.withdrawRD(amount, upperHint, lowerHint)
BorrowerOperations.repayRD(amount, upperHint, lowerHint)
BorrowerOperations.closeTrove()

For all hints, the relevant NICR for the user's post-operation trove is what matters. Compute it client-side; don't reuse pre-op hints.

Integration: Stability Pool

StabilityPool.provideToSP(uint256 _amount, address _frontEndTag)
StabilityPool.withdrawFromSP(uint256 _amount)
BorrowerOperations.moveCollateralGainToTrove(_user, _coll, _upperHint, _lowerHint)

For tagged deposits, _frontEndTag is your registered address. It's recorded on first deposit; subsequent provideToSP calls inherit the same tag. To switch tags, the user must fully withdrawFromSP and re-deposit.

Practical advice

  • Surface the live numbers. Par, market price, branch TCR, your trove's ICR, your effective APR, redemption quota. These are readable from Aggregator, RateParControl, MarketOracle, and each branch's TroveManager / PriceFeed / InterestEngine.
  • Compute hints client-side and verify before sending. Cheaper for users.
  • Don't show "the redemption fee is X%"; show the realized output. The fee depends on the branch the redemption hits; users care about RD-in vs collateral-out.
  • Surface bootstrap and shutdown state clearly. During bootstrap, redemption is disabled. During branch shutdown, the discount schedule applies. Both are easy to mis-render.
  • Handle the gas-comp reserve in the open-trove UI. Users see "Borrow 1800 RD" and expect to receive 1800 RD; the 200 RD reserve is held by the protocol, not transferred to them.

Deep dive