A production-grade, open-source ERC-4626-inspired asset-management vault built on Soroban (Stellar's smart contract platform).
It is designed as a reusable base template: fork it, plug in a yield strategy, and you have a working vault. See STRATEGY.md for the extension guide. This is a Stellar Wave contribution-program project.
Every yield-bearing vault needs the same scaffolding — share accounting, rounding-safe fixed-point math, deposit/withdraw plumbing — before it can express a single line of actual strategy logic. Zenith provides that scaffolding once, audited and tested, so contributors can focus on the part that's actually novel.
- ERC-4626-style interface:
deposit,withdraw,mint,redeem, pluspreview_*variants for all four. - Fixed-point share math: overflow-checked
mul_div/mul_div_ceil, no floating point anywhere. - Vault-favoring rounding: deposits/mints round down, withdrawals/redeems round up — protects existing holders from share-price manipulation via dust deposits.
no_std, nounsafe, deterministic, single-threaded (Soroban execution model).- Events: every state-changing call emits a topic-indexed event for off-chain indexers.
- Extensible by design:
total_assets()is the single seam a fork overrides to add a yield strategy — seeSTRATEGY.md.
| Function | Description |
|---|---|
initialize(admin, asset, share_token, decimals) |
One-time setup. share_token must be a token contract (e.g. a Stellar Asset Contract) with its admin later transferred to the vault. decimals must match both tokens' actual decimals (FR-2) or the call fails with DecimalMismatch. |
deposit(caller, assets) -> shares |
Deposit assets, receive shares (rounded down). |
mint(caller, shares) -> assets |
Mint an exact number of shares, pulling the required assets (rounded up). |
withdraw(caller, assets) -> shares |
Withdraw an exact amount of assets, burning the required shares (rounded up). |
redeem(caller, shares) -> assets |
Burn shares, receive the proportional assets (rounded down). |
total_assets() -> i128 |
Vault's underlying asset balance. |
total_supply() -> i128 |
Outstanding share supply, tracked by the vault itself (see below). |
preview_deposit / preview_mint / preview_withdraw / preview_redeem |
Read-only quotes for each operation, no state change. |
config() -> VaultConfig |
Returns the vault's admin, asset, share token, and decimals. |
Full rounding rationale is documented in STRATEGY.md.
SEP-41, the standard Soroban token interface, does not expose a total_supply function — tokens track balances, not a supply counter. Since the vault is the sole mint/burn authority for shares (NFR-2), it tracks the running total itself in instance storage rather than depending on a non-standard call into the share token contract. total_supply() is therefore always in lockstep with actual mint/burn activity and costs nothing extra to query.
src/
lib.rs Contract entry points (deposit/withdraw/mint/redeem, admin, views)
types.rs VaultConfig, VaultError, and storage helpers (config + total-shares tracking)
math.rs Fixed-point mul_div / mul_div_ceil / scale helpers, no_std, no floats
events.rs Topic-indexed event emission for each state-changing call
test.rs Unit tests covering deposits, withdrawals, rounding, and error paths
| Doc | Purpose |
|---|---|
requirements.md |
Functional and non-functional requirements this contract satisfies |
STRATEGY.md |
How to fork this vault and add a yield strategy, fees, or harvest logic |
STELLAR_WAVE.md |
Contribution program guide and PR checklist |
- Rust (stable, 1.74+): https://rustup.rs
wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown- Soroban CLI (optional, for testnet deployment):
cargo install --locked soroban-cli
cargo build --target wasm32-unknown-unknown --releaseThe compiled contract lands at target/wasm32-unknown-unknown/release/zenith_vault_ref.wasm.
cargo test30 unit tests cover initialization (including decimals-mismatch rejection), all four vault operations, rounding behavior, preview/actual parity, total-supply bookkeeping, checked-arithmetic overflow at i128::MAX, and error paths (zero amounts, double-init, insufficient shares/assets).
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --checkBoth run in CI (see .github/workflows/ci.yml), alongside a check that forbids unsafe (NFR-1).
# 1. Deploy the vault contract
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/zenith_vault_ref.wasm \
--network testnet \
--source <your-keypair>
# 2. Deploy (or reuse) a share token — e.g. a Stellar Asset Contract — and
# transfer its admin rights to the vault's contract ID once initialize()
# below has run, so the vault becomes the sole mint/burn authority.
# 3. Initialize
soroban contract invoke \
--id <vault-contract-id> \
--network testnet \
--source <your-keypair> \
-- \
initialize \
--admin <admin-address> \
--asset <asset-contract-id> \
--share_token <share-token-contract-id> \
--decimals 7- No
unsafeRust, enforced by CI. - All arithmetic is overflow-checked (
checked_*operations,overflow-checks = truein release profile). no_std, matching the Soroban execution environment.- This is a reference template, not an audited production deployment. If you fork it for mainnet use, get an independent audit — see the security checklist in
STRATEGY.md.
See STELLAR_WAVE.md for the contribution workflow, PR requirements, and open contribution areas (fee modules, strategy interfaces, multi-asset support, and more).