This document describes the revenue split mechanism in the QuickLendX protocol, which allows administrators to configure how platform fees are distributed among different parties.
The revenue split system enables flexible distribution of collected platform fees between:
- Treasury: The protocol's operational treasury
- Developers: Developer funding pool for ongoing development
- Platform: Platform reserves for growth and maintenance
Revenue distribution is configured using basis points (bps), where 10,000 bps = 100%. The sum of all shares must equal exactly 10,000 bps.
Admin-only function to set up the revenue split configuration.
pub fn configure_revenue_distribution(
env: Env,
admin: Address,
treasury_address: Address,
treasury_share_bps: u32, // e.g., 6000 = 60%
developer_share_bps: u32, // e.g., 2000 = 20%
platform_share_bps: u32, // e.g., 2000 = 20%
auto_distribution: bool,
min_distribution_amount: i128,
) -> Result<(), QuickLendXError>Parameters:
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Must match the stored admin address |
treasury_address |
Address |
Address to receive treasury share |
treasury_share_bps |
u32 |
Treasury share in basis points |
developer_share_bps |
u32 |
Developer share in basis points |
platform_share_bps |
u32 |
Platform share in basis points |
auto_distribution |
bool |
Enable automatic distribution on threshold |
min_distribution_amount |
i128 |
Minimum amount required for distribution |
Validation:
- Requires admin authorization
treasury_share_bps + developer_share_bps + platform_share_bpsmust equal10,000
Errors:
NotAdmin: Caller is not the adminInvalidAmount: Shares don't sum to 10,000 bps
Query the current revenue split configuration.
pub fn get_revenue_split_config(env: Env) -> Result<RevenueConfig, QuickLendXError>Returns: RevenueConfig struct containing all configuration parameters.
Errors:
StorageKeyNotFound: Configuration not yet set
Execute revenue distribution for a specific period.
pub fn distribute_revenue(
env: Env,
admin: Address,
period: u64,
) -> Result<(i128, i128, i128), QuickLendXError>Parameters:
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Admin address (requires authorization) |
period |
u64 |
Period identifier (calculated as timestamp / 2,592,000) |
Returns: Tuple of (treasury_amount, developer_amount, platform_amount)
Idempotency (per settlement): If the period’s revenue record exists and pending_distribution == 0 (typically after a successful distribution), the call returns OperationNotAllowed (OP_NA). This blocks duplicate no-op distributions when min_distribution_amount is zero and avoids duplicate audit events. New fee collection in the same period increases pending_distribution again and a later call may succeed.
Treasury routing alignment: If the platform fee treasury is configured (configure_treasury) and treasury_share_bps > 0, RevenueConfig.treasury_address must match that treasury. Otherwise distribution returns InvalidFeeConfiguration (FEE_CFG).
Distribution Logic:
- Treasury amount =
pending * treasury_bps / 10,000 - Developer amount =
pending * developer_bps / 10,000 - Platform amount =
pending - treasury - developer(receives any rounding remainder)
Record collected fees for later distribution.
pub fn collect_transaction_fees(
env: Env,
user: Address,
fees_by_type: Map<FeeType, i128>,
total_amount: i128,
) -> Result<(), QuickLendXError>pub struct RevenueConfig {
pub treasury_address: Address,
pub treasury_share_bps: u32,
pub developer_share_bps: u32,
pub platform_share_bps: u32,
pub auto_distribution: bool,
pub min_distribution_amount: i128,
}pub struct RevenueData {
pub period: u64,
pub total_collected: i128,
pub fees_by_type: Map<FeeType, i128>,
pub total_distributed: i128,
pub pending_distribution: i128,
pub transaction_count: u32,
}// Configure revenue split: 60% Treasury, 20% Developer, 20% Platform
client.configure_revenue_distribution(
&admin,
&treasury_address,
&6000, // 60% to treasury
&2000, // 20% to developers
&2000, // 20% to platform
&false, // manual distribution
&1000, // minimum 1000 units to distribute
);// Get current period
let current_period = env.ledger().timestamp() / 2_592_000;
// Distribute revenue and get amounts
let (treasury, developer, platform) = client.distribute_revenue(
&admin,
¤t_period,
);// Get current configuration
let config = client.get_revenue_split_config();
println!("Treasury share: {}%", config.treasury_share_bps / 100);- Admin-Only Configuration: Only the verified admin can modify revenue split settings
- Validation: Share percentages must sum to exactly 100% (10,000 bps)
- Minimum Threshold: Prevents dust distributions that waste gas
- Remainder Handling: Platform receives rounding remainder to prevent fund loss
- Period-Based Tracking: Revenue is tracked per period to enable auditing
- Settlement idempotency: No second distribution while
pending_distribution == 0for that period - Consistent treasury target: When a platform treasury is set and the split sends a non-zero share to treasury, the configured revenue treasury address must match it
Routing fees to an incorrect address is an irreversible on-chain action. The protocol therefore requires a two-step confirmation before any treasury or fee-recipient address change takes effect.
Admin New Treasury Address
| |
|-- initiate_treasury_rotation --> | (pending rotation stored, 7-day window)
| |
| <-- confirm_treasury_rotation -- (new address proves ownership)
| |
| (rotation committed, old address replaced)
If the admin changes their mind, cancel_treasury_rotation can be called at any time
before the new address confirms.
pub fn initiate_treasury_rotation(
env: Env,
new_address: Address,
) -> Result<RecipientRotationRequest, QuickLendXError>- Requires admin authorization.
- Rejects if a rotation is already pending (
RotationAlreadyPending). - Rejects if
new_addressequals the current treasury (InvalidAddress). - Stores a
RecipientRotationRequestwith a 7-day (604,800 s) confirmation deadline. - Emits
rot_initevent.
pub fn confirm_treasury_rotation(
env: Env,
new_address: Address,
) -> Result<Address, QuickLendXError>- Must be called by the
new_addressfrom the pending request. - Rejects if no rotation is pending (
RotationNotFound). - Rejects if called by a different address (
Unauthorized). - Rejects after the 7-day deadline (
RotationExpired), and clears the pending state. - On success: writes
new_addressas the treasury, clears pending request, emitsrot_conf.
pub fn cancel_treasury_rotation(env: Env) -> Result<(), QuickLendXError>- Admin-only.
- Rejects if nothing is pending (
RotationNotFound). - Clears pending request without modifying the current treasury. Emits
rot_canc.
pub fn get_pending_treasury_rotation(env: Env) -> Option<RecipientRotationRequest>Read-only query returning the pending rotation, if any.
pub struct RecipientRotationRequest {
pub new_address: Address,
pub initiated_by: Address,
pub initiated_at: u64,
pub confirmation_deadline: u64,
}| Error | Code | Meaning |
|---|---|---|
RotationAlreadyPending |
1853 | A rotation is already waiting for confirmation |
RotationNotFound |
1854 | No pending rotation to confirm or cancel |
RotationExpired |
1855 | Confirmation deadline has passed |
| Topic | Fields | Trigger |
|---|---|---|
rot_init |
(new_address, initiated_by, deadline, timestamp) |
Rotation initiated |
rot_conf |
(old_address, new_address, confirmed_at) |
Rotation confirmed |
rot_canc |
(cancelled_address, cancelled_by, timestamp) |
Rotation cancelled |
- Ownership proof: The new address must sign a transaction to confirm, preventing misrouting to an address nobody controls.
- Time-bounded: Unconfirmed requests expire after 7 days, preventing stale rotations.
- Single-pending: Only one rotation at a time prevents confusion about the intended destination.
- Idempotent cancel: Cancellation is always safe; it never silently overwrites state.
pub fn get_fee_analytics(env: Env, period: u64) -> Result<FeeAnalytics, QuickLendXError>Returns analytics including:
total_fees: Total fees collected in the periodaverage_fee_rate: Average fee per transactiontotal_transactions: Number of fee-generating transactionsfee_efficiency_score: Distribution efficiency (0-100)