Skip to content

StellarSend/contracts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

90 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

StellarSend Contracts

Soroban smart contracts powering the StellarSend global money transfer platform. All contracts are written in Rust and deployed on the Stellar network.


Table of Contents


Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                      User / dApp                        │
└──────────────────────┬──────────────────────────────────┘
                       │ send_payment / send_path_payment
                       ▼
┌─────────────────────────────────────────────────────────┐
│               stellar_send  (Entry Point)               │
│                                                         │
│  • Validates sender + amount                            │
│  • Deducts protocol fee (bps)                           │
│  • Routes payment to recipient                          │
│  • Emits PaymentSent / PathPaymentSent events           │
└─────────┬───────────────────────────┬───────────────────┘
          │ forward fee               │ DEX path swap
          ▼                           ▼
┌──────────────────────┐   ┌─────────────────────────────┐
│    fee_collector     │   │       token_bridge           │
│                      │   │                              │
│  • Accumulates fees  │   │  • wrap / unwrap assets      │
│  • Admin withdrawal  │   │  • Enables cross-asset       │
│  • Per-token totals  │   │    path payments             │
└──────────────────────┘   └─────────────────────────────┘

The system is intentionally modular: stellar_send is the only contract that end users interact with directly. fee_collector and token_bridge are infrastructure contracts managed by the protocol admin.


Contracts

stellar_send

Path: contracts/stellar_send/

The main entry point for the StellarSend protocol. Supports direct token transfers and DEX path payments between any Stellar addresses.

Feature Description
Direct payments Transfer any SEP-41 token from sender to recipient
Path payments Route through the Stellar DEX for cross-asset transfers
Fee deduction Protocol fee in basis points (bps), deducted at source
Pause / unpause Admin can halt new payments in an emergency
Payment records Every transfer is stored on-ledger for auditability
Admin rotation Two-step admin handover (propose → accept)

Key data types:

pub struct ContractConfig {
    pub admin: Address,
    pub fee_bps: u32,          // 100 bps = 1%
    pub fee_collector: Address,
    pub active: bool,
    pub min_amount: i128,
    pub max_amount: i128,
}

pub struct PaymentRecord {
    pub from: Address,
    pub to: Address,
    pub token: Address,
    pub net_amount: i128,
    pub fee_amount: i128,
    pub memo: String,
    pub ledger: u32,
}

fee_collector

Path: contracts/fee_collector/

Receives protocol fees forwarded by stellar_send and allows the treasury admin to withdraw accumulated balances.

Feature Description
Fee accounting Tracks lifetime totals collected per token
Withdrawal Admin-only; supports arbitrary recipient
Treasury management Treasury address updatable by admin
Period limits Configurable withdrawal limit per epoch
Multi-token Operates on any SEP-41 token

token_bridge

Path: contracts/token_bridge/

A lightweight wrap / unwrap bridge for non-native Stellar assets. Users deposit an underlying token and receive an equal amount of wrapped tokens, enabling cross-asset path payments through the StellarSend DEX flow.

Feature Description
wrap Deposit underlying → receive wrapped balance
unwrap Burn wrapped balance → receive underlying
Bridge fee Configurable bps fee on wrap operations
Supply cap Configurable maximum wrapped supply
Pause / unpause Admin can halt wrapping in an emergency
Admin rotation Two-step admin handover

escrow

Path: contracts/escrow/

A standalone contract holding funds in on-chain custody until a time or arbiter condition releases them — deposits, marketplace holdbacks, milestone payments. Split out as its own crate (like fee_collector/token_bridge) since it has its own fund-custody lifecycle independent of stellar_send.

Feature Description
Time-locked release Beneficiary can claim once unlock_time has passed
Optional arbiter Can release to the beneficiary or refund to the depositor at any time
Anti-griefing refund Depositor can only self-refund after unlock_time + a 1-week grace period
Escrow lookup get_escrow for status/UI polling

Differentiator Features

StellarSend isn't just a wallet — these on-chain building blocks are what any plain Stellar wallet can't do. All four are implemented directly in the contracts in this repo (stellar_send for subscriptions/batch/requests, a new escrow crate for conditional transfers), with unit tests covering the happy path and at least one failure case each.

Scheduled & recurring payments

