Incentivization
An LP’s claim on a leg is balance × liquidityIndexWad, so every on-chain way of paying BTR liquidity reduces to raising that index. Four levers do it; nothing else on chain does. Points, third-party reward campaigns and venue reward tokens are off-chain distribution the protocol carries no accounting for (§6). Every lever is per pool and per chain. “AC owner” below means the pool’s own seat (Pool.poolAdmin()): the protocol’s AccessControl.owner() on a protocol pool, the deployer’s seat on a white-label one. LP mechanics: Providing Liquidity.
1. The levers
| Lever | Call | Who | Effect |
|---|---|---|---|
| Fee split | requestOp(UPDATE_FEES) → executeUpdateFees | AC owner, TUNING tier | Lower protoSharePct → larger LP slice of every swap and flash spread |
| Direct subsidy | Pool.donate(token, amount) | Anyone; deposit-lane holders only while DEPOSIT_GATED_BIT is set | Raises one leg’s index, one-shot |
| POL | Pool.deposit(token, amount) | Anyone | Adds depth; earns fees, pays no one else |
| Hook yield | YieldHook.rebalance() → Pool.hookCreditYield | Keeper or owner | Venue yield on the invested slice lands on the index |
2. Fee split
protoSharePct is the percentage of the swap and flash spread routed to the pool’s fee sink; the remainder raises the LP index. Set in initdata at createPool, changed afterwards through the queue:
IPool.FeeParams memory p =
IPool.FeeParams({protoSharePct: 0, flashFeePbps: 100, flowCooldownSecs: IPool(pool).flowCooldownSecs()});
admin.requestOp(pool, uint8(IPool.OpType.UPDATE_FEES), bytes32(0), abi.encode(p));
// … TUNING tier delay, 1 hour under PROD_DELAYS …
admin.executeUpdateFees(pool); // emits FeeParamsUpdatedprotoSharePct = 0 is a valid, permanent “all fees to LPs” launch stance and costs nothing on the hot path: the fee split returns the whole fee as lpFee. No token, no distributor, no claim.
Range is [0, 100]; anything above reverts InvalidInput. UPDATE_FEES is pool-wide, so its subject is bytes32(0). It is also the only way to lower flowCooldownSecs; Admin.setFlowCooldown only raises it. Receiving side: Protocol Fee Collection.
3. Donations
function donate(address token, uint256 amount) external payable;Pulls amount, adds it to both reserves and liabilities, and raises that leg’s index. Emits Donated(sender, token, amount). Coverage is unchanged: a gift to existing holders of the leg, not new depth.
Two hard rules:
- A donation into an unopened leg is permanently unclaimable. With
liabilities == 0there is no share to raise; the surplus above the dead seed backs no claim.depositfirst, thendonate. - It is instantaneous and un-vested. A donation lands on whoever holds the leg’s
LPTokenin that block, including someone who deposited one block earlier. Only the flow cooldown stands between a donation and a JIT deposit. Size and time donations accordingly, or pay through a hook instead.
4. Protocol-owned liquidity
POL is a plain deposit. It earns the LP slice like any other position and deepens the book, tightening quoted spread for everyone: that is its incentive value. It pays no one else directly.
LPToken is a standard ERC-20 whose minted quantity is frozen for Pool.flowCooldownSecs() (default 15 s, maximum 300). Transfers of unfrozen balance are unrestricted, which makes the receipt usable as a campaign measurement basis (§6). Mechanics: Flow Guards.
5. Hook yield
A YieldHook is the only lever here that pays LPs continuously. Full mechanics, buffer sizing and the dual ledger: Pool Hooks. Incentive-relevant facts:
sync()andrebalance()read venue NAV and book the gain viaPool.hookCreditYield, raising the index for every holder of that leg.- The credit is capped as a rate, not a per-call allowance: at most
maxHarvestCreditBpsof book per day, default 100 BPS/day, hard ceiling 500. A campaign that dumps a large one-shot gain into the venue is credited over days, not in one block.0disables crediting entirely. Details: Hooks §5.2. - Yield only accrues on what is invested, and the owner caps that: a hook starts at
maxInvested0, the position is held to 5% of the venue’s live cash, and no invest lands above 95% venue utilization (Hooks §5.4). On a thin venue the credit is small whatever the rate. - Venue reward tokens do not reach LPs. Two claims pull them onto the hook:
claimMerklfor Merkl campaigns (the hook is the claimant, the distributor is a fixed address) andclaimVenueIncentivesfor the venue’s own program (Aave, Venus).sweepIncentivesalways pushes them topool.treasury(). Adapters never swap rewards. Routing that value back to LPs is a treasury decision, executed as adonate(§3).
Installing a hook is UPDATE_HOOK at the LISTING tier, 1 day.
6. Third-party campaigns
The protocol exposes no reward-distribution surface (no gauge, no notifyRewardAmount, no staking wrapper, no referral fee-share), so a campaign operator paying BTR LPs runs it entirely outside the protocol:
- Measure: snapshot
LPToken.balanceOfper holder for the leg. One EIP-1167 clone per(pool, asset), address fromPool.lpToken(token), ABI asLP_TOKEN_ABIin@btr-protocol/sdk/abis. Balances are already index-adjusted claims, so no unit conversion is needed. Snapshot each chain separately. - Distribute: the operator’s own Merkl campaign or distributor. BTR ships neither and takes no custody.
The one in-protocol alternative is to convert the campaign budget into a donate on the incentivized leg. Trustless, no claim UI, but untargeted: it pays every current holder pro rata, including passive ones.
7. End to end
Launch stance for an incentivized leg: all fees to LPs, a hook for baseline yield, and a periodic treasury donation funded by swept venue rewards.
import { encodeAbiParameters } from 'viem';
import { Contract } from '@btr-protocol/sdk/eth';
import { ADMIN_ABI, POOL_ABI } from '@btr-protocol/sdk/abis';
const admin = new Contract({ address: adminAddress, abi: ADMIN_ABI, provider, account: owner });
const pool = new Contract({ address: poolAddress, abi: POOL_ABI, provider, account: treasury });
// OpType.UPDATE_FEES = 7 in IPool.OpType. ZERO32 = 32 zero bytes: UPDATE_FEES is
// pool-wide, so its subject is ignored.
const UPDATE_FEES = 7;
const ZERO32 = `0x${'00'.repeat(32)}`;
const feeParams = encodeAbiParameters(
[{ type: 'tuple', components: [{ type: 'uint8' }, { type: 'uint16' }, { type: 'uint16' }] }],
[[0, 100, 15]], // protoSharePct = 0, flashFeePbps = 100, flowCooldownSecs = 15 (unchanged)
);
// 1. All swap fees to LPs (TUNING tier, 1 hour).
await admin.write('requestOp', [poolAddress, UPDATE_FEES, ZERO32, feeParams]);
// … after the delay …
await admin.write('executeUpdateFees', [poolAddress]);
// 2. Seed the leg BEFORE anything is donated into it (§3, rule 1).
await pool.write('deposit', [token, seedAmount]);
// 3. Install the hook: UPDATE_HOOK, LISTING tier, 1 day, then raise its maxInvested
// (requestCurve, queued). Thereafter the keeper's rebalance() credits venue yield
// to the index automatically.
// 4. Recycle swept venue rewards from the treasury as a donation.
await pool.write('donate', [token, sweptBudget]);Order matters: step 2 before any donate, and the hook’s HOOK_PRE_OUTFLOW flag must be set before capital can be deployed at all.
8. Boundaries
| Action | Authority | Timing |
|---|---|---|
donate | anyone (deposit-lane holders while DEPOSIT_GATED_BIT is set) | immediate |
deposit (POL) | anyone | immediate; flowCooldownSecs transfer lock |
UPDATE_FEES | AC owner | TUNING, 1 hour |
UPDATE_HOOK | AC owner | LISTING, 1 day |
rebalance, claimMerkl, claimVenueIncentives, sweepIncentives | keeper or owner | immediate |
| treasury rotation (the reward sink) | AC owner | GOVERNANCE, 7 days |
claimTreasury | pool.treasury(), minting to itself | immediate |
No incentive lever can move reserves out of a pool. donate and deposit only add, hookCreditYield only raises the index, and the sweep moves reward tokens that were never pool reserves.
9. Related
- Pool Hooks · Protocol Fee Collection
- Pool Deployment & Curation:
FeeParamsatcreatePool, timelock tiers - Spread & Fees · Basic Operations