Where the money goes

Profit and the high water mark

Profit is whatever the vault is worth above the highest level it has ever been swept from. The mark only moves up, so a loss has to be earned back before anything is paid out again.

Measuring equity#

sweep_profit is permissionless, and the first thing it does is work out what the vault is worth. Equity is the bankroll's own token balance plus every registered venue account, and the caller has to supply all of them, as pairs of [allowlist entry, token account], in ascending token-account order, with no repeats.

equity = bankroll.amount
       + sum over every registered venue account:
           Local  ──▶ the token account's balance, read directly
           Remote ──▶ VenueAccount.remote_equity, the attested figure

The ordering requirement is not cosmetic. The instruction checks that the number of supplied accounts is exactly venue_count * 2 and that each token account key is strictly greater than the last, so the set can be neither padded with duplicates nor cherry picked to understate how much is already deployed. Each allowlist entry's PDA is re-derived from ["venue", policy, token_account] and compared, so a balance that is not this policy's cannot be passed off as one.

A stale attestation fails the whole call

If any Remote venue account's attestation is older than Config.max_remote_staleness, the call reverts with StaleRemoteEquity. It does not fall back to the last known figure and it does not skip that venue. A money path never runs on stale data, not even for the portion of equity that happens to be local. See remote equity and attestation.

A schematic chart of vault equity over time against the high water mark. Equity rises to a first peak, where a sweep distributes the amount above the mark and the mark ratchets up. Equity then falls and recovers only to the level of the mark, and that recovery distributes nothing. Equity later rises past the mark to a second peak, where a second sweep distributes the amount above the mark and the mark ratchets up again. The mark never falls.EQUITYTIMESweepSweepmark starts at zeroBelow the marknothing is distributedEquityMarkdistributedretained, the mark is not beatenshape only, no data

Scroll the chart sideways to see all of it.

The mark ratchets up at every sweep and never falls, so a loss has to be earned back before anything is distributed again.

The three-way split#

If equity is at or below the mark, the call reverts with NothingToDo. Above it, the excess is profit and splits three ways by the policy's own basis points, which must sum to 10000.

ShareGoes toWhat happens next
profit_to_lp_bpslp_quotepicked up by compound_lp and pushed into the liquidity position
profit_to_holders_bpsholder_rewardsspent by buyback_and_burn buying the token back, which is then burned
profit_to_bankroll_bpsnowhereit is already in the bankroll, so it simply stays and keeps working

The holders' share is not a payment to holders

profit_to_holders_bps moves quote into a program-owned account that only buyback_and_burn can spend, and that instruction buys the token and burns it. No wallet is credited at any point. There is no claim, no airdrop and no snapshot.

Profit above the high water mark splits three ways. The LP share is transferred to the compound authority's staging account and compounded into the liquidity position. The holders share is transferred to the holder rewards account, where it buys the token back and burns it, so the supply falls and no wallet is ever credited. The bankroll share is never transferred at all: it is whatever is left, and it stays in the vault. A fourth box, a holder's wallet, is drawn unreachable because no arrow in the program ever ends there.Profit above the markthree shares, summing to 10000 bpsLP shareprofit_to_lp_bpsDeeper liquiditycompounded into the positionHolders' shareprofit_to_holders_bpsBuy back, then burnsupply fallsno wallet is creditedBankroll shareprofit_to_bankroll_bpsStays in the vaultnever transferred at alland one box nothing reachesA holder's walletno instruction ends here

Holders benefit through a smaller supply, never through a payment. There is no instruction that credits a wallet.

When the bankroll cannot cover the split#

Equity includes money sitting at venues, but only the bankroll's own liquid balance can actually move during the sweep. If the LP and holders' shares together ask for more than that balance, both are shrunk in place, proportionally, and the bankroll's own share absorbs the shortfall.

requested = to_lp + to_holders
if requested > bankroll.amount:
    to_lp     = to_lp * bankroll.amount / requested
    to_holders = bankroll.amount - to_lp
moved = to_lp + to_holders           # never more than the liquid balance

The ratio between the LP and holders' legs is preserved exactly; only their absolute size shrinks. to_holders is computed as the remainder so the two always add back to the liquid balance with no unit lost to truncation.

Moving the mark#