stellar_send/src/subscription.rs — a payer authorizes a recurring transfer once; a keeper (anyone, including an unprivileged bot) can then trigger each due payment without the payer being online.

create_subscription(env, payer, recipient, token, amount, interval_seconds, start_time) -> Result<u64, StellarSendError>
cancel_subscription(env, id) -> Result<(), StellarSendError>
execute_subscription(env, id) -> Result<i128, StellarSendError>
get_subscription(env, id) -> Result<Subscription, StellarSendError>

The payer grants the contract a standard SEP-41 token allowance (token.approve) when creating the subscription; execution pulls funds via transfer_from rather than requiring the payer's live signature, which is what makes unattended, scheduled execution possible while staying non-custodial — the contract enforces the authorization, not a trusted backend. Re-execution before next_execution_time is rejected.

Split / batch payments

stellar_send/src/batch.rs — one sender, many recipients, one call.

send_batch_payment(env, from, token, payments: Vec<(Address, i128)>) -> Result<Vec<PaymentRecord>, StellarSendError>

Reuses the same fee-splitting logic as send_payment. Batches are all-or-nothing: every leg is validated up front, and since a Soroban host transaction is atomic, any transfer failure reverts the whole call — there's no partial batch state to reconcile.

Payment requests / invoicing

stellar_send/src/payment_request.rs — turns StellarSend into a two-sided payment tool: a requester creates a shareable "invoice," and a payer fulfills it.

create_payment_request(env, requester, payer: Option<Address>, token, amount, memo, expiry) -> Result<u64, StellarSendError>
fulfill_payment_request(env, request_id, payer) -> Result<i128, StellarSendError>
cancel_payment_request(env, request_id) -> Result<(), StellarSendError>
get_payment_request(env, request_id) -> Result<PaymentRequest, StellarSendError>

payer is optional — None means anyone can fulfill it. Expired or already-fulfilled/cancelled requests are rejected; the protocol fee is deducted exactly as in send_payment.

Escrow / conditional transfers

escrow/src/lib.rs — funds locked in-contract until a time or arbiter condition releases them.

create_escrow(env, depositor, beneficiary, token, amount, unlock_time, arbiter: Option<Address>) -> Result<u64, EscrowError>
release_escrow(env, escrow_id, caller) -> Result<(), EscrowError>
refund_escrow(env, escrow_id, caller) -> Result<(), EscrowError>
get_escrow(env, escrow_id) -> Result<Escrow, EscrowError>

caller must authorize the call itself (caller.require_auth()) — the beneficiary can release after unlock_time, and an optional arbiter can release or refund at any time. The depositor can only self-refund after unlock_time plus a one-week grace period, so the beneficiary always gets a fair window to claim before the depositor can walk away with the funds.

Note on automation: because release/refund require the caller's own signature, they can't be executed by an unattended keeper the way subscriptions can — unless the keeper is the configured arbiter. The backend's escrow endpoints build an unsigned transaction for whichever party is acting to sign client-side, the same pattern used for a regular payment.


Getting Started

Prerequisites

Tool Version Install
Rust stable (see rust-toolchain.toml) curl https://sh.rustup.rs -sSf | sh
Soroban CLI ≥ 21.0.0 cargo install --locked soroban-cli
wasm32 target rustup target add wasm32-unknown-unknown

Clone and install

git clone https://github.com/StellarSend/contracts.git
cd contracts

The workspace Cargo.toml pins all dependency versions. No additional install step is required.


Building

Build all contracts to WASM:

make build
# or directly:
bash scripts/build.sh

Individual contract:

soroban contract build --package stellar_send
soroban contract build --package fee_collector
soroban contract build --package token_bridge

Compiled WASM files are written to target/wasm32-unknown-unknown/release/.


Testing

Run the full test suite:

make test
# or:
bash scripts/test.sh

Individual contract tests:

cargo test -p stellar_send
cargo test -p fee_collector
cargo test -p token_bridge

Run with output:

cargo test -p stellar_send -- --nocapture

Deployment

Testnet

# Set environment
export STELLAR_NETWORK=testnet
export STELLAR_RPC_URL=https://soroban-testnet.stellar.org
export STELLAR_ACCOUNT=<your-secret-key>

# Deploy fee_collector first (stellar_send depends on its address)
bash scripts/deploy.sh fee_collector

