Real-World Asset (RWA) Tokenization on Stellar/Soroban
A suite of Soroban smart contracts for tokenizing, managing, and distributing dividends for real-world assets on the Stellar network.
Contract
Description
RWA Token (rwa-token)
SEP-41 compliant token with compliance extensions: pause, KYC hook, investor registry, transfer/holding limits, dividend distribution
KYC Hook (kyc-hook)
KYC/AML verification with expiry, tiered levels, and batch approval
Investor Registry (investor-registry)
Whitelist with accredited status, purchase limits, purchase tracking, and batch operations
Dividend Distributor (dividend-distributor)
Multi-round dividend distribution with snapshot-based pro-rata calculation, claiming, round closure, and unclaimed fund withdrawal
┌──────────────────┐
│ RWA Token │◄──── SEP-41 Standard
│ (rwa-token) │ (transfer, approve, mint, burn)
├──────────────────┤
│ Compliance Layer │
│ ┌────────────┐ │
│ │ KYC Hook │──│──► check_kyc(investor) → bool
│ └────────────┘ │
│ ┌────────────────┐
│ │Investor Registry│──► is_investor(investor) → bool
│ └────────────────┘
│ ┌──────────────────┐
│ │ Transfer/Holding │──► configurable limits
│ │ Limits │
│ └──────────────────┘
├──────────────────┤
│ Dividend Dist. │────► snapshot, pro-rata, claim
└──────────────────┘
Rust (nightly) with wasm32-unknown-unknown target
Soroban CLI
Node.js 18+ (for TypeScript CLI, optional)
Stellar account with testnet/mainnet funds
cargo build --target wasm32-unknown-unknown --release
All 48 tests should pass.
export STELLAR_SECRET_KEY=S...
# Deploy full suite
./scripts/rwa-cli.sh deploy --name " T-Bill Token" --symbol " TBILL" --decimals 6
# Configure compliance
./scripts/rwa-cli.sh configure --transfer-limit 1000000 --holding-limit 50000000
# Manage investors
./scripts/rwa-cli.sh investors --add GA... --accredited true --purchase-limit 100000
# Mint tokens
./scripts/rwa-cli.sh mint GA... 10000
# Dividends
./scripts/rwa-cli.sh dividends --distribute 5000 --token CA...
# Pause/Unpause
./scripts/rwa-cli.sh pause
./scripts/rwa-cli.sh unpause
4. Deploy (TypeScript CLI)
cd cli
npm install
npm run build
STELLAR_SECRET_KEY=S... node bin/rwa-cli.js deploy --network testnet --name " T-Bill Token" --symbol TBILL --decimals 6
Function
Description
initialize(admin, name, symbol, decimals)
Initialize the token
name(), symbol(), decimals()
SEP-41 metadata
balance(id), total_supply()
SEP-41 queries
transfer(from, to, amount)
SEP-41 transfer with compliance checks
transfer_from(spender, from, to, amount)
SEP-41 delegated transfer
approve(from, to, amount, expiration_ledger)
SEP-41 approval with expiration
allowance(owner, spender)
SEP-41 allowance query
burn(from, amount)
Burn tokens
burn_from(spender, from, amount)
Burn from allowance
mint(to, amount)
Mint tokens (admin only)
set_admin(new_admin)
Transfer admin role
set_paused(bool), get_paused()
Pause/unpause all transfers
set_kyc_hook(hook), get_kyc_hook(), remove_kyc_hook()
KYC integration
set_investor_registry(registry), get_investor_registry(), remove_investor_registry()
Registry integration
set_transfer_limit(limit), get_transfer_limit(), remove_transfer_limit()
Per-transfer limits
set_holding_limit(limit), get_holding_limit(), remove_holding_limit()
Per-address holding limits
mint_to_investor(to, amount)
Mint with full compliance checks
distribute_dividend(token, amount)
Start a dividend round
claim_dividend(investor)
Claim pro-rata dividend share
get_active_dividend(), has_claimed_dividend(investor)
Dividend queries
Function
Description
initialize(admin)
Initialize contract
approve(investor, level, expiry_ledger)
Approve KYC with level and expiry
revoke(investor)
Revoke KYC approval
check_kyc(investor) → bool
Check if investor is KYC'd and not expired
get_kyc_level(investor)
Get investor's KYC level
get_kyc_record(investor)
Get full KYC record
batch_approve(investors, level, expiry_ledger)
Batch approve multiple investors
Investor Registry (investor-registry)
Function
Description
initialize(admin)
Initialize contract
add_investor(investor, accredited, purchase_limit)
Add to whitelist
remove_investor(investor)
Remove from whitelist
is_investor(investor) → bool
Check whitelist status
is_accredited(investor) → bool
Check accredited status
set_accredited(investor, bool)
Update accredited status
get_investor_info(investor)
Get full investor record
set_purchase_limit(investor, limit)
Set per-investor purchase limit
record_purchase(investor, amount)
Record a purchase (checks limit)
get_investor_count()
Total whitelisted count
batch_add_investors(investors, accredited[], limits[])
Batch whitelist
Dividend Distributor (dividend-distributor)
Function
Description
initialize(admin, rwa_token)
Initialize with link to RWA token
create_distribution(dividend_token, total_amount) → round_id
Create new distribution round
claim_dividend(round_id, claimant) → amount
Claim pro-rata share
get_round(round_id)
Get round details
get_claim(round_id, claimant)
Get claim details
has_claimed(round_id, claimant) → bool
Check if claimed
close_round(round_id)
Force-close a round
withdraw_unclaimed(round_id, to) → amount
Withdraw unclaimed funds (closed rounds only)
Every state-changing function emits events. Key event topics:
initialize, transfer, approve, mint, burn
set_paused, set_kyc_hook, set_investor_registry
kyc_approved, kyc_revoked, kyc_batch_approved
investor_added, investor_removed, investors_batch_added
dividend_distributed, dividend_claimed, distribution_created
# Check compilation
cargo check --workspace
# Run all tests
cargo test --workspace
# Run specific contract tests
cargo test -p rwa-token
cargo test -p kyc-hook
cargo test -p investor-registry
cargo test -p dividend-distributor
# Generate docs
cargo doc --workspace --no-deps --open
MIT