Skip to main content
The CLP program holds the protocol’s market-making capital and runs both trading venues: committed orders, which fill at the next price push while a market is open, and native bin books, which fill instantly while its price is stale. It also mints and redeems its own L+S inventory through CPI into mint-redeem. A single GlobalClp PDA tracks funding and per-market allocations, and its cUSDC vault is the protocol treasury. Each market has a Clp PDA with its inventory, swap guards and committed-order spread, plus two Pool PDAs, one bin book per side.
Anyone can call pool_swap and commit_swap, anyone can send cancel_swap once an order has expired, and anyone can send claim for a held order. Only the keeper can send settle_swap. clp_swap is retired and always fails. The sections below give the signer every other instruction requires, usually global_clp.authority, the market’s clp.authority or the pool’s pool.authority.

The two venues

A committed order fills at a price pushed after the commit, so the trader never trades against a quote seen before committing. Since 11 Oct 2026 committed orders are on for every live market and are the protocol’s only venue while its price is live. pool_swap fills only while the market floats (its risk state isn’t Normal and its last push is more than 300 s old) and fails with PoolClosedWhileLive otherwise. The keeper sizes the bin books to expected demand. The instant oracle swap, clp_swap, was retired the same day. See SwapGuardsState below.

Exposure cap

Clp.oi_cap bounds traders’ net position in the market, in cUSDC atoms. 0 means uncapped. The protocol is the counterparty to whatever traders hold net, so the cap bounds what a one-sided move can cost the treasury. Exposure cap explains the reasoning and the nightly sizing. Traders’ net position E is measured at NAV from on-chain balances:
CLP-held for a leg is the balance of the pool’s synth vault for that leg plus the CLP’s own token account for it. E above 0 means traders are net long and the treasury is net short. A paired mint splits the deposit by value between the two legs, so it never moves E. A committed buy of L, a book buy of L and a committed sell of S back to the CLP all raise E. The reverse trades lower it. Redemptions burn a balanced pair, so they move E by less than $1. A change ΔE is allowed when the cap is 0 or when E + ΔE stays inside [min(−cap, E), max(cap, E)]. Nobody can push E further past the cap than it already is, and a trade that shrinks it always passes, even when a price move has carried E past the cap with no trade at all. The program reads the inventory from four accounts pinned by address, so a caller cannot understate the CLP’s holdings with a look-alike account. In order, they are the pool long synth vault, the pool short synth vault, the CLP’s long token account and the CLP’s short token account. An account that was never created counts as empty. A wrong address, owner or mint fails with ExposureAccountMismatch (6061) in mint-redeem. The measurement lives in mint-redeem’s exposure module, which the CLP calls. commit_swap is not checked, because nothing has moved yet: the cap applies when the order settles. A redemption signed by the market’s own CLP (vault_redeem_pairs) isn’t checked either, because burning CLP-held tokens lowers a supply and the CLP’s holding of it together, so E doesn’t move. Trades on the P2P order book are unaffected by the cap.

Accounts

GlobalClp PDA

One per cluster (BcGR8kuGCiMhwonyztXZHEZtjJXxXVRgjwbn58bUfXtw on devnet). The global vault is this PDA’s cUSDC associated token account (5GSTKN4u5LfropcnQQoqFht48JMaF1SzZzBcgDNL5VKy on devnet), and it is the protocol treasury. It held about 22.13M cUSDC after the keeper’s returns of 11 Oct 2026 and 36.1M on 13 Oct 2026, as the devnet keeper burns it down. The 22.13M was: 5,534,820.82 from funding, plus idle CLP cash that the keeper returned to it, 12.99M on the first pass and 3.6M more once the instant swap was retired. See Treasury. allocate_to_market adds a market_allocations entry the first time it funds a market. The table is informational and holds 10 entries, fewer than the markets, so it never blocks a move. Once it is full, a market without an entry takes over one whose allocation has been fully returned. If there is none, the move still goes ahead, and the market’s own allocated_from_global is the only record. Until the upgrade of 11 Oct 2026, funding an 11th market failed with MaxDeploymentsExceeded. As of 11 Oct 2026, five entries exist: QQQ, SPY, XAU, MSTR (closed on 12 Oct 2026, its allocation fully returned) and the delisted VXX market.

