| Crates.io | nvana_rev_epoch |
| lib.rs | nvana_rev_epoch |
| version | 0.1.0 |
| created_at | 2025-12-23 00:09:55.56276+00 |
| updated_at | 2025-12-23 00:09:55.56276+00 |
| description | High-precision revenue distribution library with time-decay bonus epochs |
| homepage | |
| repository | |
| max_upload_size | |
| id | 2000499 |
| size | 119,227 |
High-precision revenue distribution with time-decay bonuses.
A library for distributing revenue to participants using time-weighted bonuses. Early participants get higher multipliers on their shares, creating a natural incentive for timely participation.
Use cases:
use nvana_rev_epoch::*;
// 1. Implement the traits for your storage backend
struct MyConfig { /* your storage */ }
impl RevEpochConfig<MyId> for MyConfig { /* ... */ }
// 2. Use the provided functions
fn distribute_revenue() -> Result<()> {
// Create an epoch that lasts 24 hours with 50% max bonus
create_new_epoch(&mut config, current_time)?;
// User adds shares - gets time-decay bonus
add_shares(&config, &mut distributor, &mut user, 1000, current_time)?;
// Accumulate revenue
distributor.increase_pot_by(50_000)?;
// After epoch ends, settle and distribute
settle_epoch(&config, &mut distributor, end_time)?;
settle_epoch_for_user(&distributor, &mut user_local, &mut user_global)?;
// User withdraws
let amount = user_collect_rev(&mut user_global);
Ok(())
}
The bonus decays linearly from max at epoch start to zero at epoch end:
time_remaining = epoch_duration - (now - epoch_start)
bonus_mbps = (max_bonus_mbps × time_remaining) / epoch_duration
effective_shares = raw_shares × (1 + bonus_mbps / 10_000_000)
Example with 50% max bonus (5,000,000 mbps):
After settling:
revenue_per_share = total_pot / total_effective_shares (WAD-scaled)
user_revenue = user_shares × revenue_per_share (floored to u64)
All intermediate calculations use WAD scaling (×10^18) to preserve precision.
Four traits define the system:
| Trait | Purpose | Lifetime |
|---|---|---|
RevEpochConfig |
Global configuration singleton | Permanent |
RevEpochDistributor |
Per-epoch accounting ledger | Persists after epoch ends |
RevEpochUserGlobalAccount |
Per-user revenue accumulator | Permanent |
RevEpochUserLocalAccount |
Per-user per-epoch shares | Ephemeral (deleted after claim) |
RevEpochConfig per tenant - Your application has one global config that defines epoch duration and max bonusRevEpochDistributor per config - Each epoch gets its own distributor. You can run epochs sequentially or overlappingRevEpochUserGlobalAccount per (config, user) pair - Each user has exactly one global account where settled revenue accumulatesRevEpochUserLocalAccount per user - Each time a user adds shares to an epoch, they get a local account for that epochConfig: Created once when you deploy your application. Contains settings like epoch_duration_seconds and max_bonus_mbps.
Distributor: Created by calling create_new_epoch(). This operation can be permissionless - anyone can start a new epoch once the previous one ends. Each distributor tracks:
Global User Account: Created before a user's first interaction. Some applications create these eagerly (during user onboarding), others create them lazily (first time user adds shares).
Local User Account: Created automatically when a user calls add_shares() for an epoch. Stores the user's raw shares and effective shares (after bonus) for that specific epoch.
create_new_epoch() to create a new RevEpochDistributoradd_shares() which:
RevEpochUserLocalAccount if neededdistributor.increase_pot_by() as revenue comes inepoch_duration_seconds, call settle_epoch() to:
revenue_per_sharesettle_epoch_for_user() to:
user_collect_rev() to withdraw from their global accountConfig (1)
├── Distributor (N) - one per epoch
│ └── LocalUserAccount (N) - one per user per epoch
└── GlobalUserAccount (N) - one per user
Each distributor references its config. Each local user account references both its distributor and its global user account. The library validates these relationships through typed IDs.
// Epoch management
pub fn create_new_epoch(config: &mut C, now: u64) -> Result<()>
// User participation
pub fn add_shares(
config: &C,
distributor: &mut D,
user: &mut U,
raw_shares: u64,
now: u64
) -> Result<()>
// Settlement
pub fn settle_epoch(config: &C, distributor: &mut D, now: u64) -> Result<()>
pub fn settle_epoch_for_user(
distributor: &D,
user_local: &mut UL,
user_global: &mut UG
) -> Result<u64>
// Withdrawal
pub fn user_collect_rev(user_global: &mut UG) -> u64
pub type Seconds = u64; // Unix timestamp
pub type Mbps = u32; // Micro basis points (10M = 100%)
pub const MBP_100: u32 = 10_000_000; // Constant for 100%
# Run all tests
cargo test
# Run with optimizations (catches different bugs)
cargo test --release
# Check for breaking changes (requires published version)
cargo install cargo-semver-checks
cargo semver-checks
| Feature | Implementation |
|---|---|
| Memory safety | #![deny(unsafe_code)] |
| Overflow protection | All arithmetic uses checked_*() operations |
| Precision | WAD scaling (18 decimals) with explicit flooring |
| Type safety | Strong typing for IDs, prevents mixing entities |
All functions return Result<T, RevEpochError>. Errors are:
Copy (cheap to pass around)PartialEq (easy to test)Common errors:
EpochNotFinished - Tried to settle too earlyEpochAlreadyFinished - Tried to add shares after settlementTimestampBeforeEpochStart - Invalid timestampSharesOverflow - Calculation would overflow u128| Operation | Complexity | Allocations |
|---|---|---|
create_new_epoch |
O(1) | 0 |
add_shares |
O(1) | 0 |
settle_epoch |
O(1) | 0 |
settle_epoch_for_user |
O(1) | 0 |
user_collect_rev |
O(1) | 0 |
All operations are constant-time with zero heap allocations.
Q: What happens if the pot is zero? A: Users get zero revenue but their shares are tracked. You can add revenue later.
Q: What if no one adds shares? A: Settlement will succeed with revenue_per_share = 0. The pot stays locked (you can't reclaim it).
Q: Are there any decimal rounding concerns? A: All division happens last and floors down (defensive). WAD scaling preserves 18 decimal places throughout.
MIT