Skip to main content

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

LimitationDetails
ScalabilityO(n) gas cost -- each additional shareholder increases transaction cost
Max shareholders~500 before gas limits are hit on Stellar
Failure riskIf any single transfer fails, the entire distribution reverts
No partial claimsAll-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

AdvantageDetails
ScalabilityO(1) creation -- supports 10,000+ shareholders
Gas efficiencyEach claim is a single, cheap transaction
FlexibilityShareholders claim on their own schedule
Balance accuracyShare balance checked at claim time, reflecting any transfers since distribution creation
Fault toleranceOne 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:

EventCommission RateWhen Deducted
Distribution0.5%At distribution creation (deducted from deposited amount)
Share Purchase1.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
// ...
}
ParameterDefaultDescription
min_interval_seconds43,200 (12 hours)Minimum seconds between create_distribution() calls

Behavior

  • If a creator calls create_distribution() before the interval has elapsed, the transaction fails with DistributionTooSoon
  • Setting to 0 disables time-gating
  • The interval is measured from the created_at timestamp 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

ParameterDefaultDescription
claim_delay_seconds3,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

ParameterDefaultDescription
round_expiry_seconds31,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_at timestamp has passed
  • Returns total_amount - total_claimed to the admin
  • Fails with NothingToReclaim if all funds were already claimed
  • Fails with RoundNotExpired if 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

OperationDescription
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.

ScenarioResult
Called before due timeScheduledDistributionNotDue error
Called when dueDistribution round created
Called after all distributions completedScheduleCompleted error
Called when schedule disabledScheduleNotEnabled error

Distribution Events

The following events are emitted by the smart contract during distribution operations:

EventTriggerData
dist_createdcreate_distribution() calledRound ID, token, amount, shares
claimedShareholder claims from a roundRound ID, user address, amount
config_updatedDistribution config changedNew config values
schedule_setSchedule configuredSchedule parameters
sched_offSchedule disabled--
sched_distScheduled distribution triggeredRound ID
reclaimedAdmin reclaimed expired roundRound 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.

ColumnTypeDescription
pool_idVARCHAR (FK)Pool address
dist_dateTIMESTAMPDistribution date
amountDECIMALDistribution amount
token_addressVARCHARDistributed token
round_idINTEGERV2 round identifier

eventRecords (Distribution Events)

ColumnTypeDescription
pool_idVARCHARPool address
event_typeVARCHARdistribute, claim, etc.
dataJSONEvent-specific data
dateTIMESTAMPEvent timestamp
chain_idVARCHARNetwork identifier

Error Reference

ErrorCodeMeaning
DistributionTooSoon25Time-gating: interval not met since last distribution
ClaimsNotOpenYet26Claim window: delay period has not elapsed
RoundExpired27Round has expired; claims no longer accepted
ScheduleNotConfigured28No schedule has been set
ScheduleNotEnabled29Schedule exists but is disabled
ScheduleCompleted30All scheduled distributions have been executed
ScheduledDistributionNotDue31Next scheduled distribution is not yet due
InvalidScheduleConfig32Schedule parameters are invalid
RoundNotExpired33Cannot reclaim -- round has not expired yet
NothingToReclaim34All funds in the round have already been claimed