Clp PDA

One per market. Its operating vault (clp_vault) is this PDA’s cUSDC associated token account. Fields in account order:

SwapGuardsState

Stored in Clp.swap_guards and set with configure_swap. The all-zero default leaves committed orders off. settle_swap applies the staleness, confidence and deviation limits and the collateral floor. Since 11 Oct 2026, all 13 live markets have guards enabled and not frozen, with the same limits: a 600 s staleness limit, a 5,000 bps confidence limit (off in practice), a 10,000 bps deviation limit (off; update_risk_state_v2 already refuses a move of more than 25% per push) and a 10,000 bps (100%) collateral floor. Their spread and window cap were left as they were: QQQ, SPY and XAU keep a 30 bps spread and a cap of 1,000,000 cUSDC per hour, and the other markets have no cap.

Pool PDA

Two per market, so 26 for the 13 live markets. Each is a native bin book for one synth leg against cUSDC. The whole book lives in the account (up to 16 bins a side, POOL_MAX_BINS), so bins cost no extra rent. The pool PDA’s two associated token accounts hold the reserves: synth for the asks and cUSDC for the bids. There is no pool kill switch. Once a pool has a shape, nothing can disable it, though it fills only while its market floats (see pool_swap below). Bin geometry. Ask bin i sits at base · r^(i+1) and bid bin i at base · r^-(i+1). The centre itself is an empty mid, so the smallest round trip costs about two steps. With 2-zone geometry, the first core_bins steps use step_bps for fine pricing near the centre. The rest use wing_step_bps, so the same 16 bins can span a large move.

Order PDA (committed orders)

Each order records what the user escrowed and when, and the fill uses a price pushed after created_at. The account is 213 bytes, with every field at a fixed offset. Orders committed before the CLP-5 upgrade are 117 bytes, have no recorded accounts and still work. A client can find a wallet’s orders with getProgramAccounts memcmp filters on user (offset 8) or market (offset 40). The keeper finds every pending order in one call, filtered on the Order discriminator and status = 0 at offset 74, over both sizes (117 and 213 bytes). Every terminal path (fill, refund, cancel) empties the escrow, closes both the order and the escrow, and returns all rent to the user, so a second settle attempt fails at account load. The exception is a held order (status 1), which stays open until claim. Since 13 Oct 2026, tokens anyone sends to an escrow after the commit go back to the owner with the rest, so they can’t stop it closing.

TreasuryPolicy PDA

One per cluster (58JeHAcMAAyEZWusVqmvoFNVqh37cTkmfbbP6bMHExWR on devnet, 210 bytes). It holds the limits on admin_withdraw and treasury_lend and is created by init_treasury_policy. It replaced the AdminWithdrawWindow account, which the upgrade of 11 Oct 2026 removed. The program refills allowance only when an instruction touches the policy. To read what can be withdrawn now, compute min(burst, allowance + rate_per_sec × (now − last_accrual_at)). The live policy as read on 13 Oct 2026:
  • Rate: 256.78 cUSDC per second, about 924,403 cUSDC an hour. The keeper keeps the rate in proportion to the balance so that the whole treasury can leave in about 24 h; see Keeper → Treasury withdrawal rate. The earlier rate was 64.06 cUSDC per second.
  • Burst: 924,402.61 cUSDC, one hour of refill.
  • Delay: 24 h.
  • Pending: a rate and a burst of 1,000,000,000 cUSDC (in effect no limit), due at 04:29 UTC on 14 Oct 2026. It stays pending until then; read TreasuryPolicy for the values in force.

TreasuryYield PDA

One per cluster (6YrCADSTyTJPNrVhrZz5S2wTzBQSPM5fGUrGSpLTBJgY on devnet, 73 bytes), created by init_treasury_yield on 12 Oct 2026. It records the treasury cUSDC that is out at lending venues. Devnet has no lending venues, so its ledger stays at zero. A 1 cUSDC lend and recall on 12 Oct 2026 tested the instructions and left it there.

RevenueAttribution PDA