new_hwm = equity - moved

The mark is set to the equity that is actually left, not to the equity that was measured. The next call therefore sees zero profit until there is a real new gain, which is what makes a double sweep impossible: nothing that has already been paid out can be counted as profit again.

The mark never falls. After a loss, equity has to climb back above the previous mark before another sweep does anything, so a vault that loses money and recovers pays out on the new gain only.

A fully worked example#

Every figure below is read from a policy on the local validator through the indexer, or is the arithmetic the program applies to those figures. The policy is ExZAh5fTMWjTFgepEC24HcmyM35ftiZskypRNaPa7GBA, Passive mode, quote at 8 decimals, profit split 3000 LP / 5000 holders / 2000 bankroll.

QuantityBase unitsHow it arises
Bankroll, liquid399,000,000what was left in the bankroll token account
Deployed at the venue666,000,000one Local venue account, asset class STOCK, read directly
Equity1,065,000,000the sum of the two
Previous high water mark0never swept before
Profit1,065,000,000equity minus the mark
Requested for LP, 3000 bps319,500,000before the shortfall rule
Requested for holders, 5000 bps532,500,000before the shortfall rule
Requested total852,000,000more than the 399,000,000 liquid, so both shrink
Actually moved to LP149,625,000319,500,000 x 399,000,000 / 852,000,000
Actually moved to holders249,375,000399,000,000 minus the LP leg
New high water mark666,000,0001,065,000,000 minus the 399,000,000 moved
the chain's own record of the same sweep, via GET /coins/:mint
"hwm":      { "value": "666000000", "source": "chain:Policy account" },
"counters": {
  "profitSwept":   { "value": "399000000", "source": "chain:Policy account" },
  "paidToHolders": { "value": "0",         "source": "chain:Policy account" }
},
"pdaBalances": [
  { "kind": "bankroll",       "balance": { "value": "0" },         "decimals": 8 },
  { "kind": "quote_escrow",   "balance": { "value": "434625000" }, "decimals": 8 },
  { "kind": "holder_rewards", "balance": { "value": "249375000" }, "decimals": 8 }
]
  • The bankroll is at zero: the shortfall rule moved its entire liquid balance, and the 20% bankroll share of profit is represented by the 666,000,000 still sitting at the venue.
  • holder_rewards holds exactly the 249,375,000 computed above, staged and not yet spent.
  • quote_escrow holds 434,625,000, which is the 285,000,000 the fee split left there plus the 149,625,000 from this sweep. This capture predates a change. Both legs now land in lp_quote instead: route moves the LP share out of the escrow, and sweep_profit pays its LP leg there rather than into the escrow, so on the current program the same run would show an empty quote_escrow and 434,625,000 in lp_quote. The arithmetic is unchanged; only the account holding the result is. See the fee split.
  • paidToHolders is still zero, because it counts quote actually spent buying the token back. Landing in holder_rewards is staging, not payment.

Two numbers are both called profitSwept#

Policy.profit_swept accumulates moved, meaning what physically left the bankroll: 399,000,000 here. The /analytics day series reports profitSwept by summing the profit field of indexed ProfitSwept events, meaning the whole amount above the mark: 1,065,000,000 here. Both are correct for what they measure, and they differ whenever the shortfall rule bites. See the analytics endpoint.

The bankroll's own share never moves#

There is no transfer for profit_to_bankroll_bps. The share is defined as what is left after the other two legs leave, so a policy with profit_to_bankroll_bps = 10000 simply never transfers anything during a sweep, and the only observable effect of the call is that the high water mark rises to the current equity. That is still meaningful: it locks in the gain so that a later loss and recovery pays out nothing until the mark is beaten again.

When a sweep refuses to run#

ConditionError
Equity is at or below the high water markNothingToDo
Not every registered venue account was supplied, or they were out of order, or repeatedVenueSetIncomplete
A supplied allowlist entry does not derive to the expected PDA, or belongs to another policyVenueNotAllowed
A Local venue account is held in a mint other than the policy's quote mintWrongMint
A Remote attestation is older than max_remote_stalenessStaleRemoteEquity
A Remote attestation is dated in the futureFutureAsof
The policy is paused, including by a tripped deviation guardPolicyPaused
The protocol is pausedProtocolPaused