The fractional claim-check, the wall the sizing did not see, and what the half really costs — 2026-08-30

Written by FRACCHECK, from FRACR3's sizing (docs/evidence/FRACTIONAL_CLAIM_CHECK_SIZING_2026_08_30.md) and docs/design/CLAIM_CHECK_COMPACTION_V1.md §17.

Result

The sleeping shard holder gets paid, and the reason is that the record names no payee — but not by the route the sizing costed, because that route cannot exist. A shard cannot be burned by its holder's signature. The design's closing sentence — "the holder redeems by burning, with their own signature, forever" — is false against the Token program this family actually deploys against, and it is false structurally rather than by an oversight in the implementation.

The claim survives the correction. What changes is that fractional compaction has to do one more thing while the market is still alive, and that one more thing is what makes the redemption route claims-only afterwards.

Four commits landed: the record type, the two conservation plans, the refusal sub-bands with the routing gate that extends FRACR3's weld, and a program-test campaign that executes the wall against the audited Token program rather than reading it. Eight did not, and the re-size below says why and by how much.

The finding: a shard holder cannot burn a shard

Every shard Mint in this family carries Token-2022's PermissionedBurn extension, and Token2022BehaviorProfileV2::read_mint requires it — the Mint is refused outright without it — pinned to the Mint's controller:

// crates/dclutch-token-svm/src/behavior_profile_v2.rs:232-240
PERMISSIONED_BURN_EXTENSION if !burn_seen => {
    require_extension(entry, PERMISSIONED_BURN_EXTENSION, AUTHORITY_EXTENSION_BYTES)?;
    require_key(entry.value, expected_controller)?;
    burn_seen = true;
}
// ...
if !close_seen || !burn_seen || pointer_seen != metadata_seen {
    return Err(Error::InvalidExtensionLayout);
}

In fractional_atomic_v3.rs::process_terminal that expected_controller is root_account.key — the Fractional capability root, which Pubkey::find_program_address(&header.seeds().as_slices(), trading_program.key) derives under the Trading program.

The audited fixture is spl-token-2022 v11.0.0 (programs/dclutch-claims-sbf/fixtures/token-2022-v11.provenance). Its process_burn is unambiguous:

// spl-token-2022-11.0.0/src/processor.rs:1148-1177
BurnInstructionVariant::Standard => {
    // Standard burns cannot be used when the permissioned burn
    // extension is present.
    if maybe_permissioned_burn_authority.is_some() {
        return Err(TokenError::InvalidInstruction.into());
    }
}
BurnInstructionVariant::Permissioned => {
    // ...
    if !approver_ai.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }
    if *approver_ai.key != expected_burn_authority {
        return Err(ProgramError::InvalidAccountData);
    }
}

It was executed, not inferred

programs/dclutch-claims-sbf/program-test/fractional-atomic/tests/permissioned_burn_wall.rs runs this against the audited v11 ELF (sha256 447ca3c6…, 615,704 bytes — the macos_arm64_audit row of the provenance, which the campaign checks for itself). The holder owns the account, signs for it, and the chain says:

Program log: Instruction: BurnChecked
Program log: Error: Invalid instruction
Program Tokenz…Pxu failed: custom program error: 0xc

0xc is TokenError::InvalidInstruction. Two further refusals pin which signature is missing rather than merely that one is: the permissioned burn with the authority present but not signing is MissingRequiredSignature, and with a different authority signing is InvalidAccountData. The fourth transaction is the control — both signatures, accepted, supply and holder balance each down by exactly the burn, 2,090 CU — without which the three refusals would be equally consistent with a Mint nobody can burn at all.

So a burn of a shard needs two signatures — the source account's owner and the Mint's configured burn authority — and the standard single-signature burn is not merely unused, it is refused. The tree already builds it that way: execute_terminal_burn passes root.key as the permissioned authority and actor.key as the owner, and gets the root's signature by propagation from the Trading frame that CPI'd in.

Three consequences, and each of them contradicts something the sizing assumed:

  1. A claims-only, holder-signed redemption route is impossible as specified. Claims cannot sign a Trading PDA, and no CPI reaches a route that survives the market.
  2. A redemption frame containing the root would not survive retirement, so it could not answer survives_retirement() and would fail the frame's own test — the discipline catches this correctly, which is why the record type below declares the root as a refused role rather than omitting it.
  3. §17.3's "shards cannot be compacted, only their backing" is right for the wrong reason. No crank should be able to burn a holder's shards; the reason it cannot is not that shards live in ordinary Token accounts, it is that the burn authority is somebody else's program's PDA.