One per cluster (A8mK2pnagabEcnkSiWieEZyQjjNWFYm1Z91dUVMXe6a4 on devnet). It splits protocol revenue into keeper flow and external flow. deposit_profit takes it as its last account, and pool_swap takes it just before the market. Amounts are cUSDC base units. A fill’s spread capture is its distance from the book centre: cUSDC paid minus synth × base_price_1e6 on a buy, and synth × base_price_1e6 minus cUSDC received on a sell.

Instructions

Swaps

pool_swap(params: PoolSwapParams) (anyone)

PoolSwapParams { direction: u8, amount_in: u64, min_out: u64 }. Direction 0 buys synth with cUSDC, and 1 sells synth for cUSDC. A buy walks the ask bins up from base·r; a sell walks the bid bins down from base/r. The handler decrements reserves in place, so the stored book always matches the vault balances, and partial bin fills round in the pool’s favour. If the book runs out first, the swap fills what it can and takes only what it used. There is no swap fee. The pool must be enabled and not frozen (SwapDisabled), and base_price_1e6 must be nonzero (OracleStale). The market must be floating: its risk_state isn’t Normal and its twap_updated_at is more than 300 s old, the age at which mint and redeem freeze (PoolClosedWhileLive otherwise). The book must have been posted after the float began: pool.shape_updated_at must be more than 300 s after twap_updated_at, else PoolShapePredatesFloat. Something must fill (InsufficientFunds), the output must reach min_out (SlippageExceeded), and the fill must fit the Clp window cap (WindowCapExceeded). pool_swap doesn’t read swap_guards.enabled, frozen or the oracle guards: it trades on the book as posted. Off-hours the keeper keeps a demand-sized free-float book, with a guard that can only pull bins on the stale side. Until 11 Oct 2026 the books traded at any hour. Accounts, in order: The pool PDA signs the outbound transfer. Each fill updates RevenueAttribution and emits SwapExecuted with route 1. Remaining accounts, needed only when the market is capped (clp.oi_cap > 0): the pool long synth vault, the pool short synth vault, the CLP’s long token account and the CLP’s short token account, in that order. They are the inventory accounts the cap measures. A fill that would push traders’ net position past the cap reverts with ExposureCapExceeded (6045), and a capped market called without them fails with ExposureAccountsMissing (6046). An uncapped market ignores them. Unlike a committed order, a book fill is never cut to the room left: it fills whole or reverts.

clp_swap(side: u8, direction: u8, amount_in: u64, min_out: u64) (retired)

Retired on 11 Oct 2026. It was the instant swap against CLP inventory at NAV ± swap_guards.spread_bps, with NAV from Market.user_twap_price. The instruction keeps its accounts (user, user_usdc, user_synth, clp, clp_vault, clp_synth, market, token_program) and arguments, so existing clients still decode it, but every call fails with SwapDisabled. Use a committed order instead. Its fills emitted SwapExecuted with route 0.

commit_swap(side: u8, direction: u8, amount_in: u64, min_out: u64, nonce: u64) (anyone)

Opens a committed order. It escrows amount_in (cUSDC for buys, synth for sells) and sets expires_at = now + 180. The user pays rent for the order and escrow accounts and gets it back on every terminal path. The payout account (user_out) must already exist, so settlement can’t stall on it. It checks market.is_active (MarketInactive), swap_guards.enabled (SwapDisabled) and frozen (SwapFrozen). Accounts: user, market, clp, order, in_mint, escrow, user_in, user_out, clp_vault, token_program, system_program. Emits OrderCommitted.

settle_swap() (clp.authority or market.keeper_authority)

Fills a pending order at NAV ± max(clp.commit_spread_bps, 10) bps, with NAV from Market.user_twap_price at settle time. Any other signer fails with SettleKeeperOnly (6047). The keeper settles at the first push it sees after the commit, so the owner can’t pick the price. It requires:
  • now ≤ expires_at, else OrderExpired.
  • market.twap_updated_at > order.created_at, else NoFreshOraclePush. Any push after the commit qualifies, not only the first.
  • The oracle guards: not frozen, a price no older than max_staleness_secs, and confidence and deviation within limits.
