Skip to main content

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 identifier
  • splits - Vector of split rules
  • total_bps - Total basis points (must equal 10000 = 100%)
  • is_active - Configuration active status
  • creator - 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 address
  • bps - Basis points (0-10000, where 10000 = 100%)
  • description - Description of this split
  • min_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 rules
  • ctx - Transaction context

Returns: SplitConfig object

Aborts:

  • E_INVALID_SPLIT_CONFIG (500) - Empty splits vector
  • E_TOTAL_BPS_MISMATCH (501) - Total BPS ≠ 10000

Validation:

  • Splits vector must not be empty
  • Sum of all bps values 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 reference
  • coin - Coin to split and distribute
  • ctx - 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 address
  • bps - Basis points allocation (0-10000)
  • description - Human-readable description
  • min_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 config
  • splits - New vector of split rules
  • ctx - Transaction context

Aborts:

  • E_NOT_AUTHORIZED (999) - Caller is not creator
  • E_INVALID_SPLIT_CONFIG (500) - Empty splits
  • E_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 config
  • is_active - New active status
  • ctx - 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

CodeConstantDescription
500E_INVALID_SPLIT_CONFIGInvalid split configuration (empty or inactive)
501E_TOTAL_BPS_MISMATCHTotal basis points don't equal 10000
999E_NOT_AUTHORIZEDCaller 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

  1. Creator Control: Only creator can modify or toggle configuration
  2. BPS Validation: Total must always equal exactly 10000
  3. Active Status: Always check is_active() before execution
  4. Recipient Validation: Verify recipient addresses are valid
  5. Minimum Amounts: Set appropriate minimums to avoid dust
  6. 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;
};

Need help? Check the SDK documentation for integration examples.