Skip to main content

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 a VerificationResult 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:
Internally, this:
  1. Loads the verification key for the specified circuit
  2. Computes vk_x = Σ (public_signals[i] × vk.ic[i]) using bn254_g1_msm
  3. Runs a 4-pair bn254_pairing_check: e(A,B) == e(alpha,beta) × e(vk_x,gamma) × e(C,delta)
Returns 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: CDV45GLXG4AOU6BDZSY5YHHVNGQIAYAPD3PUGXIIIYLIO6V2XGO6SMFV
All 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:
Security properties:
  • The borrow amount is extracted from the ZK proof’s public signal — the caller cannot supply an arbitrary amount
  • The repay amount is recovered from field-negation inversion of the proof’s delta signal
  • The liquidate usdc_debt is 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
Deployed: CC2OZ3LG5U6RE3U7QC2R5QMID5GHQBE7QXTJQ4ZSTP5W73WDTKQPRW7E

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
Key functions:
Deployed: CCLH2GJYG3QSHZJI7V7VK3DNMNK3I3QJCECBSFGX3AC6CK4I7EF7ZJ2G

Interest Rate Model

Writz uses a kinked utilization curve — the same model pioneered by Aave and Compound, adapted for Writz’s parameters.
Parameters: Rate examples: Interest accrues continuously. Every position-touching call (borrow, repay, liquidate) applies accrued interest before processing the action.

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
SEP-40 interface: The oracle stub in both contracts follows the Stellar SEP-40 standard interface. Switching oracle providers requires only updating the oracle contract address — no changes to lending logic.

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: Permissionless refresh: All critical entries have public 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: Events are the primary mechanism for the Writz backend to detect loan repayments and trigger the BTC co-signing release on the Bitcoin side.

Contract Interactions: Full Deposit Flow


Next: Developer Quick Start →