It doesn’t recheck swap_guards.enabled. Since 13 Oct 2026 a paused market (market.is_active false) refunds the order before any price check, so a pause stops fills at once. The collateral floor is checked only on the fill path. A fill updates swap_prev_price but doesn’t count toward the window cap. Accounts, in order: The inventory accounts come after these. They are needed only when the market is capped: the pool long synth vault, the pool short synth vault, the CLP’s long token account and the CLP’s short token account. The keeper passes them on every settle. A capped market settled without them fails with ExposureAccountsMissing (6046), and the order stays pending until the keeper settles it with them. An uncapped market ignores them, as did the program before the upgrade, so passing them is always safe. A partial fill is the CLP’s check against the room left, measured at the settle price: the largest input whose value fits the room, with the spread included on a buy. A min_out that the partial fill misses is an ordinary refund. A settle always empties the escrow, so nothing is stranded. The keeper posts commit_spread_bps with set_commit_spread, as clamp(half-spread, 10, 500) from its volatility model, and re-posts it whenever it moves by 2 bps or more. As of 10 Oct 2026 it is 18 bps on QQQ, 15 on SPY and 18 on XAU.

claim() (anyone)

Pays a held order’s owner. It is permissionless, like cancel_swap, and every token goes to an account owned by order.user, so a caller can’t redirect anything. It returns the claim’s payout to the owner’s account for that mint, any refund in the escrow to the owner’s account for the escrowed mint, the escrow and order rent to the owner, and the claim account’s rent to claim_payer, the keeper that fronted it. The keeper cranks it and creates the owner’s associated token account first. Fails with OrderNotClaimable (6049) when nothing is held, and ClaimDestinationInvalid (6050) when a destination isn’t a live, unfrozen account of the owner for its mint. Emits OrderClaimed. Accounts: user, order, escrow, claim, claim_payer, refund_to, payout_to, token_program.

cancel_swap() (anyone, after expiry)

Refunds an expired order once now > expires_at (else OrderNotExpired). Settle owns now ≤ expires_at, so there is no gap or overlap, and before expiry nobody can cancel. Since 11 Oct 2026 anyone may send it: user must be the order’s owner but doesn’t sign, and the escrow can go only to user_in, a token account the owner holds for the escrowed mint (SwapAccountMismatch). For a 213-byte order, user_in must be the account recorded at commit. If that account can’t receive the refund, the cancel still succeeds, the funds wait in the escrow and the order is held for claim. Nothing else is checked (no frozen, enabled, oracle or market-active gate), so escrowed funds can always be recovered after the 180 s TTL. Returns the escrow’s whole balance and all rent to the owner and emits OrderCancelled. The keeper sends it for every expired order about 5 s after expiry. Accounts: user, order, escrow, user_in, token_program.

Bin book management

init_pool(side: u8, step_bps: u32, num_bins: u8) (global_clp.authority)

Creates the Pool PDA, and the signer becomes pool.authority. step_bps must be in (0, 1000] and num_bins in [1, 16]. The pool starts disabled with an empty book.

set_pool_shape(base_price_1e6: u64, asks: Vec<u64>, bids: Vec<u64>) (pool.authority)

The reposition. It sets the centre and every bin’s reserve in one call, and sets enabled = true. Each vector must have exactly num_bins entries and sum to no more than the matching vault balance. The keeper centres the book on NAV shifted for its inventory, with a skewed shape.

configure_pool_geom(wing_step_bps: u32, core_bins: u8) (pool.authority)

Sets the 2-zone geometry. A nonzero wing_step_bps must be at least step_bps, and core_bins at most num_bins. wing_step_bps = 0 restores the uniform ladder.

realloc_pool() (pool.authority)

Grows a pool created before the 2-zone fields existed. The new fields start at 0, which reads as the uniform ladder.

pool_sweep_usdc(amount: u64) / pool_sweep_synth(amount: u64) (clp.authority)

Move cUSDC out of a pool’s bid vault, or synth out of its ask vault, back to the CLP. One-way buying turns ask inventory into cUSDC that collects in the pool; the sweep recycles it through vault_mint_pairs into fresh L+S. The synth sweep trims ask depth far beyond the bid side, and the trimmed synth supplies committed orders instead.

