> For the complete documentation index, see [llms.txt](https://docs.trover.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trover.tech/engineering/claim-system-design.md).

# Trover stock claim system

## Decision

Trover will replace browser-driven push airdrops with a pull-based, prefunded Merkle distributor on Robinhood Chain. An operator creates an immutable holder snapshot and entitlement manifest; users claim their own stock-token entitlements from `/claim`.

The claim contract is the authority for whether an entitlement has been spent. The database is an index and user interface, not the source of truth.

## Non-negotiable invariants

1. A tranche has one immutable token, Merkle root, manifest hash, allocation, start time, and deadline after activation.
2. Every leaf commits to the chain ID, distributor contract, tranche ID, entitlement index, recipient, token, and raw token amount. A proof therefore cannot be replayed on another chain, contract, tranche, wallet, or token.
3. A bitmap marks each entitlement index before transferring funds. The claim function is non-reentrant and follows checks-effects-interactions.
4. Anyone may relay a valid proof, but funds always go to the recipient encoded in the leaf. There is no caller-controlled recipient.
5. Every active tranche is fully reserved. Activation fails unless the contract balance covers all existing token reservations plus the new allocation.
6. A claim decreases both the tranche remainder and the token’s global reserved balance by the exact raw amount.
7. An operator cannot alter a live root or increase an active allocation. A tranche can be cancelled only before it starts.
8. Unreserved tokens may be recovered. Reserved tokens cannot be rescued. Expired remainder can be released only after the deadline.
9. Fee-on-transfer, rebasing, callback-bearing, paused, denied, stale, noncanonical, or unsupported tokens cannot be used.
10. The API never marks a claim confirmed from a browser response alone. It requires a successful chain receipt with the exact `Claimed` event, or independently indexes that event.

## Entitlement construction

An internal operator enters:

* eligibility token address;
* minimum raw/display holding;
* stock token to distribute;
* total stock amount and either fixed or pro-rata allocation mode;
* snapshot block;
* claim start and deadline.

Trover completes one holder-provider snapshot cycle, records the contemporaneous chain block as its audit marker, and fails closed if the provider truncates or cannot prove completeness. It excludes:

* zero and burn addresses;
* the source/treasury/distributor addresses;
* known scam addresses;
* contract addresses by default;
* holders below the raw minimum.

Automatic stock tranches use the eligible balances as weights:

```
wallet allocation =
  purchased stock-token raw amount
  × wallet eligible TROVER balance
  ÷ total TROVER balance across eligible wallets
```

Integer remainders are assigned deterministically by largest remainder, with address order as the tie-breaker. Each resulting wallet-specific amount is committed into its own Merkle leaf. Snapshot reads and proof construction are offchain and require no gas; manager create/activate transactions and the treasury's stock-token funding transfer are gas-preflighted onchain writes.

Recipients are checksummed, sorted by raw address bytes, and assigned a stable zero-based index. Duplicate addresses are rejected before tree generation.

The canonical leaf is:

```solidity
keccak256(
  bytes.concat(
    keccak256(
      abi.encode(
        block.chainid,
        address(this),
        trancheId,
        index,
        recipient,
        token,
        amount
      )
    )
  )
)
```

The double hash follows OpenZeppelin `StandardMerkleTree` conventions and avoids ambiguous 64-byte preimages. Sorted-pair proofs are generated with the same library used by contract tests. The full manifest is canonical JSON; its SHA-256 digest is stored onchain as `manifestHash`.

## Contract state and roles

`TroverStockDistributor` uses audited OpenZeppelin components:

* `AccessControl`;
* `Pausable`;
* `ReentrancyGuard`;
* `SafeERC20`;
* `MerkleProof`.

Roles:

* `DEFAULT_ADMIN_ROLE`: production multisig;
* `TRANCHE_MANAGER_ROLE`: creates and activates prepared tranches;
* `PAUSER_ROLE`: can pause claims;
* `RECOVERY_ROLE`: can release expired, unreserved balances (`releaseExpired(trancheId)` after the deadline, then `recoverUnreserved(token, to, amount)` for balance above `reservedByToken`).

No raw server key should hold `DEFAULT_ADMIN_ROLE`. Deployment ownership must be transferred to a multisig before the first funded tranche.

On Robinhood Chain the live `TroverStockDistributor` is `0x53f6B2498374ebEB8c47B0C4a83b0eF78ea071CC`, administered by the 2-of-2 Safe `0x0e491ff9dB0956558891a71587015a8Cc64FaF35`. `TroverEthDistributor` mirrors the same ABI for native ETH (`address(0)` in leaves and events) and is part of the stack in `packages/contracts/scripts/deploy-stack.mjs`; it is rehearsed on testnet 46630 and not yet on mainnet. Both embed `PROJECT_X_URL` and `AGENT_X_URL` as constants that are immutable once deployed: the live stock distributor names `@troverobinhood`, the ETH distributor source names `@troveragent`. Decide the handle before the mainnet stack run.

Per tranche:

```
token, root, manifestHash, totalAllocated, totalClaimed,
startsAt, deadline, active, expired
```

Global:

```
reservedByToken[token]
claimedBitMap[trancheId][word]
```

## Claim transaction

The page obtains the canonical wallet from the authenticated Privy profile and requests entitlements only for that wallet. It independently reads:

* tranche configuration;
* claimed bitmap;
* distributor token balance;
* reservation state;
* current block time.

The connected wallet sends:

```
claim(trancheId, index, account, amount, proof)
```

An embedded Privy wallet may sign in-app. An external wallet signs through its provider. A relayer may be added later, but the first production release will not introduce gasless signatures or server-side claim custody.

The UI treats `submitted` as pending. It shows stock/logo/value only under “claimable” until the exact `Claimed` event is confirmed, then moves it to “claimed/current holdings.”

## Database model

`ClaimTranche`

* immutable snapshot inputs and recorded block marker;
* contract tranche ID and distributor address;
* token/root/manifest hash/allocation/reservation;
* prepared, funding, active, completed, expired, cancelled status;
* creation/activation transaction hashes.

`ClaimEntitlement`

* tranche ID, stable index, wallet, raw/display amount;
* leaf and proof;
* submitted/confirmed transaction hash;
* observed `Claimed` block/log index;
* unique `(trancheId, index)` and `(trancheId, wallet)`.

`ClaimEvent`

* unique `(chainId, txHash, logIndex)`;
* normalized event data and observed block;
* reorg-aware confirmation state.

## Admin workflow

1. Preview the completed holder snapshot.
2. Freeze and save the canonical manifest.
3. Generate and locally verify every proof.
4. Create the onchain tranche with root and manifest hash.
5. Fund the distributor.
6. Verify exact balance/reservation solvency.
7. Activate the tranche.
8. Publish it to `/claim`.

Steps 4–7 each require an explicit connected-wallet signature. “Sign all” is not permitted. The push-transfer queue will be removed.

## Failure and recovery rules

* Truncated snapshot: do not create a tranche.
* Root/manifest mismatch: do not fund or activate.
* Insufficient contract balance: activation fails.
* Transaction pending: never resubmit automatically until reconciled.
* Reverted claim: entitlement remains unclaimed.
* Duplicate/replayed claim: bitmap check reverts.
* RPC disagreement: show unavailable and do not claim.
* Chain reorg: wait configured confirmations and re-index.
* Expired tranche: claims stop; multisig may release only its remaining reservation. The ETH distributor's `releaseExpired` then `recoverUnreserved` path is covered by `packages/contracts/test/TroverEthDistributor.test.ts` (recovery is refused above the unreserved balance, refused before the deadline, and sweeps the released remainder after it). The Safe transaction is built, signed and executed with `packages/contracts/scripts/safe-rescue.mjs` (`build eth.releaseExpired <trancheId>`, then `build eth.recoverUnreserved <to> <eth>`); procedures are in [contracts-runbook.md](/engineering/contracts-runbook.md).
* Compromised manager: pause; manager cannot withdraw reserved funds.

## Verification gates before deployment

* Solidity unit and fuzz tests for every invariant above.
* Differential proof tests between TypeScript and Solidity.
* Property test: total successful claims never exceeds allocation.
* Property test: reserved token balance never becomes negative.
* Replay tests across tranche, account, token, contract, and chain.
* Reentrancy and malicious-token tests.
* Fork test on Robinhood Chain using supported stock tokens.
* Independent static analysis (`slither`) and contract size checks.
* Testnet deployment with at least two tranches and repeated/expired claims.
* Mainnet deployment behind a multisig and pause drill.
* For the ETH distributor and the rest of the stack: the testnet rehearsal in [contracts-runbook.md](/engineering/contracts-runbook.md) (35 recorded transactions on 46630, every rescue path and every pause/unpause) before the mainnet run.

The Claim navigation item must not be enabled in production until the contract address, chain deployment receipt, multisig roles, funded test tranche, proof vectors, and indexer reconciliation all pass.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.trover.tech/engineering/claim-system-design.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
