This project is currently intended for development, learning, and Stellar testnet usage. It is not production-ready or mainnet-ready.
The savings vault now uses internal balance tracking and real token transfers: deposit transfers tokens from the user to the contract, withdraw transfers tokens from the contract to the user, and locking operations manage which tokens are available to withdraw.
See Known Limitations for other current constraints.
This contract is for educational and testnet use. Review the following before any mainnet deployment.
See the Admin Role document for details on what the initialize(admin) value records, what the admin can and cannot do today, and future admin design considerations.
See the Emergency Pause and Admin Misuse Threat Model for malicious or compromised admin scenarios, withdrawal impact, recovery assumptions, mitigations, and residual risks.
See the Vault Fee Model document for clarification on fee assumptions, accounting implications, and user transparency requirements.
| Function | Description |
|---|---|
initialize(admin, token) |
One-time setup; records the admin and token addresses |
deposit(user, amount) |
Add funds to a user's vault |
withdraw(user, amount) |
Remove funds from a user's vault |
withdraw_lock(user, lock_id) |
Withdraw a specific matured lock entry |
get_balance(user) |
Query available (unlocked) balance |
lock_funds(user, amount, unlock_time) |
Lock funds until a Unix timestamp |
get_locked_balance(user) |
Query locked balance |
get_lock(user, lock_id) |
Read one lock record by ID |
list_locks(user, offset, limit) |
Page through a user's lock records |
can_withdraw(user) |
Check if locked funds are withdrawable |
pause(admin, duration_secs) |
Activate emergency pause (blocks deposits/locks; withdrawals remain open) |
unpause(admin) |
Deactivate an active pause |
is_paused() |
Check whether the contract is currently paused |
get_version() |
Query the deployed contract version |
Deposits now transfer real tokens into the contract. Calling
deposittransfers the specified amount from the user to the contract, and callswithdrawtransfer the specified amount from the contract to the user. The contract's internal balance tracking ensures withdrawals are limited to unlocked funds.
The contract uses a Stellar Asset Contract (SAC) to manage token transfers, which is specified during contract initialization via the token parameter.
Install the following before you begin:
-
Rust (latest stable)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
Soroban CLI
cargo install --locked soroban-cli
-
WASM target
rustup target add wasm32-unknown-unknown
Compile the contract to a WASM binary:
# Debug build
cargo build --target wasm32-unknown-unknown
# Optimized release build (recommended for deployment)
cargo build --target wasm32-unknown-unknown --release
# Optimized release build with an immediate WASM size report
make build-releaseThe compiled .wasm file will be at:
target/wasm32-unknown-unknown/release/savings_vault.wasm
Soroban contract size affects upload and deployment costs and can reveal unexpected binary growth. Use the release wrapper above to build and print the artifact size in both human-readable units and exact bytes:
WASM artifact: target/wasm32-unknown-unknown/release/savings_vault.wasm
WASM size: 5.73 KiB (5871 bytes)
To report the size of an existing release artifact without rebuilding it, run:
make wasm-sizeThe reporting command exits with an error and identifies the expected path when the WASM file is missing. CI pipelines should run make build-release (or make wasm-size after their release build) so contract-size changes remain visible in build logs.
Run the full unit test suite:
cargo testAll tests run natively (no WASM needed) using the Soroban SDK test utilities.
Common tasks are available via make:
make test # Run all tests
make build-wasm # Build the contract WASM in release mode
make clean # Clean build artifactssoroban network add \
--global testnet \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015"soroban keys generate --global deployer --network testnet
soroban keys address deployerFund the account at Stellar Friendbot.
- Deploy the Contract
Use the automated deployment script to build the release WASM and deploy it to the network. Pass your configured identity (e.g., deployer) as the first argument:
./scripts/deploy-testnet.sh deployerThe script will verify prerequisites, compile the contract, deploy it, and print your new Contract ID. Save the returned Contract ID — you'll need it to invoke functions.
See the deployment output example to learn what successful output looks like and which Contract ID value to copy.
soroban contract invoke \
--id YOUR_CONTRACT_ID \
--source deployer \
--network testnet \
-- \
initialize \
--admin deployer# Deposit 1000 units
soroban contract invoke \
--id YOUR_CONTRACT_ID \
--source deployer \
--network testnet \
-- \
deposit \
--user deployer \
--amount 1000
# Check balance
soroban contract invoke \
--id YOUR_CONTRACT_ID \
--source deployer \
--network testnet \
-- \
get_balance \
--user deployerstellar-pocketpay-contracts/
├── Cargo.toml # Workspace root
├── .gitignore
├── README.md
└── contracts/
└── savings_vault/
├── Cargo.toml # Contract crate
└── src/
├── lib.rs # Contract implementation
└── test.rs # Unit tests
└── docs/
├── admin-role.md # Admin role documentation
├── api-reference.md # Function naming conventions
├── architecture.md # Architecture overview
├── contract-id-handoff.md # Contract ID handoff guide
├── deployment-environments.md # Deployment environment config
├── error-codes.md # Error code reference
├── events.md # Event schema documentation
├── state-machine.md # Vault state machine documentation
├── pause-design.md # Pause / emergency stop research
├── admin-pause-threat-model.md # Emergency pause and admin misuse threat model
├── storage-migration.md # Storage versioning and migration guide
├── storage-ttl.md # Storage TTL guide
├── testing.md # Test naming conventions
├── troubleshooting.md # Troubleshooting guide
└── upgrade-strategy.md # Upgrade strategy research
- Audit Preparation Checklist — Checklist of documentation, tests, threat model, and deployment details required before any external security review or audit.
- Emergency Pause and Admin Misuse Threat Model — Threat scenarios, withdrawal impact, recovery assumptions, mitigations, limitations, and residual risks for admin-controlled pause mechanisms.
- Vault Fee Model — Clarification of no-fee assumptions, accounting implications, user transparency requirements, design rationale, and framework for potential future fee support.
- Storage Audit — Comprehensive details on the contract's storage layout, keys, mutating functions, and security invariants.
- Storage Migration Guide — Safe storage versioning and migration strategy for future contract upgrades.
- Deployment Environments — Network configuration for local, testnet, and future mainnet, including RPC URLs, identities, environment variables, and deployment commands.
- Contract Error Reference - Current savings vault failure conditions and guidance for SDK and mobile callers.
- SDK Error Mapping Guide — Maps contract errors to SDK handling expectations with user-facing and developer-facing examples.
- State Machine Documentation — Contract lifecycle, user account states, valid and invalid transitions, and error states.
- Architecture Documentation – Overview of project structure, state management, storage, SDK integration, and future extension points.
- API Reference: Function Naming Conventions — Naming convention for
SavingsVault's public functions (commands, single-value queries, collection queries, capability and state queries), with the full function list and category for each. - SDK ↔ Contract Sequence Diagrams – Mermaid sequence diagrams for balance query, deposit, withdraw, and error paths across mobile, SDK, Soroban RPC, and the vault contract.
- Event Schema Documentation – Overview of event names, topics, payload schemas, and JSON examples for vault actions.
- Vault Contract ID Handoff - How to pass a deployed vault contract ID safely to SDK configuration and the mobile app.
- Documentation Style Guide — Conventions for Testnet wording, avoiding production claims, placeholders, command formatting, and linking related docs.
- Sample Vault Interaction Walkthrough — End-to-end deploy, deposit, lock, query, and withdraw example with expected state changes and current limitations.
- CLI Smoke Test Guide — Quick post-deployment verification flow using the Soroban CLI to confirm every contract function responds correctly on testnet or a local sandbox.
- Balance Reconciliation Design Note — How internal accounting should reconcile with real token balances once SAC integration is implemented, including failure modes and invariants tests must enforce.
- Version Metadata — How the
get_versionread-only function works, how SDKs and deployment scripts should use it, and how to bump the version. - Lock Read Helpers — Response shapes and pagination for
get_lockandlist_locks. - Test Coverage Summary — Maps initialization, deposit, withdrawal, and locking behaviours to the tests that cover them, plus known test gaps.
- Failure Mode Catalogue — Comprehensive list of all contract failure modes with expected behavior and test coverage.
- Test Naming Conventions — Naming pattern for unit tests under
contracts/savings_vault/src/test/, with good/bad examples and coverage guidance.
This contract is for educational and testnet use. Review the following before any mainnet deployment.
- Every state-changing function calls
require_auth()on the user's address. - Only the signing user can deposit, withdraw, or lock their own funds.
- Zero and negative amounts are rejected for deposits, withdrawals, and locks.
- Withdrawals exceeding the available balance are rejected.
- Lock amounts exceeding the available balance are rejected.
- Unlock times in the past are rejected.
- Pause duration of zero is rejected.
initialize()can only be called once; subsequent calls panic.
- The admin can activate a time-bounded pause via
pause(admin, duration_secs). - During a pause,
depositandlock_fundsare blocked;withdrawandwithdraw_lockremain available. - The pause auto-expires after
duration_secsseconds (auto-unpause). - Only the admin can pause or unpause (single admin key; multi-sig recommended for mainnet).
- User balances are stored in persistent storage (survives ledger expiry longer).
- Admin and initialization flags use instance storage (tied to contract lifetime).
-
No admin recovery: There is no mechanism for the admin to recover or migrate funds.
-
No upgrade mechanism: The contract does not implement
upgrade(). See docs/upgrade-strategy.md for research into possible upgrade paths. -
No on-chain events: No events are emitted for state changes (deposit, withdraw, lock, unlock). See docs/events.md for planned event schemas.
-
No custom error enum: Contract uses panic strings instead of a structured error enum for off-chain callers.
-
No custom error enum: Contract uses panic strings instead of a structured error enum for off-chain callers.
-
No custom error enum: Contract uses panic strings instead of a structured error enum for off-chain callers.
-
No custom error enum: Contract uses panic strings instead of a structured error enum for off-chain callers.
-
No custom error enum: Contract uses panic strings instead of a structured error enum for off-chain callers.
-
No custom error enum: Contract uses panic strings instead of a structured error enum for off-chain callers.
- Testnet RPC:
https://soroban-testnet.stellar.org:443 - Network passphrase:
Test SDF Network ; September 2015 - Friendbot (free testnet XLM):
https://friendbot.stellar.org - Soroban Explorer: stellar.expert
- Deployment help: see the troubleshooting guide for common Soroban CLI, Friendbot, WASM, network, and invocation issues. For a full breakdown of environment-specific configuration, see the deployment environments guide.
- Always test thoroughly on testnet before considering mainnet deployment.
- Monitor contract storage TTL and extend as needed using
soroban contract extend. See the Storage TTL Guide for persistent vs. instance storage details and example commands.
For a full list of CLI command examples and arguments for each contract method, check out the Contract Invocation Examples.
Contributions are welcome! This project is intentionally beginner-friendly.
See CONTRIBUTING.md for the full guide, including:
- How to format code (
cargo fmt) - How to lint code (
cargo clippy -- -D warnings) - How to run the test suite (
cargo test) - PR checklist and commit message conventions
Quick start:
# Fork & clone, then verify everything is green before making changes
cargo fmt --check
cargo clippy --tests -- -D warnings
cargo testThis project is licensed under the MIT License.