Skip to content

Merkle-claim splits: pay thousands of recipients past the 32-recipient ceiling #345

Description

@grantfox-oss

Difficulty: Expert. Scope: multi-week epic. Contract + SDK + dashboard.

Problem

MAX_RECIPIENTS: u32 = 32 (contracts/splitter/src/lib.rs) hard-caps a split at 32 recipients, and payout() iterates every recipient inside a single transaction. That makes the whole class of large-scale distributions impossible: a 5,000-address airdrop, a token-holder revenue share, a creator paying 400 contributors. There is no path to it today, and raising the constant does not work because the per-transaction resource budget cannot pay 5,000 transfers in one call.

Proposal

Add a second split kind: a merkle-claim split. The creator commits a merkle root of (recipient, share) leaves; recipients (or anyone on their behalf) call claim(id, token, recipient, share, proof) to pull their portion of a distribution. This turns an O(N) push into N independent O(log N) pulls.

Why this is hard

  • Requires a new storage and event model that coexists with the existing push splits without breaking pay, deposit, distribute, or nested Recipient::Split routing.
  • Claim accounting must be exact across multiple funding rounds: a root can receive several deposits over time, and a claimant must be able to claim their cumulative entitlement without double claiming. Design the "claimed so far" bookkeeping (per epoch, or cumulative-amount merkle a la Uniswap MerkleDistributor).
  • Must preserve the protocol invariant that amount in equals amount claimable out, including rounding and dust, matching the existing amounts() rules.
  • Interaction with nested splits: can a merkle leaf itself be a Recipient::Split? Decide and enforce.
  • SDK must generate roots and proofs deterministically and match the on-chain hashing exactly (byte-for-byte leaf encoding), or every claim fails.

Deliverables

  • Design RFC first (see acceptance).
  • Contract: create_merkle_split, fund (deposit against a root), claim with proof verification, is_claimed, and events.
  • SDK: leaf encoding, root construction, proof generation, and a typed claim helper.
  • Dashboard: a claim page where a recipient sees and claims their entitlement.
  • Full test suite including a large-tree fixture.

Acceptance criteria

  1. A design RFC is reviewed and signed off by a maintainer before implementation (leaf encoding, claim accounting model, dust handling, nested-leaf decision).
  2. A single root supports at least several thousand recipients, provable by test.
  3. Claiming is exact and idempotent: no double claim, cumulative funding handled, conservation holds (funded == sum of claimable).
  4. On-chain leaf hashing and SDK leaf hashing are proven identical by a cross-check test.
  5. Gas/resource cost of a claim is bounded and documented (coordinate with the cost-benchmark epic).
  6. Docs cover when to use a merkle split vs a push split.

Related

Complements, does not replace, push splits. Interacts with #260 (claimable fallback) and #264 (distribute_all_tokens); reconcile the claim model with those.

Metadata

Metadata

Assignees

No one assigned

    Labels

    GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26enhancementNew feature or requesthelp wantedExtra attention is neededrustPull requests that update rust code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions