The Stellar Side
Four contracts, one system — how Writz lives on Soroban. The Stellar side of Writz consists of four Soroban smart contracts, a USDC liquidity pool, an interest rate model, and an oracle layer. This page walks through each component, how they interact, and the design decisions behind them.The Four Contracts
1. bitcoin-spv
Verifies Bitcoin transactions on Soroban using Simplified Payment Verification. Completely stateless — no Bitcoin headers are stored on-chain. Takes a proof bundle (headers + Merkle proof + raw transaction) and returns aVerificationResult containing the txid, block hash, and confirmed outputs.
This contract is called first in every deposit flow. Its output — specifically the txid — is passed to the commitment-tree contract and bound into the ZK proof, ensuring every position corresponds to a real Bitcoin transaction.
Deployed: CAE5L7BO2GNF7MIZWXB2BTUMLYNIMQZUSWN2BWLZQS7HRHLOUSL6VLWJ
2. zk-verifier
Verifies Groth16 BN254 proofs on Soroban using Protocol X-Ray’s BN254 host functions. Stores one verification key per circuit type (Deposit, BorrowRepay, Liquidation).
The core verification:
- Loads the verification key for the specified circuit
- Computes
vk_x = Σ (public_signals[i] × vk.ic[i])usingbn254_g1_msm - Runs a 4-pair
bn254_pairing_check:e(A,B) == e(alpha,beta) × e(vk_x,gamma) × e(C,delta)
true if the proof is valid, false otherwise. Malformed proofs (invalid curve points) cause the host to reject the transaction entirely — the correct security behavior.
Deployed: CDV45GLXG4AOU6BDZSY5YHHVNGQIAYAPD3PUGXIIIYLIO6V2XGO6SMFVAll three verification keys are set on testnet (Deposit IC=6, BorrowRepay IC=9, Liquidation IC=6).
3. commitment-tree
The core privacy and lending contract. Manages the Poseidon Merkle commitment tree and all ZK-gated lending operations. This is where positions are created, loans are issued, and repayments are recorded. Key functions:- The
borrowamount is extracted from the ZK proof’s public signal — the caller cannot supply an arbitrary amount - The
repayamount is recovered from field-negation inversion of the proof’s delta signal - The
liquidateusdc_debtis extracted from the proof — the liquidator cannot inflate the debt they claim - Nullifier freshness is checked before any state change — double-spending is impossible
- Merkle root must match the current on-chain root — stale proofs are rejected
CDFAP3J4WLFZC2N5U66X5EO62POBBIBXOKCCMCM3IRLJNXT73C4IBKA7
4. private-lend
A non-ZK lending skeleton that provides the borrowing and repayment interface without the ZK layer. Used for:- Phase 1 testing (simpler than the full ZK flow)
- A reference implementation showing the core lending mechanics
- Future: may be used as a “fast lane” for users who opt out of ZK privacy
CCLH2GJYG3QSHZJI7V7VK3DNMNK3I3QJCECBSFGX3AC6CK4I7EF7ZJ2G
Interest Rate Model
Writz uses a kinked utilization curve — the same model pioneered by Aave and Compound, adapted for Writz’s parameters.| Parameter | Value | Rationale |
|---|---|---|
base_rate | 0% | No charge when pool is empty |
Uoptimal | 75% | Target utilization where rates are attractive to both sides |
slope1 | 8% APR | Gradual increase up to target |
slope2 | 200% APR | Steep increase above target — strong incentive to repay/supply |
| Protocol fee | 15% | Share of borrow rate captured by protocol; rest goes to USDC suppliers |
| Utilization | Borrow APR | Supply APR |
|---|---|---|
| 0% | 0% | 0% |
| 50% | 5.33% | 4.53% |
| 75% | 8.00% | 6.80% |
| 90% | 48.0% | 40.8% |
| 100% | 208.0% | 176.8% |
Oracle Design
Writz uses a multi-oracle approach for BTC/USD price feeds, with a median aggregation strategy to resist price manipulation. Primary oracle: RedStone (push model, SEP-40 interface)Secondary oracle: Pyth Network (pull model, SEP-40 interface) Staleness check: Price data older than 90 seconds is rejected. If both oracles are stale, borrowing and liquidation are paused until fresh prices are available. Manipulation resistance:
- Median of two oracles: a single oracle manipulation requires moving the median
- Liquidation smoothing: large liquidations can be executed in tranches to prevent single-block oracle manipulation attacks
Storage and TTL Management
Soroban’s storage has a time-to-live (TTL) system. Every storage entry has an expiration point; entries that are not accessed eventually expire and are deleted. Writz manages TTL carefully to ensure user positions never expire unexpectedly:| Entry type | TTL window |
|---|---|
| Spent nullifiers | 180-day window (near Soroban mainnet max) |
| Merkle root | 180-day window |
| ZK commitments | 180-day window |
| USDC pool balances | 90-day window |
| Per-lender supply balances | 90-day window |
refresh_* functions that extend their TTL. Any keeper — including Writz’s own keeper, a third-party keeper, or even the user themselves — can call these functions to prevent expiry. No permission required.
Events
All contract state changes emit structured events using Soroban’s#[contractevent] annotation:
| Event | Contract | When emitted |
|---|---|---|
DepositVerified | commitment-tree | SPV + ZK deposit accepted |
CommitmentInserted | commitment-tree | New commitment added to Merkle tree |
Borrowed | commitment-tree | USDC loan issued |
Repaid | commitment-tree | Loan partially or fully repaid |
Liquidated | commitment-tree | Position liquidated |
UsdcSupplied | commitment-tree | USDC added to pool |
UsdcWithdrawn | commitment-tree | USDC removed from pool |
Contract Interactions: Full Deposit Flow
Next: Developer Quick Start →