The resolution, and its price

SetAuthority with AuthorityType::PermissionedBurn can move the authority, and processor.rs:996-1007 shows the shape: the current authority must sign, and the new one may be any address (or none). So:

Fractional compaction re-points the shard Mint's burn authority from the Fractional root to the Claims escrow PDA, while the root is still alive to authorize it. After that instruction, a redemption is claims-only and holder-signed: the escrow PDA signs as the burn approver, which Claims can do, and the holder signs as their token account's owner, which only they can do.

The second test in the campaign executes exactly this. A stranger attempting the hand-off is refused OwnerMismatch (0x4); the current authority's hand-off is accepted; the old authority is then as powerless as the market that held it (InvalidAccountData); and the holder-signed, escrow-approved burn goes through.

The price is real and is not in the sizing:

An alternative was considered and rejected. Shards could be impounded rather than burned — transfer is not permissioned, only burning is — into a claims-owned sink. It needs no Trading route and no new Mint profile. It was rejected because it replaces a supply the whole family reads with a balance in a second account: RetireCoordinate's supply gate, the terminal evaluator's expected_base_supply, and the record's own escrowed == floor(supply/denominator) * payout_per_claim invariant would each have to learn about the sink, and the sink itself becomes a perpetual account holding instruments nobody can destroy. Burning keeps one number meaning one thing.

What landed

commitwhat
9b65d15cFractionalClaimCheckV1 — 320 B, magic DCLTFCK1, kind 3, seeds [FRACTIONAL_CLAIM_CHECK_SEED, aggregate, shard_mint], the redemption frame spec
592ad8d9FractionalClaimCheckCompactionPlanV1 and FractionalClaimCheckRedemptionPlanV1
49a83dc6refusal sub-bands 0x5640 / 0x5660, and claim_check_route_for
ee7e86acthe permissioned-burn campaign: the wall and the hand-off, on real ELFs

The record, and why the unsignable owner dissolves

The record's coordinates are the instrument, not a person. The native record's address proves its holder; this one's proves which Mint it answers to. Nobody is named as the payee, so nobody has to sign as the payee, so the fact that the reserve Position's owner is a PDA stops mattering: the record was never going to pay that owner. That is the whole of the fractional correction, and it is why FRACR3's weld does not have to move — the two gates are complementary, and no_owner_kind_is_admitted_by_both_gates says so over the whole enum.

The lifetime, argued against the sizing's reasoning

FRACR3 specified "paid down across burns" and asked what replaces absence-based anti-replay. The answer has two halves:

Where the remainder goes: nowhere, and that is the decision

Compaction escrows floor(supply / denominator) * payout_per_claim — every whole claim the outstanding supply could form. Holders can only redeem sum_i floor(shards_i / denominator), which is smaller whenever the supply is spread across accounts carrying sub-denominator dust. Worked example: denominator 1,000, supply 250 held as 90 + 90 + 70; the compaction escrows nothing at all, and if the same 250 sat in one account it would escrow nothing either — but at supply 12,345 spread as 12,000 + 345 the escrow holds twelve claims and the dust holder can form none.

That gap is not stranded, and the difference matters. Shard transfer is ordinary — only burning is permissioned — so any two dust holders can consolidate and redeem what neither could alone. The remainder therefore stays escrowed against a claim that can still be formed, and the record can stay open forever if nobody forms it. This is the escrow's own §4.9 ruling one level down: an escrow holding a live claim-check is holding somebody's collateral, and closing it would be taking their money. A sweep-to-a-beneficiary rule was written, then deleted: it pays a third party out of collateral whose claimants had merely not coordinated yet.

The one place fractional conservation is stricter than native

A native record promises a total, so §15.4's fee tolerance works: a vault credited less than was sent can promise the smaller number and the one holder is paid what is there. A fractional record promises a rate that every holder applies independently, arriving one at a time and unknown at compaction. Reducing the rate underpays the early holders; keeping it leaves the last holder unpayable. So a short vault credit is RateNotCovered — a refusal, not a smaller promise. The credit is still observed rather than assumed; only its tolerance is zero.

Conservation tests, by name

