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

LeverCallWhoEffect
Fee splitrequestOp(UPDATE_FEES)executeUpdateFeeParamsAC owner, LOW tierLower protoSharePct → larger LP slice of every swap and flash spread
Direct subsidyPool.donate(token, amount)AnyoneRaises one leg’s index, one-shot
POLPool.deposit(token, amount)AnyoneAdds depth; earns fees, pays no one else
Hook yieldYieldHook.rebalance()Pool.hookCreditYieldKeeper or ownerVenue 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 FeeParamsUpdated

protoSharePct = 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:

  1. Donating into an unopened leg strands the gift permanently. With liabilities == 0 there is no share to raise; the surplus above the dead seed backs nothing and is inherited by nobody, ever. deposit first, then donate.
  2. It is instantaneous and un-vested. A donation lands on whoever holds the leg’s LPToken in 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 via Pool.hookCreditYield, raising the index for every holder of that leg.
  • The credit is capped as a rate, not a per-call allowance: at most maxHarvestCreditBps of 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. 0 disables crediting entirely. Details: Hooks §5.2.
  • Venue reward tokens do not reach LPs. claimVenueIncentives pulls them onto the hook and sweepIncentives pushes them to pool.treasury() (or incentivesReceiver). Adapters never swap rewards. Routing that value back to LPs is a treasury decision, executed as a donate (§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:

  1. Measure. Snapshot LPToken.balanceOf per holder for the leg: one EIP-1167 clone per (pool, asset), address from Pool.lpToken(token), ABI as LP_TOKEN_ABI in @btr-protocol/sdk/abis. Balances are already index-adjusted claims, so no unit conversion is needed. Snapshot each chain separately.
  2. 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

ActionAuthorityTiming
donateanyoneimmediate
deposit (POL)anyoneimmediate; flowCooldownSecs transfer lock
UPDATE_FEESAC ownerLOW, 1 hour
UPDATE_HOOKAC ownerHIGH, 3 days
rebalance, claimVenueIncentives, sweepIncentiveskeeper or ownerimmediate
setIncentivesReceivertreasuryOwnerimmediate
collectProtocolFeespool.treasury(), paying itselfimmediate

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.