Split Payment API Reference
The Split Payment module enables automatic payment distribution based on predefined percentage allocations. Perfect for revenue sharing, referral programs, and multi-party transactions.
Overview
Module: dolphinpay::split
File: sources/tokens/split.move
Key capabilities:
- Percentage-based payment splitting using basis points (BPS)
- Multiple recipient support with custom allocations
- Minimum amount thresholds per recipient
- Reusable split configurations
- Automatic calculation and distribution
Data Structures
SplitConfig
Configuration defining how payments should be split.
public struct SplitConfig has key, store {
id: UID,
splits: vector<Split>,
total_bps: u64,
is_active: bool,
creator: address,
}
Fields:
id- Unique identifiersplits- Vector of split rulestotal_bps- Total basis points (must equal 10000 = 100%)is_active- Configuration active statuscreator- Creator address (only one who can modify)
Split
Individual split rule defining recipient and allocation.
public struct Split has store, copy, drop {
recipient: address,
bps: u64,
description: String,
min_amount: u64,
}
Fields:
recipient- Recipient addressbps- Basis points (0-10000, where 10000 = 100%)description- Description of this splitmin_amount- Minimum amount to send (skip if below)
Constants
const TOTAL_BPS: u64 = 10000; // 100% = 10000 basis points
Basis Points Reference:
- 1 BPS = 0.01%
- 100 BPS = 1%
- 1000 BPS = 10%
- 5000 BPS = 50%
- 10000 BPS = 100%
Core Functions
create_split_config
Create a new split configuration.
public fun create_split_config(
splits: vector<Split>,
ctx: &mut TxContext,
): SplitConfig
Parameters:
splits- Vector of split rulesctx- Transaction context
Returns: SplitConfig object
Aborts:
E_INVALID_SPLIT_CONFIG(500) - Empty splits vectorE_TOTAL_BPS_MISMATCH(501) - Total BPS ≠ 10000
Validation:
- Splits vector must not be empty
- Sum of all
bpsvalues must equal exactly 10000
Example:
use dolphinpay::split;
use std::string;
let mut splits = vector::empty<Split>();
// 50% to primary recipient
vector::push_back(&mut splits, split::create_split(
@0xALICE,
5000, // 50%
string::utf8(b"Primary revenue share"),
1_000_000 // Min 0.001 SUI
));
// 30% to secondary recipient
vector::push_back(&mut splits, split::create_split(
@0xBOB,
3000, // 30%
string::utf8(b"Secondary revenue share"),
500_000 // Min 0.0005 SUI
));
// 20% to platform
vector::push_back(&mut splits, split::create_split(
@0xPLATFORM,
2000, // 20%
string::utf8(b"Platform fee"),
100_000 // Min 0.0001 SUI
));
let config = split::create_split_config(splits, ctx);
execute_split_payment
Execute a split payment using a configuration.
public entry fun execute_split_payment<T>(
config: &SplitConfig,
mut coin: Coin<T>,
ctx: &mut TxContext,
)
Parameters:
config- Split configuration referencecoin- Coin to split and distributectx- Transaction context
Behavior:
- Calculates each split amount based on BPS
- Sends to each recipient if amount ≥ min_amount
- Returns any remaining funds to sender
- Destroys coin if fully distributed
Aborts:
E_INVALID_SPLIT_CONFIG(500) - Config is not active
Example:
// Execute split with 10 SUI
let coin = coin::mint_for_testing<SUI>(10_000_000_000, ctx);
split::execute_split_payment(&config, coin, ctx);
// Alice receives: 5 SUI (50%)
// Bob receives: 3 SUI (30%)
// Platform receives: 2 SUI (20%)
create_split
Create an individual split rule.
public fun create_split(
recipient: address,
bps: u64,
description: String,
min_amount: u64,
): Split
Parameters:
recipient- Recipient addressbps- Basis points allocation (0-10000)description- Human-readable descriptionmin_amount- Minimum amount threshold
Returns: Split struct
update_split_config
Update an existing split configuration.
public fun update_split_config(
config: &mut SplitConfig,
splits: vector<Split>,
ctx: &mut TxContext,
)
Parameters:
config- Mutable reference to configsplits- New vector of split rulesctx- Transaction context
Aborts:
E_NOT_AUTHORIZED(999) - Caller is not creatorE_INVALID_SPLIT_CONFIG(500) - Empty splitsE_TOTAL_BPS_MISMATCH(501) - Total BPS ≠ 10000
toggle_split_status
Enable or disable a split configuration.
public entry fun toggle_split_status(
config: &mut SplitConfig,
is_active: bool,
ctx: &mut TxContext,
)
Parameters:
config- Mutable reference to configis_active- New active statusctx- Transaction context
Aborts:
E_NOT_AUTHORIZED(999) - Caller is not creator
Query Functions
get_creator
Get the configuration creator address.
public fun get_creator(config: &SplitConfig): address
get_total_bps
Get total basis points (should always be 10000).
public fun get_total_bps(config: &SplitConfig): u64
is_active
Check if configuration is active.
public fun is_active(config: &SplitConfig): bool
get_splits_count
Get number of splits in the configuration.
public fun get_splits_count(config: &SplitConfig): u64
get_split
Get split by index.
public fun get_split(config: &SplitConfig, index: u64): &Split
Aborts: If index is out of bounds
get_split_recipient
Get recipient address from a split.
public fun get_split_recipient(split: &Split): address
get_split_bps
Get basis points from a split.
public fun get_split_bps(split: &Split): u64
get_split_description
Get description from a split.
public fun get_split_description(split: &Split): String
get_split_min_amount
Get minimum amount from a split.
public fun get_split_min_amount(split: &Split): u64
Error Codes
| Code | Constant | Description |
|---|---|---|
| 500 | E_INVALID_SPLIT_CONFIG | Invalid split configuration (empty or inactive) |
| 501 | E_TOTAL_BPS_MISMATCH | Total basis points don't equal 10000 |
| 999 | E_NOT_AUTHORIZED | Caller is not the creator |
Usage Examples
Revenue Sharing (50/30/20)
use dolphinpay::split;
use std::string;
// Create 50/30/20 split for revenue sharing
let mut splits = vector::empty();
vector::push_back(&mut splits, split::create_split(
@founder,
5000, // 50%
string::utf8(b"Founder share"),
0 // No minimum
));
vector::push_back(&mut splits, split::create_split(
@investor,
3000, // 30%
string::utf8(b"Investor share"),
0
));
vector::push_back(&mut splits, split::create_split(
@treasury,
2000, // 20%
string::utf8(b"Treasury allocation"),
0
));
let config = split::create_split_config(splits, ctx);
// Later, split revenue
let revenue = coin::mint_for_testing<SUI>(100_000_000_000, ctx); // 100 SUI
split::execute_split_payment(&config, revenue, ctx);
// Founder: 50 SUI, Investor: 30 SUI, Treasury: 20 SUI
Referral Commission (90/7/3)
// 90% to merchant, 7% to referrer, 3% to platform
let mut splits = vector::empty();
vector::push_back(&mut splits, split::create_split(
merchant_address,
9000, // 90%
string::utf8(b"Merchant payment"),
0
));
vector::push_back(&mut splits, split::create_split(
referrer_address,
700, // 7%
string::utf8(b"Referral commission"),
100_000 // Min 0.0001 SUI
));
vector::push_back(&mut splits, split::create_split(
platform_address,
300, // 3%
string::utf8(b"Platform fee"),
0
));
let config = split::create_split_config(splits, ctx);
Equal Split (33.33/33.33/33.34)
// Split evenly among 3 partners
let mut splits = vector::empty();
// Partner 1: 33.33%
vector::push_back(&mut splits, split::create_split(
@partner1,
3333,
string::utf8(b"Partner 1 share"),
0
));
// Partner 2: 33.33%
vector::push_back(&mut splits, split::create_split(
@partner2,
3333,
string::utf8(b"Partner 2 share"),
0
));
// Partner 3: 33.34% (gets the extra 0.01%)
vector::push_back(&mut splits, split::create_split(
@partner3,
3334,
string::utf8(b"Partner 3 share"),
0
));
let config = split::create_split_config(splits, ctx);
Tiered Commission Structure
// Top affiliate: 5%, Sub-affiliates: 2%, Merchant: 93%
let mut splits = vector::empty();
vector::push_back(&mut splits, split::create_split(
merchant_address,
9300, // 93%
string::utf8(b"Merchant revenue"),
0
));
vector::push_back(&mut splits, split::create_split(
top_affiliate,
500, // 5%
string::utf8(b"Top affiliate commission"),
100_000
));
vector::push_back(&mut splits, split::create_split(
sub_affiliate,
200, // 2%
string::utf8(b"Sub-affiliate commission"),
50_000
));
let config = split::create_split_config(splits, ctx);
Updating Split Configuration
// Update split ratios for existing config
let mut new_splits = vector::empty();
// Change to 60/25/15 split
vector::push_back(&mut new_splits, split::create_split(
@recipient1,
6000, // 60%
string::utf8(b"Updated share"),
0
));
vector::push_back(&mut new_splits, split::create_split(
@recipient2,
2500, // 25%
string::utf8(b"Updated share"),
0
));
vector::push_back(&mut new_splits, split::create_split(
@recipient3,
1500, // 15%
string::utf8(b"Updated share"),
0
));
split::update_split_config(&mut config, new_splits, ctx);
Best Practices
Basis Points Calculation
// Helper function to convert percentage to BPS
fun percentage_to_bps(percentage: u64): u64 {
percentage * 100 // 50% = 5000 BPS
}
// Helper function to ensure total equals 100%
fun validate_percentages(percentages: &vector<u64>): bool {
let mut total = 0u64;
let mut i = 0;
while (i < vector::length(percentages)) {
total = total + *vector::borrow(percentages, i);
i = i + 1;
};
total == 10000 // Must equal 100%
}
Minimum Amount Thresholds
// Set reasonable minimums to avoid dust amounts
split::create_split(
recipient,
bps,
description,
1_000_000 // 0.001 SUI minimum, skip if below
)
Configuration Management
// Temporarily disable config for maintenance
split::toggle_split_status(&mut config, false, ctx);
// ... perform updates ...
// Re-enable
split::toggle_split_status(&mut config, true, ctx);
Amount Calculation Preview
// Calculate expected amounts before execution
fun preview_split_amounts(
total_amount: u64,
config: &SplitConfig
): vector<u64> {
let mut amounts = vector::empty<u64>();
let split_count = split::get_splits_count(config);
let mut i = 0;
while (i < split_count) {
let split_rule = split::get_split(config, i);
let bps = split::get_split_bps(split_rule);
// Calculate: (amount * bps) / 10000
let split_amount = (((total_amount as u128) * (bps as u128))
/ 10000u128) as u64;
vector::push_back(&mut amounts, split_amount);
i = i + 1;
};
amounts
}
Security Considerations
- Creator Control: Only creator can modify or toggle configuration
- BPS Validation: Total must always equal exactly 10000
- Active Status: Always check
is_active()before execution - Recipient Validation: Verify recipient addresses are valid
- Minimum Amounts: Set appropriate minimums to avoid dust
- Overflow Protection: Calculations use u128 internally
Gas Optimization
- Reusable Configs: Create once, use multiple times
- Sequential Processing: Recipients processed one by one
- No Intermediate Objects: Direct transfers minimize gas
Approximate costs:
- Create config: ~0.01 SUI
- Execute split (3 recipients): ~0.015 SUI
- Execute split (10 recipients): ~0.03 SUI
Mathematical Precision
The module uses u128 for intermediate calculations to prevent overflow:
// Split amount = (total_amount * bps) / 10000
let amount_u128 = (total_amount as u128);
let bps_u128 = (bps as u128);
let result = (amount_u128 * bps_u128) / 10000u128;
let split_amount = (result as u64);
Rounding: Due to integer division, small rounding differences may occur. Excess funds are returned to sender.
Common Patterns
NFT Royalty Splits
// 5% creator, 2.5% platform, 92.5% to seller
let mut royalty_splits = vector::empty();
vector::push_back(&mut royalty_splits, split::create_split(
seller_address,
9250, // 92.5%
string::utf8(b"Seller proceeds"),
0
));
vector::push_back(&mut royalty_splits, split::create_split(
creator_address,
500, // 5%
string::utf8(b"Creator royalty"),
0
));
vector::push_back(&mut royalty_splits, split::create_split(
platform_address,
250, // 2.5%
string::utf8(b"Platform fee"),
0
));
Multi-Signature Treasury
// Split to multiple signers for governance
let mut treasury_splits = vector::empty();
// 5 equal signers
let mut i = 0;
while (i < 5) {
vector::push_back(&mut treasury_splits, split::create_split(
signer_addresses[i],
2000, // 20% each
string::utf8(b"Signer allocation"),
0
));
i = i + 1;
};
Related Documentation
- Batch Payment API - Multi-recipient flat amounts
- Token Registry API - Multi-currency support
- Payment API - Single payment operations
- Core Modules - Module architecture
Need help? Check the SDK documentation for integration examples.