Distributions
Distributions are how revenue flows from a tokenized asset to its shareholders. When the underlying asset generates income, the pool creator deposits funds into the smart contract and triggers a distribution. Each shareholder receives a proportional share based on their ownership.
DobProtocol supports two distribution models: V1 (push-based) and V2 (pull-based lazy claims).
V1: Push-Based Distribution
The original distribution model. The pool creator triggers a single transaction that loops through all shareholders and sends each one their proportional share.
How V1 Works
1. Creator deposits revenue tokens to the pool contract
2. Creator calls distribute()
3. Contract iterates over all shareholders:
- Shareholder A: 2,000/10,000 shares -> 20% of funds
- Shareholder B: 1,500/10,000 shares -> 15% of funds
- Shareholder C: 500/10,000 shares -> 5% of funds
- ... continues for every shareholder
4. All transfers happen in a single transaction
V1 Limitations
| Limitation | Details |
|---|---|
| Scalability | O(n) gas cost -- each additional shareholder increases transaction cost |
| Max shareholders | ~500 before gas limits are hit on Stellar |
| Failure risk | If any single transfer fails, the entire distribution reverts |
| No partial claims | All-or-nothing execution |
V1 remains available for existing pools and simple use cases with a small number of shareholders.
V2: Pull-Based Lazy Claims
The current recommended model. The pool creator creates a distribution round in a single O(1) transaction. Each shareholder then independently claims their share whenever they choose.
How V2 Works
1. Creator deposits revenue tokens to the pool contract
2. Creator calls create_distribution(token, amount)
- Single O(1) transaction regardless of shareholder count
- Records: total_amount, total_shares, timestamp
- DobProtocol commission deducted at this step
3. Distribution round is now active
4. Each shareholder calls claim(round_id) independently:
- Contract checks shareholder's share balance
- Calculates: claim = (shares / total_shares) * total_amount
- Transfers claim amount to shareholder
- Marks shareholder as claimed for this round
5. Process repeats for each round
V2 Advantages
| Advantage | Details |
|---|---|
| Scalability | O(1) creation -- supports 10,000+ shareholders |
| Gas efficiency | Each claim is a single, cheap transaction |
| Flexibility | Shareholders claim on their own schedule |
| Balance accuracy | Share balance checked at claim time, reflecting any transfers since distribution creation |
| Fault tolerance | One failed claim does not affect others |
Distribution Round Lifecycle
Each distribution creates a round with a defined lifecycle:
create_distribution()
|
v
+-------------------+
| CREATED |
| (waiting for |
| claim window) |
+--------+----------+
|
claim_delay_seconds elapsed
|
v
+-------------------+
| CLAIMABLE |
| (shareholders |
| can claim) |
+--------+----------+
|
round_expiry_seconds elapsed
|
v
+-------------------+
| EXPIRED |
| (no more claims; |
| admin can |
| reclaim funds) |
+-------------------+
Round Data Structure (V2)
pub struct DistributionRound {
pub id: u64, // Unique round identifier
pub token: Address, // Distributed token address
pub total_amount: i128, // Total amount to distribute (post-commission)
pub total_shares: i128, // Total pool shares at creation time
pub is_finalized: bool, // Whether round is complete
pub created_at: u64, // Timestamp of creation
pub claimable_from: u64, // Timestamp when claims open
pub expires_at: u64, // Timestamp when round expires
pub total_claimed: i128, // Running total of claimed amounts
}
Commission Structure
DobProtocol charges commissions on two types of transactions:
| Event | Commission Rate | When Deducted |
|---|---|---|
| Distribution | 0.5% | At distribution creation (deducted from deposited amount) |
| Share Purchase | 1.5% | At time of purchase on the marketplace |
Distribution Commission Example
Creator deposits: $10,000
Commission (0.5%): - $50
Available to claim: $ 9,950
Shareholder with 20% (2,000 shares):
Claim = 20% of $9,950 = $1,990
The commission is deducted once when the distribution round is created, not per claim.
Time-Gating
V2 introduces configurable time-gating to prevent distribution spam and ensure meaningful intervals between rounds.
Configuration
pub struct DistributionConfig {
pub min_interval_seconds: u64, // Minimum time between distributions
// ...
}
| Parameter | Default | Description |
|---|---|---|
min_interval_seconds | 43,200 (12 hours) | Minimum seconds between create_distribution() calls |
Behavior
- If a creator calls
create_distribution()before the interval has elapsed, the transaction fails withDistributionTooSoon - Setting to
0disables time-gating - The interval is measured from the
created_attimestamp of the most recent round
Round 1 created at: 12:00 PM
min_interval_seconds: 43200 (12 hours)
Earliest next round: 12:00 AM (next day)
Attempt at 6:00 PM --> DistributionTooSoon (rejected)
Attempt at 12:01 AM --> Success
Claim Windows
A claim window introduces a delay between distribution creation and when shareholders can start claiming. This gives all shareholders equal notice before claims open.
Configuration
| Parameter | Default | Description |
|---|---|---|
claim_delay_seconds | 3,600 (1 hour) | Seconds after creation before claims are allowed |
Behavior
Distribution created at: 2:00 PM
claim_delay_seconds: 3600 (1 hour)
Claims open at: 3:00 PM
Claim attempt at 2:30 PM --> ClaimsNotOpenYet (rejected)
Claim attempt at 3:01 PM --> Success
This prevents a scenario where informed shareholders claim immediately while others miss the window.
Round Expiry and Reclaim
Distribution rounds expire after a configurable period. Once expired, shareholders can no longer claim, and the admin can reclaim unclaimed funds.
Configuration
| Parameter | Default | Description |
|---|---|---|
round_expiry_seconds | 31,536,000 (1 year) | Seconds after creation before round expires |
Expiry Flow
Round created: Jan 1, 2026
round_expiry_seconds: 31536000 (1 year)
Round expires: Jan 1, 2027
Before expiry:
- Shareholders can claim --> OK
- Admin calls reclaim_expired --> RoundNotExpired (rejected)
After expiry:
- Shareholders call claim --> RoundExpired (rejected)
- Admin calls reclaim_expired --> OK (unclaimed funds returned to admin)
Reclaim
The admin calls reclaim_expired_round(round_id) to recover funds that were not claimed before expiry:
- Only works after
expires_attimestamp has passed - Returns
total_amount - total_claimedto the admin - Fails with
NothingToReclaimif all funds were already claimed - Fails with
RoundNotExpiredif the round has not yet expired
Auto-Scheduling
V2 supports automated distribution scheduling, allowing creators to pre-configure recurring distributions.
Setting a Schedule
pub struct ScheduleConfig {
pub enabled: bool,
pub first_distribution_time: u64, // When the first distribution should happen
pub interval_seconds: u64, // Time between distributions
pub total_distributions: u64, // Number of distributions (0 = unlimited)
pub completed_distributions: u64, // Counter of completed distributions
}
Schedule Operations
| Operation | Description |
|---|---|
set_schedule(first_time, interval, total) | Configure a recurring schedule |
trigger_scheduled_distribution(reward_token) | Execute the next scheduled distribution (anyone can call when due) |
disable_schedule() | Turn off the schedule |
Schedule Example
Set schedule:
first_distribution_time: March 1, 2026 12:00 PM
interval_seconds: 2592000 (30 days)
total_distributions: 12 (one year of monthly distributions)
Timeline:
March 1 -> trigger_scheduled_distribution() -> Round 1 created
March 31 -> trigger_scheduled_distribution() -> Round 2 created
April 30 -> trigger_scheduled_distribution() -> Round 3 created
...
Feb 28 (next year) -> Round 12 created -> ScheduleCompleted
Setting total_distributions to 0 creates an unlimited schedule that continues indefinitely.
Triggering
Anyone can call trigger_scheduled_distribution() when a distribution is due. This allows automation through bots or the DobProtocol backend without requiring the creator to be online.
| Scenario | Result |
|---|---|
| Called before due time | ScheduledDistributionNotDue error |
| Called when due | Distribution round created |
| Called after all distributions completed | ScheduleCompleted error |
| Called when schedule disabled | ScheduleNotEnabled error |
Distribution Events
The following events are emitted by the smart contract during distribution operations:
| Event | Trigger | Data |
|---|---|---|
dist_created | create_distribution() called | Round ID, token, amount, shares |
claimed | Shareholder claims from a round | Round ID, user address, amount |
config_updated | Distribution config changed | New config values |
schedule_set | Schedule configured | Schedule parameters |
sched_off | Schedule disabled | -- |
sched_dist | Scheduled distribution triggered | Round ID |
reclaimed | Admin reclaimed expired round | Round ID, amount recovered |
These events are captured by the Stellar sync script and stored in the eventRecords table.
Database Tables
pool_distribution_dates
Records each distribution event for a pool.
| Column | Type | Description |
|---|---|---|
pool_id | VARCHAR (FK) | Pool address |
dist_date | TIMESTAMP | Distribution date |
amount | DECIMAL | Distribution amount |
token_address | VARCHAR | Distributed token |
round_id | INTEGER | V2 round identifier |
eventRecords (Distribution Events)
| Column | Type | Description |
|---|---|---|
pool_id | VARCHAR | Pool address |
event_type | VARCHAR | distribute, claim, etc. |
data | JSON | Event-specific data |
date | TIMESTAMP | Event timestamp |
chain_id | VARCHAR | Network identifier |
Error Reference
| Error | Code | Meaning |
|---|---|---|
DistributionTooSoon | 25 | Time-gating: interval not met since last distribution |
ClaimsNotOpenYet | 26 | Claim window: delay period has not elapsed |
RoundExpired | 27 | Round has expired; claims no longer accepted |
ScheduleNotConfigured | 28 | No schedule has been set |
ScheduleNotEnabled | 29 | Schedule exists but is disabled |
ScheduleCompleted | 30 | All scheduled distributions have been executed |
ScheduledDistributionNotDue | 31 | Next scheduled distribution is not yet due |
InvalidScheduleConfig | 32 | Schedule parameters are invalid |
RoundNotExpired | 33 | Cannot reclaim -- round has not expired yet |
NothingToReclaim | 34 | All funds in the round have already been claimed |