How it Works
Introduction

How it Works

Step-by-step lifecycle from pool creation to atomic settlement on Soroban.

The SplitPay protocol follows a deterministic lifecycle designed to guarantee that no payment can be settled unless the pool configuration is strictly valid.

Protocol Lifecycle Overview

[1] CREATE POOL → [2] ADD MEMBERS (10,000 BPS) → [3] CREATE PAYMENT → [4] SETTLE PAYMENT → [5] DISTRIBUTED
• CREATE: Allocates on-chain pool record with owner address and asset contract.
• CONFIGURE: Adds recipients and percentage shares until sum equals exactly 10,000 BPS.
• PAYMENT: Payer registers payment record with amount > 0.
• SETTLE: Atomically transfers funds and disburses shares directly to member addresses.
• FINALIZED: Payment marked Settled; historical distributions become permanently immutable.

Step 1: Pool Creation

The creator calls create_pool(pool_id, owner, asset). The contract verifies that the caller authorized the transaction and that the pool ID does not already exist. The pool is initialized in the Active status with an empty member roster.

rustcontracts/splitpay/src/contract.rs
let pool = Pool {
id: pool_id,
owner: owner.clone(),
asset: asset.clone(),
status: PoolStatus::Active,
created_at: env.ledger().timestamp(),
};
set_pool(&env, &pool);
events::pool_created(&env, pool_id, &owner, &asset, created_at);

Step 2: Member & Share Configuration

The pool owner registers member addresses and their percentage shares in basis points using add_member(pool_id, address, share_bps).

10,000 Basis Points Constraint
The contract strictly enforces that the sum of all members' basis points cannot exceed 10,000 during addition, and must equal exactly 10,000 BPS before any payment can be accepted or settled.

Step 3: Payment Creation

A payer initiates a payment targeting the pool using create_payment(payment_id, pool_id, payer, amount). The contract asserts:

  • Payer authorized the call (payer.require_auth()).
  • Amount is strictly greater than zero (amount > 0).
  • The pool status is PoolStatus::Active.
  • Total configured member shares currently equal 10,000 BPS.

Step 4: Atomic Settlement & Snapshot

Calling settle_payment(payment_id) triggers the settlement engine:

  1. Validates that the payment has not already been settled (Error::PaymentAlreadySettled).
  2. Snapshots the member shares at that exact ledger timestamp.
  3. Computes integer allocations using checked arithmetic and allocates any remainder stroops to member index 0.
  4. Invokes the Stellar Asset Contract to pull total funds from the payer to the contract address.
  5. Dispatches individual token transfers to each member address.
  6. Persists a Distribution record for each recipient and marks the payment Settled.

Step 5: Querying Historical Distributions

Anyone can query historical distributions using get_distributions(payment_id) or inspect individual allocations with get_distribution(payment_id, recipient). Even if the pool configuration is modified later, historical distributions remain completely unchanged.