clp_fund_pool_synth(amount: u64) (clp.authority)

Moves synth from the CLP’s L or S account into a pool’s ask vault, the refill step after vault_mint_pairs. No counters change; the next set_pool_shape places the tokens in bins.

clp_fund_pool_usdc(amount: u64) (clp.authority)

Moves cUSDC from the market’s CLP vault into a pool’s bid vault: the bid-side refill. Sell flow spends a book’s bid cUSDC. pool_sweep_synth takes back the synth the book bought, and this puts the cUSDC back. The source must be the CLP’s own vault, the destination must be the pool’s cUSDC vault, and the pool must belong to the CLP’s market (SwapAccountMismatch). No counters change; the next set_pool_shape places the cUSDC in bins. Added 11 Oct 2026.

Inventory minting (clp.authority)

The CLP mints and redeems through CPI into mint-redeem, signing as the Clp PDA.
  • vault_mint_pairs(usdc_amount: u64) calls mint_paired with cUSDC from the market’s CLP vault. The L+S land in the PDA’s token accounts, and vault_l_deployed/vault_s_deployed grow by the amounts actually minted.
  • vault_redeem_pairs(l_amount: u64, s_amount: u64) burns L+S held by the PDA and returns cUSDC to its vault.
  • vault_redeem_single(is_long: bool, token_amount: u64) redeems one leg at NAV through mint-redeem’s redeem_single_for_vault, which refuses to leave the market below 100% collateral.
  • clp_burn_excess(is_long: bool, amount: u64) burns leftover L or S without any payout, through permissionless_burn.
The three vault instructions take 15 accounts. The upgrade of 11 Oct 2026 dropped the trailing collateral_authority account, and callers that still send 16 keep working because the extra account is ignored. Each one checks where tokens can go:
  • market must be this CLP’s own market (MarketMismatch).
  • clp_vault must be owned by the Clp PDA and hold the market’s collateral mint (InvalidVault).
  • clp_long_account and clp_short_account must be owned by the Clp PDA and hold the market’s L and S mints (InvalidVault).

Capital flow

initialize_global_clp(liquidity_seed_threshold: u64)

One-time setup of GlobalClp. The signer becomes authority, the admin_wallet account is recorded, and the buffer target is set to 1,000 bps. It doesn’t create the global vault token account.

admin_fund(amount: u64) (global_clp.authority)

Moves cUSDC from the admin into the global vault, adds it to total_deposits and stamps last_admin_fund_at.

allocate_to_market(amount: u64) / return_to_global(amount: u64) (global_clp.authority)

Move cUSDC between the global vault and a market’s CLP vault, updating allocated_from_global and market_allocations. allocate_to_market refuses while the treasury is frozen, and takes treasury_policy as its last account. They move only between those two accounts. The global vault must be owned by the GlobalClp PDA, and the CLP vault must be owned by this market’s Clp PDA and hold the same mint (InvalidVault).

deposit_profit(params: DepositProfitParams) (global_clp.authority or a registered keeper)

DepositProfitParams { amount: u64, source: u8 }. Since 13 Oct 2026 the keeper signer must be global_clp.authority or a wallet in RevenueAttribution.keeper_authorities (else Unauthorized). Moves amount of cUSDC out of keeper_source, a token account the keeper signer controls. A tiered tax goes to admin_token_account, which must be owned by admin_wallet. The rest goes to the global vault and total_fees_accumulated. The tier follows total_deposits: 15% below $5M, 10% up to $25M and 5% above. The net amount counts as keeper flow in RevenueAttribution, under deposit_by_source[source] (0 arb, 1 recycled LP fees, 2 yield harvest, 3 other; unknown tags count as 3). Accounts: global_clp, keeper, keeper_source, global_vault, admin_token_account, token_program, revenue_attribution (last).

collect_fees(amount: u64) (clp.authority)

Moves fees into the market’s CLP vault and adds them to total_fees_collected.

Treasury

The global vault is the protocol treasury. admin_withdraw pays cUSDC out of the protocol, within the limits in TreasuryPolicy. treasury_lend sends idle cUSDC to a lending venue within the same limits, and treasury_recall brings it back. backstop_market moves cUSDC into a market’s collateral and has no limit, because the funds stay inside the protocol. The policy, backstop_market and the instructions that manage the policy were added on 11 Oct 2026, the three lending instructions on 12 Oct 2026, and advance_to_market on 13 Oct 2026.

admin_withdraw(amount: u64) (global_clp.authority)

Pays idle cUSDC from the global vault to the policy’s destination. It works as a token bucket: the allowance refills at rate_per_sec up to burst, and each withdrawal spends it. A withdrawal must fit in the allowance (TreasuryAllowanceExceeded) and in the vault balance (InsufficientLiquidFunds), and must pay the policy destination (InvalidTreasuryDestination). It is refused while the policy is frozen (TreasuryFrozen). Capital allocated to markets must first come back through return_to_global. Accounts: global_clp, authority, treasury_policy, global_vault (owned by the GlobalClp PDA, else InvalidVault), destination, token_program. Emits TreasuryWithdrawn. On 11 Oct 2026 the bucket replaced the old 24 h cap and the 1 h hold after admin_fund, so AdminWithdrawCapExceeded (6033) is no longer returned.

init_treasury_yield() (global_clp.authority)

Creates TreasuryYield once, paid for by the signer. Accounts: global_clp, authority, treasury_yield, system_program.

treasury_lend(params: TreasuryLendParams) (global_clp.authority)

TreasuryLendParams { amount, venue }, where venue is the YieldProtocol index, 0 to 4 (else InvalidAmount). Moves amount cUSDC from the global vault to lend_account, the authority’s own cUSDC account, which deposits it at the venue in the same transaction, as deploy_collateral_to_yield does for market collateral. It spends the TreasuryPolicy allowance exactly as admin_withdraw does, so lending can’t move money out of the treasury faster than the policy allows. A lend must fit in the allowance (TreasuryAllowanceExceeded) and in the vault balance (InsufficientLiquidFunds), and is refused while the treasury is frozen (TreasuryFrozen). It adds amount to by_venue[venue] and principal. Accounts: global_clp, authority, treasury_policy, global_vault (owned by the GlobalClp PDA, else InvalidVault), lend_account (owned by the authority, else Unauthorized, and holding the vault’s mint, else InvalidVault), treasury_yield, token_program. It emits no event; the program log carries the amount, the venue and the principal out.

treasury_recall(params: TreasuryRecallParams) (global_clp.authority)

TreasuryRecallParams { amount, principal, venue }. Moves amount cUSDC from lend_account back to the global vault. principal of it repays what was lent at venue and the rest is booked as interest. principal can’t exceed amount (InvalidAmount) or what is out at that venue (InsufficientDeployment). The returned principal restores the allowance its lend spent, up to burst. A recall works while the treasury is frozen, so money coming home is never blocked. Same accounts as treasury_lend. Nothing calls these on a schedule yet. The keeper’s yield allocator lends market collateral only, and devnet has no venues.

advance_to_market(amount: u64) (global_clp.authority and market.authority)

Lends amount cUSDC from the global vault into a market’s collateral vault, so that a queued redemption can be paid. The signer must be both GlobalClp.authority and the market’s authority. It is refused while the treasury is frozen (TreasuryFrozen), and it is not charged to the withdrawal allowance, because the cash stays protocol-owned until a settle pays a redeemer. It calls mint-redeem’s receive_advance by CPI with the GlobalClp PDA as funder, so the market’s max_advance_bps caps it (AdvanceCapExceeded) and the cash isn’t counted in total_collateral. mint-redeem’s repay_advance returns it to the global vault when the market’s own recall lands. Accounts: global_clp, authority, treasury_policy, market, market_advance, collateral_vault, global_vault, mint_redeem_program, token_program. Emits MarketAdvanced.

init_treasury_policy(params: TreasuryPolicyParams) (global_clp.authority)