# Deploy token_bridge
bash scripts/deploy.sh token_bridge

# Deploy stellar_send, passing fee_collector address
bash scripts/deploy.sh stellar_send <fee_collector_address>

Mainnet

Same steps as testnet; update STELLAR_NETWORK=mainnet and STELLAR_RPC_URL=https://horizon.stellar.org.

Note: Mainnet deployments require a multi-sig admin key. Refer to the Security section.


Contract API Reference

stellar_send

initialize(admin, fee_bps, fee_collector) → Result<(), StellarSendError>

Initialise the contract. Must be called exactly once by admin.

  • fee_bps: Protocol fee in basis points. Range: 0..=10_000.
  • fee_collector: Address of the deployed fee_collector contract.

send_payment(from, to, token, amount, memo) → Result<PaymentRecord, StellarSendError>

Transfer amount of token from from to to. The protocol fee is deducted from amount before transfer.

Requires auth from: from

send_path_payment(from, to, send_token, send_amount, dest_token, min_dest_amount, path) → Result<i128, StellarSendError>

Route a payment through the Stellar DEX. The fee is deducted from send_amount before the swap is attempted.

Requires auth from: from

set_fee(new_fee_bps) → Result<(), StellarSendError>

Update the protocol fee. Admin only.

pause() / unpause() → Result<(), StellarSendError>

Halt or resume new payments. Admin only.

transfer_admin(new_admin) → Result<(), StellarSendError>

Propose a new admin (step 1 of 2). Current admin only.

accept_admin() → Result<(), StellarSendError>

Complete the admin rotation (step 2 of 2). Proposed admin only.

get_config() → Result<ContractConfig, StellarSendError>

Return current configuration.

get_payment_record(from, seq) → Result<PaymentRecord, StellarSendError>

Retrieve a stored payment record by sender address and sequence number.


fee_collector

initialize(admin, treasury) → Result<(), FeeCollectorError>

collect_fee(token, amount) → Result<(), FeeCollectorError>

Record fee receipt. The token transfer must already have been made by stellar_send.

withdraw(token, amount, recipient) → Result<(), FeeCollectorError>

Withdraw fees to recipient. Admin only.

get_balance(token) → i128

Return current token balance held by the contract.

get_total_collected(token) → i128

Return the lifetime total of fees collected in token.


token_bridge

initialize(admin, underlying_token) → Result<(), TokenBridgeError>

wrap(from, amount) → Result<i128, TokenBridgeError>

Deposit amount of underlying token; credit wrapped balance.

unwrap(from, amount) → Result<i128, TokenBridgeError>

Burn amount of wrapped balance; return underlying tokens.

get_wrapped_balance(holder) → i128

get_underlying_token() → Result<Address, TokenBridgeError>


Events

All contracts emit structured events consumable by the Horizon API or Soroban event subscriptions.

Contract Topic Data
stellar_send payment_sent (from, to, token, net_amount, fee_amount, memo)
stellar_send path_payment_sent (from, to, send_token, send_amount, dest_token, dest_amount, fee_amount)
stellar_send fee_updated (old_bps, new_bps)
stellar_send paused / unpaused ledger
fee_collector fee_received (token, amount)
fee_collector fee_withdrawn (token, recipient, amount)
token_bridge wrapped (underlying, amount)
token_bridge unwrapped (underlying, amount)

Subscribe to events via Horizon:

soroban events --contract-id <CONTRACT_ID> --network testnet

Security

Admin key management

  • All admin keys should be multi-sig accounts on mainnet.
  • Admin rotation uses a two-step propose/accept pattern to prevent key loss.
  • The fee_collector treasury address is separate from the admin key.

Audit status

Contract Auditor Status
stellar_send In progress
fee_collector In progress
token_bridge In progress

Reporting vulnerabilities

Please report security issues privately via email to security@stellarsend.io. Do not open a public issue for a security vulnerability. See SECURITY.md for our responsible disclosure policy.


Contributing

We welcome contributions! Please read CONTRIBUTING.md before opening a pull request.

  1. Fork the repo
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Make your changes and add tests
  4. Run make test — all tests must pass
  5. Open a PR against main

License

MIT © 2026 StellarSend. See LICENSE for full text.

About

StellarSend Contracts — Soroban smart contracts for global money transfers on Stellar

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages