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, and nothing else on chain does. Everything beyond them (points, third-party reward campaigns, venue reward tokens) is off-chain distribution the protocol carries no accounting for (§6).
Every lever is per pool and per chain.
1. The levers
| Lever | Call | Who | Effect |
|---|---|---|---|
| Fee split | requestOp(UPDATE_FEES) → executeUpdateFeeParams | AC owner, LOW tier | Lower protoSharePct → larger LP slice of every swap and flash spread |
| Direct subsidy | Pool.donate(token, amount) | Anyone | 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: 5});
admin.requestOp(pool, uint8(IPool.OpType.UPDATE_FEES), bytes32(0), abi.encode(p));
// … LOW tier delay, 1 hour under PROD_DELAYS …
admin.executeUpdateFeeParams(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. It needs no token, no distributor and no claim.
Range is [0, 100]; anything above reverts InvalidInput. UPDATE_FEES is pool-wide, so its subject is bytes32(0). The receiving side is 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: this is a gift to existing holders of the leg, not new depth.
Two hard rules:
- Donating into an unopened leg strands the gift permanently. With
liabilities == 0there is no share to raise; the surplus above the dead seed backs nothing and is inherited by nobody, ever.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 is what 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. The incentive-relevant facts:
rebalance()(keeper or AC owner) harvests venue NAV and credits 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. - Venue reward tokens do not reach LPs.
claimVenueIncentivespulls them onto the hook andsweepIncentivespushes them topool.treasury()(orincentivesReceiver). 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 HIGH tier, 3 days.
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 and needs 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 = 8 in IPool.OpType. ZERO32 = 32 zero bytes: UPDATE_FEES is
// pool-wide, so its subject is ignored.
const UPDATE_FEES = 8;
const ZERO32 = `0x${'00'.repeat(32)}`;
const feeParams = encodeAbiParameters(
[{ type: 'tuple', components: [{ type: 'uint8' }, { type: 'uint16' }] }],
[[0, 5]], // protoSharePct = 0, flashFeePbps = 5
);
// 1. All swap fees to LPs (LOW tier, 1 hour).
await admin.write('requestOp', [poolAddress, UPDATE_FEES, ZERO32, feeParams]);
// … after the delay …
await admin.write('executeUpdateFeeParams', [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, HIGH tier, 3 days. 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 | immediate |
deposit (POL) | anyone | immediate; flowCooldownSecs transfer lock |
UPDATE_FEES | AC owner | LOW, 1 hour |
UPDATE_HOOK | AC owner | HIGH, 3 days |
rebalance, claimVenueIncentives, sweepIncentives | keeper or owner | immediate |
setIncentivesReceiver | treasuryOwner | immediate |
collectProtocolFees | pool.treasury(), paying 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