TreasuryPolicyParams { rate_per_sec, burst, delay_secs, guardian, destination }. Creates TreasuryPolicy once, paid for by the signer. The allowance starts at 0. Rate and burst must be above 0, the delay must be 0 to 30 days, and a guardian must be set (InvalidTreasuryParams). The destination account must be the one named in the params and hold the global vault’s mint (InvalidTreasuryDestination).

set_treasury_limits(params: TreasuryLimitsParams) (global_clp.authority)

TreasuryLimitsParams { rate_per_sec, burst, delay_secs, destination, guardian }, checked like init_treasury_policy.
  • When every field is equal or tighter, the change applies at once. Tighter means a lower rate, a lower burst, a longer delay, the same destination and the same guardian. Emits TreasuryLimitsApplied.
  • Any loosening becomes pending until now plus the current delay_secs. A new proposal replaces an older pending one. Emits TreasuryLimitsProposed.
TreasuryLimitsProposed carries pending_at and a loosened bitmask: 1 rate up, 2 burst up, 4 delay shorter, 8 new destination, 16 new guardian.

apply_treasury_limits() (anyone)

Applies the pending change once pending_at has passed. It fails with NoPendingTreasuryChange when nothing is pending and TreasuryChangeNotReady before pending_at. It also works while frozen.

cancel_treasury_limits() (global_clp.authority or the guardian)

Drops the pending change. Emits TreasuryLimitsCancelled.

freeze_treasury() (the guardian or global_clp.authority) and unfreeze_treasury() (the guardian only)

Set and clear frozen. Both emit TreasuryFreezeChanged.

set_treasury_guardian(new_guardian: Pubkey) (the guardian only)

Hands the guardian role to a new key at once. If a pending change kept the old guardian, it now names the new one, so applying it later can’t restore the old key. Emits TreasuryGuardianSet.

backstop_market(amount: u64) (global_clp.authority)

Moves treasury cUSDC straight into a market’s collateral vault and credits total_collateral. It calls mint-redeem’s fund_collateral_from by CPI, with the GlobalClp PDA as funder. The signer is the global authority, which must also be the market’s authority. It is not rate limited, because the funds stay inside the protocol, and it works while frozen. The keeper’s liquidity reallocator calls it: at once for a market below 100% collateral at its stored price, and after 5 minutes for one short of its posted target by more than one confidence band. It fills to the posted target, at most 130% by default, and never takes a fill back. See Solvency → Treasury backstop. Accounts: global_clp, authority, market, collateral_vault (the market’s), global_vault, mint_redeem_program, token_program, treasury_policy (last). It refuses while the treasury is frozen. Emits MarketBackstopped, and mint-redeem emits CollateralFunded.

sweep_clp_lamports() (global_clp.authority)

Moves the lamports a per-market Clp PDA holds above its rent-exempt minimum to the authority. The signer must be global_clp.authority. Only Clp accounts qualify, never token accounts, orders or other records, and the account keeps exactly its minimum. It fails with NothingToSweep (6051) when there is nothing above it. Accounts: global_clp, authority (signer, writable), clp (writable). Emits ClpLamportsSwept.

