Skip to content

feat: build holder reward for distribution key holder - #1162

Merged
Chucks1093 merged 4 commits into
accesslayerorg:mainfrom
somotochukwu-dev:build-holder-rewards-for--distribution-key-holders-1096
Oct 20, 2026
Merged

Chucks1093 merged 4 commits into
accesslayerorg:mainfrom
somotochukwu-dev:build-holder-rewards-for--distribution-key-holders-1096

Conversation

@somotochukwu-dev

Copy link
Copy Markdown
Contributor

Add holder-rewards contract for airdrop distributions to key holders

Summary

Implements a new holder-rewards Soroban contract that lets a key's creator or the
protocol admin fund token airdrops distributed pro-rata to all current key holders,
based on a balance snapshot taken at airdrop creation.

Motivation

Creators and the protocol need a way to reward existing key holders with arbitrary
SEP-41 tokens. The existing airdrop_keys in creator-keys mints new keys; this
contract covers the complementary flow — distributing external reward tokens to the
holder base without touching key balances or the bonding curve.

Changes

New crate: holder-rewards/

src/lib.rs — HolderRewardsContract

  • create_airdrop(key_id, caller, reward_token, total_amount) -> airdrop_id
    • caller must be the key creator (key_id, matching the creator-keys
      convention) or the protocol admin; otherwise reverts with Unauthorized.
    • Snapshots every holder's balance at the creation ledger by reading the
      creator-keys contract's get_holder_registry / get_key_balance views via a
      #[contractclient] trait (CreatorKeysReader). The creator-keys address is
      stored at init.
    • Only non-zero balances are written; their sum is frozen as
      total_snapshot_supply in the airdrop record, so later balance changes cannot
      shift any allocation.
    • Escrows total_amount of reward_token from the funder into the contract.
    • Bounded by MAX_AIRDROP_HOLDERS = 100 non-zero holders, mirroring the bounds
      creator-keys applies to take_snapshot / airdrop_keys.
  • claim_airdrop(airdrop_id, caller) -> amount
    • Pays total_amount * snapshot_balance / total_snapshot_supply, marks the
      wallet claimed, transfers the reward tokens, and reverts with
      AlreadyClaimed on a double claim.
    • Reverts with NoAllocation for wallets that held nothing at the snapshot.
  • get_airdrop_allocation(airdrop_id, wallet) -> AirdropAllocation { claimable, claimed }
    • Derived exclusively from frozen snapshot balances — accurate for any wallet,
      independent of current key balances.
  • get_airdrop(airdrop_id) view returning the full record incl. snapshot metadata.
  • Bootstrap: init(admin, creator_keys_contract) (once-only) and set_admin.
  • Errors are append-only ABI (AlreadyClaimed = 6 per the issue).

src/events.rs

  • AirdropCreated — topics (Symbol, key_id); payload: airdrop id, key id,
    funder, reward token, total amount, snapshot supply, snapshot ledger.
  • AirdropClaimed — topics (Symbol, key_id, wallet); payload: airdrop id,
    key id, wallet, amount.

src/test.rs — 16 tests

Mock creator-keys contract + real Stellar Asset Contract reward token. Covers
every acceptance criterion:

Test Acceptance Criterion
test_pro_rata_allocation_computed_from_snapshot_balances Pro-rata allocation computed correctly from snapshot balances (30/70 split)
test_double_claim_reverts_with_already_claimed claim_airdrop reverts on double claim with AlreadyClaimed
test_post_snapshot_balance_changes_do_not_affect_allocation Post-snapshot balance changes (sell-out, buy-in, new holder) do not affect allocation
test_get_airdrop_allocation_tracks_claimed_amount get_airdrop_allocation returns accurate claimable/claimed before and after claim
test_get_airdrop_allocation_returns_zero_for_unknown_wallet Accurate zeros for wallets outside the snapshot
test_airdrop_created_event_emitted_with_correct_data AirdropCreated emitted with correct airdrop data
test_airdrop_claimed_event_emitted_with_correct_data AirdropClaimed emitted with correct amount data
+ 9 more Token escrow/payout transfers, creator vs admin authorization, NotPositiveAmount, NoHolders, HolderLimitExceeded, independent multi-airdrop ids

Root Cargo.toml

  • Added holder-rewards to workspace members.

Acceptance Criteria Verification

  • ✅ Pro-rata allocation computed correctly from snapshot balances — 30/70
    holder split of a 1_000-token airdrop claims 300/700.
  • ✅ claim_airdrop reverts on double claim with AlreadyClaimed — asserted
    via try_claim_airdrop error match.
  • ✅ Post-snapshot balance changes do not affect allocation — balances mutated
    in creator-keys after creation; frozen allocations and claims unchanged.
  • ✅ get_airdrop_allocation returns accurate amounts for any wallet —
    verified before/after claim and for non-holders.
  • ✅ Both events emitted with correct airdrop and amount data — payloads
    decoded and field-checked in dedicated event tests.

CI Pre-Checks

All local CI checks pass:

  • ✅ cargo fmt --all -- --check
  • ✅ cargo clippy --workspace --all-targets -- -D warnings
  • ✅ cargo test --workspace — all tests pass (16 new)

Closes #1096

@Chucks1093
Chucks1093 merged commit d86bda5 into accesslayerorg:main Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build holder rewards contract for airdrop distributions to key holders

3 participants