The fractional half of R3 — a hole in the narrowing, and what closing it costs — 2026-08-30

Result

Not closed, and not merely deferred: the narrowing had a hole, and the hole is welded. Claim-check compaction shipped this morning refusing one of the two owner kinds that cannot sign. The other one is the Fractional reserve Position. Admitting it meant that past the deadline any caller could convert every shard holder's collateral into a record nobody can ever open — turning R3's delay into a total loss for exactly the holders §10 of the design said it was leaving alone.

The fractional half of R3 remains open, as it was. It is now sized, with measured numbers, and its real blocker turns out not to be the one the design anticipated.

The finding

RedeemClaimCheck pays the record's owner and requires that address to sign — 0x5621, plus the holder's signer role in the redemption frame spec. A program-derived address cannot sign a top-level instruction and no CPI reaches the route, so a claim-check minted for a PDA is collateral written to an address that can never open it.

ProtocolPositionOwnerKindV2 has three variants and two of them are PDAs:

kindownercan sign
User = 1a walletyes
TradingRecord = 0a Trading-program PDAno
ClaimsCapability = 2a Claims-program PDAno

claim_check_compaction_v1.rs refused ClaimsCapability and admitted TradingRecord. The design's own §16.4 states the reason correctly and then does not act on it: "a Trading record or Claims capability owner is a PDA and cannot sign" — two kinds named, one refused. §4.7's precondition list makes the same error explicitly, admitting User or TradingRecord.

TradingRecord is the Fractional reserve. The Fractional family asserts it itself, in fractional_retirement_v3.rs:

if admission.owner_kind() != ProtocolPositionOwnerKindV2::TradingRecord
    || admission.position_owner() != input.root

where input.root is the Trading-owned Fractional capability root PDA — the same account fractional_atomic_v3.rs requires as the reserve Position's owner on both the open and the terminal path. That Position holds the collateral backing every outstanding shard of one coordinate.

So the reachable sequence was: market goes terminal with shards outstanding → anyone opens the escrow → the deadline elapses → anyone cranks the reserve. The collateral moves to the vault, a claim-check is minted naming a PDA, and the Position every shard holder's own redemption reads is closed in the same transaction. Nobody can redeem the record. Nobody can redeem the shards. The escrow can never close, because its outstanding counter never returns to zero.

Same class as the design's own §2 finding — value destruction by an arbitrary actor with no stake — and arrived at by implementing §4.7 faithfully. Not exploitable today: the deadline is 38,880,000 slots (~180 days) and the feature landed this morning. It ships in the next claims-sbf cohort.

The weld

The gate is now an exhaustive function over the enum rather than a comparison against one variant:

const fn owner_kind_can_open_a_claim_check(kind: ProtocolPositionOwnerKindV2) -> bool

Exhaustive on purpose. A fourth owner kind has to answer can this address sign for its own payout rather than inherit whichever arm it was written beside. This is C0's own lesson from §15.5 applied a second time: a named admission with one author, not a predicate each site maintains separately.

Nothing is stranded by refusing TradingRecord. A Trading-owned Position has its own parent-authenticated close; the Fractional reserve has fractional_retirement_v3's ordered route. Compaction was never the only way to retire either — only the only way to destroy them.

Tests

testwhat it pins
a_position_whose_owner_could_never_sign_for_it_is_not_compactedboth PDA kinds refuse at 0x560A, on the fixture the differential test compacts successfully, so the gate is the only thing refusing. Asserts the three things a partial refusal would leave behind: no claim-check minted, the position keeps every atom, the position is not closed out from under its claimants
only_an_owner_that_can_sign_may_be_promised_a_claim_checkthe answer is stated over the whole enum, so a kind added later is a compile error and not a silent third true
exactly_one_owner_kind_is_a_wallet_and_the_other_two_are_program_derivedthe reason the gate reads the way it does, kept as an assertion a future edit has to argue with

17 of 17 claim-check campaign tests pass against a freshly built claims-sbf ELF, 0 SBF frame diagnostics across the six ELFs the campaign loads. The 16 pre-existing tests were unchanged by the weld.

Sizing the fractional close

The arithmetic is kinder than §10 assumed

§10 sketched "a pro-rata entitlement per shard". There is no pro-rata. divide_exposure_shards_v2 is the sole quotient/remainder boundary, and the payout below it is a multiplication by a per-coordinate constant:

whole_claims     = shard_atoms / denominator     (floor, the only division)
consumed         = whole_claims * denominator    (burned)
change           = shard_atoms - consumed        (stays in the holder's account)
collateral_atoms = whole_claims * payout_per_claim[coordinate]

A fractional claim-check storing denominator and payout_per_claim therefore pays to the atom what on-time redemption would have paid, from the same two numbers and the same two operations — no second rounding boundary, no remainder ledger, no last-burner residue. Sub-denominator dust is not a claim on collateral before compaction (NoWholeClaim is a refusal, not a zero payout) and must not become one after.

Record-shape decision: a second record type, [FRACTIONAL_CLAIM_CHECK_SEED, aggregate, shard_mint], sharing the escrow, vault, deadline and close gate unchanged. The new fields are 20 bytes and would fit ClaimCheckV1's 24 reserved body bytes — the decision is not about space. entitlement_atoms means "paid once, then the record closes" natively and "the remaining balance, paid down across many burns" fractionally; the lifetimes differ; and one width meaning two field layouts turns the hostile decode into a union whose arms diverge after the kind byte.

Compute is not the blocker

Measured in this campaign, same fixture, same build:

transactionCU
the holder's own wallet payout472,599
compaction of that same position503,554
claim-check redemption20,958

Compaction costs 30,955 CU more than the redemption it stands in for — the record write, the close, the four-credit split, the plan, the escrow update. Fractional terminal settlement's own measured table (program-test/fractional-atomic/tests/fractional_atomic.rs) is

width   8    16   32   48   64    96     98     99
units 463k 519k 593k 731k 897k 1356k  1393k  exhausted

so a fractional compaction at the supported width 64 lands near 928k of 1,400,000 — 6.6% over the settlement, inside the 503k headroom already there. Frame: fractional terminal is 44 accounts, plus compaction's 6 is 50, under devnet's 64-lock limit, over the ALT these campaigns already serialise through.

The blocker is that fractional retirement is not reachable at all

fractional_retirement_v3.rs dispatches only RetireCoordinate. It refuses FractionalRetirementActionV3::Begin and ::Finish outright, and no program calls either — FractionalRetirementCursorV3::begin and ::finish exist in the contract with tests, but nothing on chain can create or close the cursor PDA the coordinate walk advances.

So a fractional claim-check would make RetireCoordinate's gates satisfiable and the market still would not retire. Wiring Begin/Finish is its own lane and is strictly upstream.

Two more structural items, both real and neither a surprise:

Estimate

One lane, eight commits, sequenced after the Begin/Finish lane. Record type and seeds; two conservation plans; a refusal sub-band (0x5640 and 0x5660 are free); the compaction route; the burn-and-pay redemption route; the RetireCoordinate arm; the campaign; the operator surface. The expensive half of the native lane — building a terminal fixture — is already paid: fractional-atomic drives terminal redeem and zero-burn against real ELFs today.

Census accounting

R3 stays narrowed, not closed: closed for native positions, open for fractional ones. What changed is that the narrowing is now safe. Before this weld, "narrowed" was too generous a word — compaction had made the fractional half strictly worse than the row it was narrowing.

Not verified