Soroban smart contracts powering the Stellar Trust Circles protocol
Decentralized rotating savings groups — built on Stellar
Rotating savings groups — known as ajo in Nigeria, chama in Kenya, tanda in Latin America, and susu in the Caribbean — are one of the oldest and most trusted forms of community finance. A fixed group of people contribute a set amount on a schedule; each cycle one member receives the full pooled amount, rotating until everyone has received once.
This repository contains the Soroban smart contract that replaces the trusted human treasurer at the center of these groups. The contract:
- Holds contributions in escrow — no individual controls the pot
- Automates payout rotation based on the member order set at creation
- Records every contribution and payout permanently on-chain
- Tracks on-chain reputation for every participating address
- Enforces social vouching for new member admission
- Executes governance votes that change circle rules
New addition: Trust Circles is no longer just "a savings app that happens to use Stellar for payments." Six new integrations make specific, non-negotiable use of Stellar infrastructure that has no equivalent on other chains — see New Features below.
Each of these uses a piece of Stellar infrastructure that a general-purpose
EVM chain either can't replicate at all, or can't replicate without
bolting on separate contracts and centralized bridges. Full integration
details are in INTEGRATION_ADDENDUM.md.
| # | Feature | Stellar primitive used | Module | Status |
|---|---|---|---|---|
| 1 | Multi-asset contributions | PathPaymentStrictReceive + built-in DEX |
multi_asset.rs |
Ready to wire in |
| 2 | Unbanked member onboarding | Stellar Disbursement Platform (SDP) | sdp_onboarding.rs |
Ready to wire in |
| 3 | NGN/GHS/KES-denominated circles | Reflector oracle network | oracle.rs |
Ready to wire in |
| 4 | Fiat on/off-ramp | SEP-6 / SEP-24 anchor protocol | (frontend-only, see frontend repo) |
Ready to wire in |
| 5 | Protocol-level dispute resolution | Native asset clawback flag | clawback.rs |
Ready to wire in |
| 6 | Private reputation proofs | Protocol 25 ZK / BLS12-381 primitives | reputation_zk.rs |
Roadmap — interface scaffolded, circuit not yet implemented or audited |
Trust Circles cannot be rebuilt on Ethereum or Solana without losing its core features: Reflector oracle pricing for NGN-denominated circles, SDP for unbanked member onboarding, path payments for multi-asset contributions, and the Stellar clawback flag for dispute resolution. The Stellar integration is not cosmetic — it is the product.
STC-smart-contracts/
├── .github/
│ └── workflows/
│ └── ci.yml # Build, test, and lint on every push
├── contracts/
│ └── trust_circle/
│ ├── Cargo.toml # Contract package — soroban-sdk v25
│ ├── Makefile # Per-contract build shortcuts
│ └── src/
│ ├── lib.rs # Core contract logic (13 functions)
│ ├── test.rs # 30 unit tests
│ ├── multi_asset.rs # path payment contributions
│ ├── sdp_onboarding.rs # unbanked member onboarding
│ ├── oracle.rs # Reflector NGN/GHS/KES pricing
│ ├── clawback.rs # clawback dispute resolution
│ └── reputation_zk.rs # ZK reputation (roadmap)
├── .clippy.toml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
└── INTEGRATION_ADDENDUM.md # How to wire the new modules into lib.rs
One contract deployment = one Trust Circle. The contract is written in Rust using the Soroban SDK and compiled to WebAssembly. All state is stored in Soroban instance/persistent storage.
| Key | Type | Purpose |
|---|---|---|
Circle |
Circle struct |
Full circle state — members, cycle, deadlines, asset config, active status |
Contributions |
Map<(Address, u32), Contribution> |
Every contribution keyed by (member, cycle) |
Reputation(Address) |
u32 |
Per-address reputation score, persistent across circles |
Vouches(Address) |
Vec<Address> |
List of addresses that have vouched for a newcomer |
Proposals |
Map<u32, Proposal> |
All governance proposals, open and executed |
NextProposalId |
u32 |
Monotonically incrementing proposal ID counter |
OffchainMember(hash) |
OffchainMember struct |
unbanked member record keyed by phone hash |
ClawbackConfig(circle) |
ClawbackConfig struct |
clawback authority and asset per circle |
Dispute(circle, id) |
DisputeRecord struct |
dispute records raised by the clawback authority |
| Function | Parameters | Description |
|---|---|---|
create_circle |
admin, name, usdc_token, members, contribution_amount, cycle_length_secs |
Initializes a new savings circle. Sets the rotation order, contribution rules, and first cycle deadline. Minimum 2 members. |
contribute |
member |
Transfers contribution_amount USDC from the member's wallet into contract escrow. Awards +10 reputation. |
release_payout |
caller |
Tallies contributions, penalizes missed members (−20 reputation), sends the pot to the next member, advances the cycle. |
restart_circle |
admin |
Reactivates a completed circle for a new rotation. |
vouch |
voucher, newcomer |
Records an on-chain endorsement, gated on the voucher's reputation ≥ 50. |
propose / vote / execute_proposal |
see below | Governance flow — change amount, cycle length, add/remove member. |
get_circle / get_reputation / has_contributed / get_vouches / get_proposal |
— | Free read functions. |
| Function | Module | Description |
|---|---|---|
set_contribution_asset |
multi_asset |
Member sets their preferred contribution asset (XLM, USDC, EURC, NGNC, ...) |
contribute_multi_asset |
multi_asset |
Records a contribution settled via PathPaymentStrictReceive, with slippage protection |
add_offchain_member |
sdp_onboarding |
Admin adds a member by phone hash, no wallet required yet |
claim_membership |
sdp_onboarding |
Member claims their SDP-issued wallet and joins the rotation |
required_usdc_amount |
oracle |
Reads Reflector and returns the current USDC amount for an NGN/GHS/KES-denominated circle |
configure_clawback |
clawback |
Admin designates a clawback authority for the circle's settlement asset |
raise_dispute / resolve_dispute |
clawback |
Authority raises/resolves a dispute, creating an on-chain audit trail for the classic clawback tx |
verify_reputation_proof |
reputation_zk |
Roadmap — always returns NotImplemented until the ZK circuit is audited |
| Variant | Value type | Effect when executed |
|---|---|---|
ChangeAmount(i128) |
New amount in stroops | Updates contribution_amount for all future cycles |
ChangeCycleLength(u64) |
New length in seconds | Updates cycle_length_secs from next cycle |
AddMember(Address) |
Stellar address | Appends address to the rotation |
RemoveMember(Address) |
Stellar address | Removes address from the rotation |
| Symbol | Emitted by | Payload |
|---|---|---|
circle_crtd |
create_circle |
Circle name |
contributed |
contribute |
(member, cycle) |
payout_sent |
release_payout |
(recipient, amount, cycle) |
completed |
release_payout |
Final cycle number |
restarted |
restart_circle |
Current cycle number |
vouched |
vouch |
(voucher, newcomer) |
proposed |
propose |
(proposer, proposal_id) |
voted |
vote |
(voter, proposal_id, vote_yes) |
executed |
execute_proposal |
proposal_id |
mlt_ctrb |
contribute_multi_asset |
(member, cycle, settled_amount) |
off_add |
add_offchain_member |
(phone_hash, rotation_slot) |
off_clm |
claim_membership |
(phone_hash, new_address) |
dispute |
raise_dispute |
(circle_id, dispute_id) |
| Action | Change |
|---|---|
| Contribute on time | +10 points |
| Miss a contribution | −20 points (saturates at 0, never negative) |
| Score | Tier |
|---|---|
| 0 – 49 | New Member |
| 50 – 99 | Building Trust |
| 100+ | Trusted |
Reputation is stored per Stellar address and persists across all circles.
The new reputation_zk module (roadmap) will let a member prove they're
in the "Trusted" tier without revealing their exact score.
| Network | Contract ID | Status |
|---|---|---|
| Testnet | CANM5X47IG3AM5JDG6DVGZ24B3RLBNT5653CXRUEUDWF6JERO4YEX6ZS |
Active |
| Mainnet | — | Pending security audit |
Note: Mainnet deployment is explicitly gated on a completed third-party security audit, which now also needs to cover the five new these modules before they are wired into
lib.rsand deployed. Do not send real funds to any Mainnet deployment of this contract until an audit report is published in this repository.
| Tool | Version | Install |
|---|---|---|
| Rust | 1.84.0+ | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| WASM target | — | rustup target add wasm32v1-none |
| Stellar CLI | 26.0.0+ | curl -fsSL https://github.com/stellar/stellar-cli/raw/main/install.sh | sh -s -- --install-deps |
git clone https://github.com/Stellar-trust-circles/STC-smart-contracts
cd STC-smart-contracts
stellar --version # should print v26.x.x
rustc --version # should print 1.84.x or higherstellar network add \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015" \
testnetstellar keys generate alice --network testnet
curl "https://friendbot.stellar.org?addr=$(stellar keys address alice)"Follow INTEGRATION_ADDENDUM.md to add the module
declarations, new Circle fields, and new #[contractimpl] entry points
to lib.rs. The five module files under contracts/trust_circle/src/ are
self-contained and independently unit-tested, but they don't do anything
until lib.rs calls into them.
stellar contract build
# Output: target/wasm32v1-none/release/trust_circle.wasmcargo teststellar contract deploy \
--wasm target/wasm32v1-none/release/trust_circle.wasm \
--source alice \
--network testnet \
--alias trust_circleThe core test suite lives in contracts/trust_circle/src/test.rs and
covers 30 tests across circle lifecycle, vouching, and governance. Each
Each new module ships its own colocated #[cfg(test)] tests (see
multi_asset.rs::test::slippage_below_floor_is_rejected for an example);
integration tests covering the new entry points should be added to
test.rs once they're wired into lib.rs per
INTEGRATION_ADDENDUM.md.
cargo test # Run all tests
cargo test test_vouch # Run vouching tests only
cargo test -- --nocapture # Show println! outputEvery push and pull request to main runs two parallel jobs — build/test
and lint (cargo clippy -- -D warnings). No pull request may be merged
with a failing CI run.
| Network | USDC Asset Issuer |
|---|---|
| Testnet | GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 |
| Mainnet | GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN |
Reflector oracle contract IDs (Testnet/Mainnet) and additional stablecoin
issuers (EURC, NGNC) used by these modules are tracked in
ARCHITECTURE.md in the documents repo.
| Need | How Stellar meets it |
|---|---|
| Sub-cent fees | A $5 weekly contribution costs less than $0.001 in fees — viable at any amount |
| 3–5 second finality | Payout recipients see funds in the same session |
| Native stablecoins | Members save and receive in dollars, euros, or naira without price volatility risk |
| Built-in DEX + path payments | Multi-asset contributions settle atomically without a separate aggregator contract |
| Reflector oracle network | On-chain FX pricing for NGN/GHS/KES-denominated circles |
| Stellar Disbursement Platform | Unbanked members join without a pre-existing wallet |
| Native clawback flag | Protocol-level dispute resolution, not just contract logic |
| Soroban smart contracts | Trustless rotation and governance logic enforced on-chain |
| Repo | Description |
|---|---|
| STC-smart-contracts (this repo) | Soroban contracts — Rust |
| frontend | React + TypeScript web interface |
| documents | Docs, JavaScript SDK, Python SDK, CLI tool, code examples |
See CONTRIBUTING.md for setup instructions, branch naming conventions, commit message format, and the review checklist every PR must pass.
Issues tagged good first issue
are scoped to single functions with complete acceptance criteria — a great
place to start. These modules each need their entry points wired into
lib.rs and integration tests added; those make natural first issues.
MIT — see LICENSE