Configuration

  • initialize_clp(oi_cap: u64, oracle_config: Pubkey) creates a market’s Clp with the default inventory budget, yield off and zeroed swap guards. The signer must be the Market’s authority and becomes clp.authority.
  • configure_swap(params: ConfigureSwapParams) (clp.authority) sets SwapGuardsState. ConfigureSwapParams { enabled, frozen, spread_bps, max_staleness_secs, max_confidence_bps, max_deviation_bps, window_cap_usd, window_secs, collateral_floor_bps } holds the separate arguments the instruction took until 11 Oct 2026, with the same fields in the same order, so the bytes on the wire are unchanged. Every field is optional, and None leaves it unchanged. frozen = true stops commit_swap and settle_swap, but not pool_swap or cancel_swap.
  • set_commit_spread(commit_spread_bps: u16) (clp.authority) sets the committed-order half-spread, at most 1,000 bps (else InvalidAmount). settle_swap floors it at 10 bps.
  • update_oi_cap(new_cap: u64) (clp.authority) sets oi_cap, the exposure cap. 0 uncaps the market. The keeper posts it nightly from the cr-targets job, and the posted value takes effect on the next trade.
  • configure_inventory_budget(params: ConfigureInventoryBudgetParams) and configure_yield_strategy(params: ConfigureYieldStrategyParams) (clp.authority) set the inventory bounds and the yield shares. Each struct holds the instruction’s former arguments as optional fields, in the same order, so the bytes on the wire are unchanged: ConfigureInventoryBudgetParams { soft_lower_bps, soft_upper_bps, hard_lower_bps, hard_upper_bps, max_drawdown_bps, target_drawdown_bps, enforce_hard_bounds } and ConfigureYieldStrategyParams { normal_deployment_bps, proxy_deployment_bps, stress_deployment_bps, recovery_deployment_bps, min_liquid_reserve, min_liquid_reserve_bps, oi_liquidity_buffer_bps, enabled }.
  • configure_liquidity_seeder(threshold: Option<u64>) and realloc_clp() (global_clp.authority) set the seed threshold and grow an older Clp to the current layout.
  • init_revenue_attribution(params: RevenueAttributionParams) and configure_revenue_attribution(params: RevenueAttributionParams) (global_clp.authority) create the RevenueAttribution singleton and replace its keeper_authorities. RevenueAttributionParams is { keeper_authorities: [Pubkey; 4] }. Configuring leaves the counters and started_at alone.

Inventory checks

  • update_inventory(volatile_value: u64, stable_value: u64) (clp.authority) recomputes q, drawdown and the breach flags.
  • update_oi(new_oi: u64) (clp.authority) sets current_oi and fails with OICapExceeded above oi_cap. Nothing calls it, and it has nothing to do with the exposure cap.
  • check_inventory_restrictions() returns hard_breach && enforce_hard_bounds, and get_yield_status() returns (total_deployed, max_deployable, can_deploy_more). Both are read-only and CPI-callable. max_deployable is still the old limit sized by the CLP’s own capital, which record_deployment stopped applying on 12 Oct 2026.

Yield (off)

record_deployment and record_withdrawal (clp.authority) and record_harvest (global_clp.authority) track deployments to lending venues. Since 12 Oct 2026 record_deployment refuses only when yield is off (YieldStrategyDisabled) or the market isn’t Active (ExceedsMaxDeployment). It used to also cap a market’s lending at a share of its CLP’s own capital, a few hundred dollars per market after the stock cuts. A market’s lending is now bounded by its yield_ceiling_bps in mint-redeem and by the keeper’s venue caps. add_yield_to_buffer and absorb_loss (global_clp.authority) maintain the global protocol buffer. Yield is off by default (yield_strategy.enabled = false).

Events

commit_swap emits OrderCommitted, settle_swap emits OrderSettled, OrderRefunded or OrderHeld (and OrderCapped as well when the exposure cap cut a fill short), cancel_swap emits OrderCancelled or OrderHeld, and claim emits OrderClaimed. sweep_clp_lamports emits ClpLamportsSwept. The treasury instructions emit TreasuryPolicyInitialized, TreasuryWithdrawn, TreasuryLimitsProposed, TreasuryLimitsApplied, TreasuryLimitsCancelled, TreasuryFreezeChanged, TreasuryGuardianSet, MarketBackstopped and MarketAdvanced. Their fields are in the IDL. Every fill on any route also emits SwapExecuted: OrderCapped is emitted together with OrderSettled when the exposure cap filled an order only in part:

Errors

→ Full error catalog

TypeScript

This reads QQQ’s long book and builds a pool_swap that spends up to 10 cUSDC on QQQ-L. provider is set up as in SDK setup, and the IDL is published in continuum-docs/idl.
On 10 Oct 2026 the read printed a centre of 751.27, a committed spread of 18 bps and true for the swap guards. A simulation of the instruction filled 10 cUSDC for 0.013257 QQQ-L. That was a Saturday, with QQQ’s price stale; while its price is live, the same instruction fails with PoolClosedWhileLive. Send it in a transaction that first creates the user’s synth ATA if it doesn’t exist.

See also

CLP concept

Why the CLP exists, how capital flows and the inventory bounds.

Trading

Swapping on the bin book and placing committed orders.