testwhat it pins
the_only_division_floors_and_sub_denominator_dust_claims_nothingthe quotient is the sole division, floors at eight inputs including u64::MAX, and never consumes more than the holder holds
a_burn_is_paid_exactly_what_on_time_redemption_would_have_paidthe payout is a multiplication by the persisted rate, and overflow refuses rather than wraps
the_escrowed_balance_pays_down_to_the_atom_and_settles_exactly_at_zerofour partial burns sum to the opening balance; everything but the balance is immutable across a pay-down
a_pay_down_that_would_overdraw_another_holder_is_refusedoverdraw is Arithmetic, not saturation; exactly the balance settles, one atom less does not
one_burn_pays_exactly_what_on_time_redemption_would_have_paid3,400 shards → 3 claims, 3,000 burned, 400 returned, 3 × payout_per_claim paid
conservation_holds_to_the_atom_across_the_whole_pay_downfour arrivals in the wrong order, one sub-denominator; per burn collateral × denominator == consumed × payout_per_claim and presented == consumed + change; across the pay-down the payouts sum to the opening escrow, the vault reaches zero, and the shards left outstanding are exactly the 345 atoms of dust the compaction never escrowed
a_vault_credited_less_than_the_rate_requires_is_refused_not_reducedRateNotCovered at one atom short; the shared plan still refuses one atom over
the_settling_burn_closes_the_record_and_no_other_burn_moves_a_lamportclosing a live record and leaving a settled one funded are both PostconditionMismatch
a_post_state_that_burned_the_wrong_number_of_shards_is_refusedsix single-field mutations of the post-state, each refused
a_compaction_escrows_every_whole_claim_the_supply_can_form_and_no_dustthe 345-atom remainder is escrowed for nobody
the_lamport_split_is_the_native_plans_and_not_a_second_copy_of_itthe rent ordering has one author

The weld, extended

FRACR3's owner_kind_can_open_a_claim_check is untouched and is now read by a test in the fractional module rather than restated:

testwhat it pins
the_native_gate_is_not_relaxed_by_this_laneall three arms of the shipped weld, asserted against the shipped function
no_owner_kind_is_admitted_by_both_gatesover the whole enum: a Position compacted twice would resolve one pot of collateral into two records
the_pda_claimant_design_is_what_admits_the_kind_the_weld_refusesTradingRecord is refused natively and admitted fractionally, and neither gate had to move
every_owner_kind_is_routed_and_exactly_one_is_still_strandedthe routing is total, and ClaimsCapability is still stranded — counted, so the debt cannot quietly grow or vanish

The re-size

FRACR3 estimated one lane, eight commits. That was wrong by roughly +5 commits and one extra program, and the correction is upstream of the estimate rather than a scaling error inside it.

#commitstatusnote
1record type and seedslanded9b65d15c
2two conservation planslanded592ad8d9
3refusal sub-bandlanded49a83dc6; grew from one sub-band to two (0x5640, 0x5660) plus the routing gate
3bnew — the permissioned-burn campaignlandedee7e86ac; not in the sizing because the sizing did not know the wall was there
4new — read_compacted_shard_mint in dclutch-token-svmnot writtena re-pointed Mint fails read_mint; the profile has to admit a split controller
5new — a Trading route that composes fractional compactionnot writtena second ELF and a second cohort; the root's signature exists nowhere else
6the compaction routenot writtenthe sized one, plus the SetAuthority leg and its own postcondition
7the burn-and-pay redemption routenot writtenthe sized one, with the escrow signing as burn approver
8RetireCoordinate's compacted armnot writtenmust skip execute_mint_close, admit nonzero supply, and use the profile from #4
9new — the fractional escrow closenot writtenthe native CloseClaimCheckEscrow reads ClaimCheckEscrowV1's counter; fractional records admit and retire against the same counter, so the close needs to be shown correct for a mixed escrow rather than assumed
10the campaignnot writtenlarger than sized: it must drive a re-point, at least two holders, a partial burn and a settling burn
11the operator surfacenot writtenas sized
12new — the design amendmentnot written§17.3's closing paragraph is wrong on the record and should say so where it is read

Fourteen commits, two programs, two cohorts. The expensive half FRACR3 identified as already paid — the terminal fixture — is genuinely already paid. What is not paid is the Trading half, which the sizing did not model at all because it assumed the burn was the holder's to make.

Not verified