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)
_kickbackRateis in the range[0, 1e18], where1e18represents1.0(depositor keeps everything) and0means depositor keeps nothing.- Registration is permanent. You cannot change
_kickbackRateafter 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:
| Rate | Depositor share | Front-end share | Typical positioning |
|---|---|---|---|
1.0 | 100% | 0% | Pure UI, no fees taken |
0.99 | 99% | 1% | Standard for thin-margin operators |
0.95 | 95% | 5% | Premium UX or insurance offerings |
0.50 | 50% | 50% | Boutique / managed-vault style |
0.00 | 0% | 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:
- Read current branch state (price, accumulated rates, par).
- Call
getApproxHintwith reasonablenumTrials(e.g.sqrt(numTroves) × 10). - From the approx hint, refine with
SortedTroves.findInsertPosition(NICR, hint, hint)to get the exact upper/lower. - 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'sTroveManager/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
- Architecture: full contract topology.
- Redemption mechanism: basket-weight construction.