Payment splitting on Stellar. Live at tributary-omega.vercel.app.
A split is a routing rule stored on-chain: a list of recipient addresses and the share each one gets. Once a split exists, anyone can push a payment through it and every recipient gets paid in the same transaction.
Things you can do with one transfer:
- pay a whole team at once
- route a marketplace sale between seller, platform and referrer
- share donation income between project maintainers
- split royalties between collaborators
The splitter contract keeps a registry of splits. Each split holds:
recipients: the addresses that get paidshares: basis points per recipient, always summing to 10,000controller: optional. If set, that address can change the recipients and shares later. If left empty, the split is locked forever.
A recipient is either an account address or another split. When a payment reaches a split recipient, that portion is credited to the child split as escrow, and anyone can distribute it onward. This lets you compose routing trees: a project split that feeds team splits, a marketplace split that feeds a referrer pool.
There are two ways money moves through a split:
paymoves an amount of any Stellar asset from the payer straight to all recipients in a single call.depositparks funds inside the contract, credited to the split. Anyone can later calldistributeto pay the whole credited balance out to the recipients. This fits cases where money arrives over time and payouts happen on a schedule.
Per-recipient amounts are rounded down and the leftover dust goes to the last recipient, so the full amount always lands somewhere. preview_payout returns the exact cut per recipient before you send anything.
| Function | Description |
|---|---|
create_split(creator, recipients, shares, controller) |
Registers a split and returns its id |
pay(from, id, token, amount) |
Splits a payment across all recipients |
pay_many(from, ids, amounts, token) |
Pays several splits in one transaction |
deposit(from, id, token, amount) |
Credits funds to the split without paying out |
distribute(id, token) |
Pays the credited balance out to all recipients |
preview_payout(id, amount) |
Per-recipient amounts a payment would produce |
balance(id, token) |
Credited amount waiting to be distributed |
update_split(id, recipients, shares) |
Controller only. Replaces the routing table |
transfer_control(id, new_controller) |
Controller only. Hands over control, or locks the split with None |
get_split(id) |
Returns a split |
splits_of(creator) |
Ids of all splits a creator registered |
split_count() |
Number of splits created so far |
Early days. The core contract works, is tested and runs on testnet, but it is not audited. Do not put serious money through this yet.
| Network | Contract |
|---|---|
| Testnet | CCZXVZUQIZT673QF6ZGLI5AJLEPWUFWVYOPIOJNLNIOO5NI27V4JGJUU |
- Install the Freighter extension and switch it to Testnet.
- Fund your account for free at friendbot.
- Open tributary-omega.vercel.app, connect, and create a split from the Create tab.
- Pay through it from the Pay tab and watch both balances land in one transaction.
You need stable Rust with the wasm32v1-none target (the checked-in rust-toolchain.toml sets this up automatically).
cargo test
cargo build --release --target wasm32v1-none -p tributary-splittercontracts/splitter core splitting contract
sdk TypeScript client generated from the contract spec
app web dashboard (Vite + React, Freighter wallet)
- Payout history in the dashboard, fed by contract events
- Controller tools in the dashboard: edit, transfer, lock
- Publish the sdk to npm
- Security review, then mainnet
docs/architecture.md covers the storage layout, money paths, error codes and events in detail.
Issues and pull requests are welcome. See CONTRIBUTING.md for how to get set up and what a good change looks like.