This repository packages the "honorary LP" module we built for Star's DAMM v2 bounty. It lets the Star program hold a dedicated LP position that accrues fees exclusively in the quote mint and runs a permissionless, once-per-day distribution crank that pays investors pro-rata to their still-locked Streamflow allocations while routing the remainder to the creator.
The work is split into a core contract under programs/honorary-lp. All logic is Anchor-compatible and no unsafe blocks are used. End-to-end tests exercise the full initialize โ claim โ distribute flow against the real cp-amm program in a local validator.
Star's fundraising platform needs to:
- Accrue trading fees from CP-AMM liquidity pools without exposing treasury funds to impermanent loss
- Distribute fees fairly to investors based on their locked Streamflow vesting allocations
- Maintain creator control over fee distribution parameters and timing
- Ensure permissionless operation so distributions can't be blocked by any single party
- Guarantee quote-only economics to protect against base token price volatility
The Honorary LP Module provides:
- PDA-owned quote-only position that accrues fees exclusively in the quote mint
- Real-time Streamflow integration for pro-rata investor payouts based on locked balances
- Permissionless 24h distribution crank with pagination for scalability
- Deterministic PDA architecture ensuring no signer risk or external dependencies
- Comprehensive security invariants including quote-only enforcement and dust carry-forward
graph TB
subgraph "Star Honorary LP Module (Anchor Program)"
A[Initialize Honorary Position] --> B[Policy PDA<br/>Immutable Config]
A --> C[Progress PDA<br/>Daily State]
A --> D[Authority PDA<br/>Signer for Transfers]
A --> E[Treasury ATA<br/>Quote Fee Accumulation]
A --> F[Position NFT<br/>CP-AMM LP Slot]
G[Distribute Fees Crank<br/>Permissionless 24h] --> H[Claim CP-AMM Fees<br/>Quote-Only Validation]
H --> I[Calculate Pro-Rata Shares<br/>Streamflow Weighted]
I --> J[Investor Payouts<br/>Paginated Transfers]
J --> K[Creator Remainder<br/>Final Distribution]
B --> G
C --> G
D --> G
E --> G
F --> G
end
subgraph "External Dependencies"
L[CP-AMM Program<br/>Fee Claiming]
M[Streamflow Program<br/>Locked Balance Queries]
N[SPL Token Program<br/>Transfers & ATAs]
end
H --> L
I --> M
J --> N
K --> N
style A fill:#e1f5fe
style G fill:#fff3e0
style L fill:#f3e5f5
style M fill:#f3e5f5
sequenceDiagram
participant Creator
participant Program
participant CPAMM as CP-AMM Program
participant Investors
participant Streamflow
rect rgb(240, 248, 255)
Creator->>Program: Initialize Honorary Position
Note over Creator,Program: Policy: 90% to investors,<br/>Daily cap: 1M USDC,<br/>Min payout: 1K USDC,<br/>Y0 total: 100 USDC
Program->>CPAMM: Create PDA-owned Position NFT
Program-->>Creator: HonoraryPositionInitialized Event
end
loop Daily Distribution Crank (24h intervals)
rect rgb(255, 248, 220)
Investors->>Program: Distribute Fees (Page 0/2)
Note over Investors,Program: total_investors=2,<br/>total_locked=100 USDC
Program->>CPAMM: Claim Position Fees
CPAMM-->>Program: 1,000,000 USDC (Quote Only)
Program->>Streamflow: Query Locked Balances
Streamflow-->>Program: Investor A: 60 USDC<br/>Investor B: 40 USDC
Program->>Program: Calculate Distribution
Note over Program: Eligible share: 90%<br/>Investor pool: 900K USDC<br/>Creator remainder: 100K USDC
Program->>Investors: Transfer to Investor A: 540K USDC
Program-->>Investors: InvestorPayoutPage Event<br/>(page_start=0, processed=1)
end
rect rgb(255, 248, 220)
Investors->>Program: Distribute Fees (Page 1/2)
Note over Investors,Program: page_start=1
Program->>Investors: Transfer to Investor B: 360K USDC
Program->>Creator: Transfer Creator Remainder: 100K USDC
Program-->>Creator: CreatorPayoutDayClosed Event<br/>(investor_total=900K, creator=100K)
end
end
Pool: SOL/USDC with Star as creator
- Honorary Position: Price range 100-200 ticks, accrues ~50K USDC daily in fees
- Investors: 5 investors with Streamflow streams totaling 1M USDC initial allocation
- Day 1 Distribution:
- Total locked: 800K USDC (20% unlocked)
- Eligible investor share: 80% (min of 90% policy, 80% locked ratio)
- Investor pool: 40K USDC โ Pro-rata payouts based on locked balances
- Creator remainder: 10K USDC
- Day 30 Distribution:
- Total locked: 200K USDC (80% unlocked)
- Eligible investor share: 20% โ Smaller investor payouts, larger creator share
- Rust 1.85.0, Anchor 0.31.0, Solana 2.1.0
- Node.js 18+ (for Bankrun tests)
- pnpm or yarn
# Install dependencies
pnpm install
# Build programs with local features
anchor build -- --features local
# Run end-to-end integration tests
yarn test:full:bankrun- โ
Full Flow Test (
CreateInitializeClaimDistribute.test.ts): Pool creation โ Position initialization โ Fee accrual simulation โ Paginated distribution - โ Quote-Only Enforcement: Aborts if CP-AMM claims return base tokens
- โ Pagination Idempotence: Cursor tracking prevents double-payments
- โ 24h Gate Enforcement: Rejects distribution attempts before cooldown
- โ Pro-Rata Math: Validates investor payouts match locked proportions
- โ Dust Carry-Forward: Tracks and carries over sub-minimum payouts
=== STARTING HONORARY FEE MODULE TEST ===
Test Flow: Create Pool โ Initialize Policy โ Claim Fees โ Distribute Fees
๐ Payer public key: [REDACTED]
๐ Admin public key: [REDACTED]
๐ฐ Created test accounts:
- Creator: [REDACTED]
- Investor A: [REDACTED]
- Investor B: [REDACTED]
๐ฆ Creating tokens and minting initial supply...
โ
Tokens created and minted to creator
๐๏ธ Setting up CP-AMM pool configuration...
โ
Pool initialized: [POOL_PUBKEY]
๐ฏ Setting up Honorary Fee Module...
Configuration:
- Investor Share: 90%
- Daily Cap: 1000000 tokens
- Min Payout: 1000 tokens
- Total Locked: 100 tokens
๐ Initializing Honorary Policy...
โ
Policy initialized successfully
โ
Policy validation passed
๐ฐ Setting up fee claiming infrastructure...
๐งพ First fee claim (setup - should claim 0)...
๐ธ Minting test tokens to simulate trading fees...
๐ Performing swaps to accrue fees on honorary position...
โ
Fee accrual simulation complete
๐ Starting distribution crank...
๐ Page 0/2: Processing first investor...
โ
Investor A payout: 540000 tokens
๐ Page 1/2: Processing second investor...
โ
Investor B payout: 360000 tokens
๐ฐ Creator remainder: 100000 tokens
โ
All distributions completed successfully
โ
24h gate test passed
- Import the Program: Add
honorary-lpto your Anchor workspace - Derive PDAs: Use/Implement SDK helpers to calculate deterministic addresses
- Initialize Once: Call
initialize_policywith your distribution parameters - Crank Daily: Anyone can call
distribute_feesonce per 24h period - Monitor Events: Subscribe to distribution events for transparency
# In your Anchor.toml
[[programs.mainnet]]
honorary-lp = "YOUR_DEPLOYED_PROGRAM_ID"
# In your Cargo.toml
[dependencies]
honorary-lp = { path = "../honorary-lp", features = ["cpi"] }// In your program
use honorary_lp::cpi::initialize_policy;
// During pool creation or upgrade
let policy_params = InitializePolicyParams {
investor_fee_share_bps: 9000, // 90%
daily_cap: 1_000_000, // 1M tokens
min_payout: 1_000, // 1K tokens minimum
y0_total_allocation: total_investment_amount,
tick_lower: price_range.min,
tick_upper: price_range.max,
};
// Call initialize_policy instruction
honorary_lp::cpi::initialize_policy(
ctx,
policy_params,
pool_key,
quote_mint,
)?;// Off-chain keeper bot
const crankDistribution = async (poolId: PublicKey) => {
// Get current progress state
const progress = await getProgressPda(poolId);
// Check if 24h has passed
if (!progress.canDistribute()) return;
// Get investor list for this pool
const investors = await getPoolInvestors(poolId);
// Paginate through investors
for (let page = 0; page < Math.ceil(investors.length / MAX_PER_PAGE); page++) {
const pageInvestors = investors.slice(page * MAX_PER_PAGE, (page + 1) * MAX_PER_PAGE);
await program.methods.distributeFees(
new BN(page), // page_start
new BN(investors.length), // total_investors
totalLockedAmount, // from Streamflow queries
)
.accounts({
// ... account mappings
remainingAccounts: pageInvestors.flatMap(inv => [inv.streamAccount, inv.ata])
})
.rpc();
}
};- Creator Details: Wallet pubkey and quote ATA for remainder payouts
- Streamflow Config: Program ID for locked balance validation
- Policy Parameters:
investor_fee_share_bps: Max percentage to investors (0-10000)daily_cap: Maximum tokens distributable per daymin_payout: Minimum payout per investor (dust threshold)y0_total_allocation: Initial total investment amount
- Pool Details: CP-AMM pool pubkey, position NFT, token mints
- Validation: Confirm quote mint matches pool's quote token
- PDA Derivation: Calculate all deterministic addresses from pool ID
- Position Creation: Initialize empty CP-AMM position owned by PDA
- Policy Storage: Persist distribution parameters in Policy PDA
- Event Emission:
HonoraryPositionInitializedwith configuration
First Page Processing:
- 24h Gate Check: Enforce
now >= last_distribution + 86400 - Fee Claim: Call CP-AMM to claim position fees (quote-only validation)
- Target Calculation: Combine claimed fees + carried dust, apply daily cap
- Share Allocation: Calculate investor vs creator portions
Investor Payout Pages:
- Streamflow Queries: Fetch real-time locked balances for each investor
- Pro-Rata Math:
payout_i = floor(investor_pool ร locked_i รท locked_total) - Dust Filtering: Skip payouts below
min_payoutthreshold - Transfer Execution: CPI to SPL Token for secure transfers
Final Page Closing:
- Remainder Routing: Transfer unallocated investor pool to creator
- State Reset: Update progress PDA for next day
- Event Emission:
CreatorPayoutDayClosedwith final totals
| Account | Seeds | Purpose |
|---|---|---|
policy |
[b"policy", pool] |
Immutable configuration and invariants |
progress |
[b"progress", pool] |
Daily distribution state and cursor |
authority_pda |
[b"honorary-authority", pool] |
Signs all token transfers and CPI calls |
quote_treasury |
[b"treasury", pool, quote_mint] |
Accumulates claimed quote fees |
position |
CP-AMM derived | PDA-owned LP position NFT |
temp_a/b_accounts |
Associated with authority | Temporary holders for CP-AMM claim outputs |
remaining_accounts |
Streamflow + Investor ATA pairs | Per-investor data for distribution |
Share Calculation:
eligible_investor_share_bps = min(
policy.investor_fee_share_bps,
floor(locked_total / y0_total_allocation ร 10000)
)
investor_pool_target = floor(total_to_distribute ร eligible_share_bps / 10000)
creator_remainder_target = total_to_distribute - investor_pool_target
Per-Investor Payout:
payout_i = floor(investor_pool_target ร locked_i / locked_total)
if payout_i < min_payout: skip (add to dust carry)
Edge Cases:
locked_total = 0: All fees go to creator- Base fees detected: Transaction aborts with
BaseFeesNotAllowed - Insufficient treasury:
InsufficientTreasuryBalanceerror
| Event | Trigger | Key Data |
|---|---|---|
HonoraryPositionInitialized |
Initialization | pool, policy, quote_mint, investor_share_bps |
QuoteFeesClaimed |
Fee claim success | pool, amount_claimed |
InvestorPayoutPage |
Per page completion | page_start, investors_processed, total_payout |
CreatorPayoutDayClosed |
Final page | investor_total, creator_amount, dust_carry_forward |
| Error Code | Description | Mitigation |
|---|---|---|
DistributionTooSoon |
24h cooldown not elapsed | Wait for gate to open |
InvalidPaginationState |
Cursor mismatch or invalid page | Check pagination logic |
InvalidStreamAccount |
Malformed Streamflow data | Validate account ownership |
BaseFeesNotAllowed |
CP-AMM returned base tokens | Position configuration error |
InsufficientTreasuryBalance |
Treasury underfunded | Check fee accrual |
- Rust: 1.85.0 (via
rust-toolchain.toml) - Anchor: 0.31.0
- Solana: 2.1.0
- Node.js: 18+ (Bankrun requirement)
# Install dependencies
pnpm install
# Build with local features
anchor build -- --features local
# Run tests
yarn test:full:bankrun
# Lint and format
cargo fmt --all
cargo clippy -p honorary-lp -- -D warnings- Modify Code: Edit Rust/TS files in
programs/andtests/ - Build:
anchor build -- --features local - Test:
yarn test:full:bankrun - Iterate: Fix any compilation or test failures
# Build for mainnet
anchor build
# Deploy program
anchor deploy --provider.cluster mainnet
# Update IDL
anchor idl init --filepath target/idl/honorary_lp.json [PROGRAM_ID]- Program ID: Update all references after deployment
- Keeper Bot: Set up automated daily cranks (24h intervals)
- Monitoring: Subscribe to events for distribution tracking
- Upgrades: Use Anchor's upgradeable program pattern if needed
- โ No External Signers: All operations use PDAs
- โ Deterministic Addresses: No seed guessing required
- โ Quote-Only Enforcement: Protects against IL exposure
- โ Pagination Safety: Idempotent and resumable operations
- โ Time-Locked Distributions: 24h minimum intervals
- Multi-Chain Support: Adapt for other AMM protocols
- Dynamic Fee Schedules: Time-based investor share adjustments
- Advanced Streamflow Parsing: Support for complex vesting schedules
- Keeper Incentives: Built-in rewards for crank operators
- Governance Integration: On-chain parameter updates
- Enhanced Monitoring: Real-time dashboard for distributions
This submission completely fulfills Star's DAMM v2 Honorary Quote-Only Fee Position + 24h Distribution Crank bounty:
- Deterministic PDA-owned CP-AMM position
- Quote-only fee accrual with strict validation
- Configurable price ranges and policy parameters
- Comprehensive initialization with event emission
- Permissionless daily distribution with 24h gating
- Real-time Streamflow locked balance integration
- Pro-rata investor payouts with pagination
- Creator remainder routing and dust carry-forward
- Full security invariants and error handling
- Quote-Only Guarantee: Aborts on base token fees
- Streamflow Integration: Parses real locked balances
- Pagination: Handles large investor sets efficiently
- 24h Gate: Enforces minimum distribution intervals
- Pro-Rata Math: Accurate weighted distributions
- Creator Control: Configurable policy parameters
- End-to-End Tests: Full integration test suite
- Documentation: Complete integration guide
The module is production-ready and includes:
- Complete Rust implementation with no unsafe code
- Comprehensive TypeScript test suite
- Full integration documentation
- SDK helpers for PDA derivation
- Event-based monitoring capabilities
Contact: Ready for deployment support and any final integration questions!