source
fabler's off-chain cast engine, in full and verbatim.
// fabler / cast engine
// the off-chain layer. it watches the collector for sol the runtime deposited,
// raises the reward-per-token index by what landed, retains a share to the
// floor, and settles each holder's claim as arithmetic. it never invents a
// number it could not read on chain.
type Lamports = bigint;
const PRECISION = 1_000_000_000_000n; // fixed-point scale for the index
const BPS = 10_000n;
interface CollectorState {
index: bigint; // reward per token, scaled by PRECISION
totalHeld: bigint; // total token supply that shares the cast
floor: Lamports; // retained, locked into liquidity, only rises
retainBps: number; // share of each deposit kept as floor
lastSeen: Lamports; // collector balance at the last sync
}
interface Holder {
owner: string;
held: bigint; // token balance
debt: bigint; // the index as it stood when they last settled
}
// a deposit from the runtime is whatever the collector gained since the last
// sync. inflation lands at the turn of an epoch, block revenue lands each
// block, neither is signalled, so we read the balance and take the difference.
function observedDeposit(state: CollectorState, collectorBalance: Lamports): Lamports {
return collectorBalance > state.lastSeen ? collectorBalance - state.lastSeen : 0n;
}
// fold a deposit into the index. a share is retained to the floor, the rest is
// spread across every holder at once by raising the index. the floor only rises.
function sync(state: CollectorState, collectorBalance: Lamports): CollectorState {
const landed = observedDeposit(state, collectorBalance);
if (landed === 0n || state.totalHeld === 0n) {
return { ...state, lastSeen: collectorBalance };
}
const retained = (landed * BigInt(state.retainBps)) / BPS;
const cast = landed - retained;
return {
...state,
index: state.index + (cast * PRECISION) / state.totalHeld,
floor: state.floor + retained,
lastSeen: collectorBalance,
};
}
// what a holder is owed: their balance times how far the index has moved since
// they last settled. someone who arrived after a deposit does not share in it,
// because the index had already risen before they held.
function claimable(state: CollectorState, holder: Holder): Lamports {
return (holder.held * (state.index - holder.debt)) / PRECISION;
}
// settle a holder: pay what they are owed and mark them current at the index.
function settle(state: CollectorState, holder: Holder): { paid: Lamports; holder: Holder } {
const paid = claimable(state, holder);
return { paid, holder: { ...holder, debt: state.index } };
}