A Pendle-style yield tokenization protocol for Starknet — enabling users to separate yield-bearing assets into principal and yield tokens, unlocking advanced DeFi strategies.
- Documentation — Build, test, deploy
- Whitepaper / Litepaper — Technical design (TBD)
- Discord — Community & support (TBD)
- X / Twitter — Updates (TBD)
- Governance — Upgrade process
| Property | Value |
|---|---|
| Status | Alpha (Mainnet live) |
| Network | Starknet Mainnet |
| Safety Model | Non-custodial; mixed upgradeability (see below) |
| License | Business Source License 1.1 (converts to GPL-3.0 on 2028-12-19) |
| Property | Status |
|---|---|
| Upgradeable | Yes: All core contracts (SY, PT, YT, Market, Factory, MarketFactory, Router) are owner-upgradeable; only Mocks and Faucet are immutable |
| Admin Keys | Owner-controlled upgrades on all core contracts; no pause functions; no parameter controls |
| Audits | Not yet audited (Alpha stage) |
| Bug Bounty | Not yet active (TBD) |
| Key Dependencies | Starknet RPC, Pragma oracle (TWAP price feeds), hrzSTRK (mock staked token for testing) |
| Live Addresses | See Deployments section |
Horizon splits yield-bearing assets into two components:
- Principal: The base value of your deposit
- Yield: The future interest/rewards your deposit will generate
This separation enables powerful DeFi strategies like locking in fixed yields, speculating on yield rates, or hedging yield exposure.
Wrapper tokens that standardize any yield-bearing asset (staking derivatives, lending positions, LP tokens) into a common interface. SY tokens track the exchange rate between the wrapper and underlying asset.
Represents the principal portion of a yield-bearing asset. PT holders can redeem 1:1 for the underlying SY at expiry. Before expiry, PT trades at a discount reflecting the time value of money (implied yield rate).
Represents the yield portion. YT holders receive all yield generated by the underlying asset until expiry. After expiry, YT becomes worthless as no more yield accrues.
A specialized AMM for trading PT against SY. Uses a time-decay pricing curve where PT price converges to 1:1 with SY as expiry approaches. The market discovers the implied yield rate through trading activity.
┌─────────────────┐
│ Underlying Asset│ (e.g., stETH, aUSDC)
└────────┬────────┘
│ deposit
▼
┌─────────────────┐
│ SY │ Standardized Yield wrapper
└────────┬────────┘
│ mint_py
▼
┌─────────────────┐
│ PT + YT │ Split into Principal + Yield
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ Market│ │ Hold │ Trade PT/SY or hold for yield
└───────┘ └───────┘
- Deposit underlying → Get SY
- Mint PT + YT from SY
- Sell YT on secondary market
- Hold PT until expiry → Redeem for guaranteed principal
- Buy YT tokens from market
- Collect yield as it accrues via
redeem_due_interest() - Profit if actual yield > price paid for YT
- Deposit SY + PT into Market
- Earn trading fees from swaps
- Remove liquidity anytime
| Contract | Description |
|---|---|
SY |
ERC20 wrapper that standardizes yield-bearing assets |
PT |
Principal token, redeemable 1:1 at expiry |
YT |
Yield token, accrues interest until expiry |
Market |
AMM with time-weighted pricing for PT/SY trading |
Factory |
Deploys SY/PT/YT token sets for new underlying assets |
MarketFactory |
Deploys markets for PT tokens |
Router |
Aggregates operations with slippage protection |
| Contract | Address | Upgradeable | Notes |
|---|---|---|---|
| Factory | 0x04fd6d42072f76612ae0a5f97d191ab4c5ede3688d2df0185352e01b7f2fc444 |
Yes | Deploys SY/PT/YT pairs; owner-upgradeable |
| MarketFactory | 0x0465bc423ddde2495e9d4c31563e0b333d9c8b818a86d3d76064fd652ee4be6f |
Yes | Deploys PT/SY AMM markets; owner-upgradeable |
| Router | 0x07ccd371e51703e562cf7c7789d4252b7a63845dc87f25a07cf8b5c28e80563b |
Yes | User entry point; owner-upgradeable |
| Contract | Address | Upgradeable | Purpose |
|---|---|---|---|
| hrzSTRK | 0x02f87c98336c7457aa5aa9237d41e2b903346aeae64be60f63423486d0c16f02 |
No (mock) | ERC-4626 mock yield token (staking vault) |
| Faucet | 0x01b472105605d569778515bcf7ea03759f77af0b2cc945dc999a19edc6572d71 |
No | Test token distributor |
| SY (hrzSTRK) | 0x0601a6717bedf8010f68ec2e4993ea12c208ed949ed76b33b616add725dbc15c |
Yes | Standardized yield wrapper |
| PT (hrzSTRK) | 0x05f94e491f3deb3137eecc94a4641f11112219e7923613d2e2f0f3ef5496b9ca |
Yes | Principal token, redeemable 1:1 at expiry |
| YT (hrzSTRK) | 0x070c396667613d74cb473ad937d717355222c76a41d3a1d2b34299eefda6405d |
Yes | Yield token, accrues interest until expiry |
| Market (hrzSTRK/SY) | 0x04d2f052b91f5c744e67816e72e426cab538661deda0d38143a9cefad973c18 |
Yes | PT/SY AMM trading pool |
Deployment Info:
- Network: Starknet Mainnet
- Deployed: 2025-12-23
- Expiry: January 17, 2026 (Unix timestamp: 1774018125)
- Deployment:
./deploy/scripts/deploy.sh mainnet - Explorer: Use Starkscan to verify addresses
- Verification: Run
scarb buildwith Scarb 2.15.0 and compare bytecode (see Reproducible Build)
// Mint PT + YT from SY tokens
fn mint_py_from_sy(yt, receiver, sy_amount, min_py_out) -> (pt_out, yt_out)
// Redeem PT + YT back to SY (before expiry)
fn redeem_py_to_sy(yt, receiver, py_amount, min_sy_out) -> sy_out
// Redeem PT only (after expiry)
fn redeem_pt_post_expiry(yt, receiver, pt_amount, min_sy_out) -> sy_out
// Swap SY for PT
fn buy_pt_from_sy(market, receiver, sy_in, min_pt_out) -> pt_out
// Swap PT for SY
fn sell_pt_for_sy(market, receiver, pt_in, min_sy_out) -> sy_out
// Add liquidity to market
fn add_liquidity(market, receiver, sy_desired, pt_desired, min_lp_out) -> lp_out
// Remove liquidity from market
fn remove_liquidity(market, receiver, lp_amount, min_sy_out, min_pt_out) -> (sy_out, pt_out)The Market uses a modified AMM curve that accounts for time until expiry:
- Rate Scalar: Controls price sensitivity to reserve changes
- Anchor: Initial implied rate setting
- Fee Rate: Trading fee percentage
As expiry approaches:
- PT price converges toward 1:1 with SY
- Implied rate becomes more volatile with smaller trades
- At expiry, PT = SY in value
- Scarb 2.15.0 (check
.tool-versions) - Starknet Foundry 0.54.1 (snforge + sncast)
- starkli 0.4.2 (deployment tool)
Use asdf or your OS package manager to install exact versions:
# Verify installations
scarb --version # Should show 2.15.0
snforge --version # Should show 0.54.1
starkli --version # Should show 0.4.2cd contracts
scarb build# Run all tests
cd contracts && snforge test
# Run with backtrace on failure
cd contracts && SNFORGE_BACKTRACE=1 snforge test
# Run specific test
cd contracts && snforge test test_full_flowcd contracts && scarb fmtTo verify bytecode matches on-chain deployments:
cd contracts
scarb clean
scarb build
# Compare CASM/Sierra artifacts in target/ with on-chain code
# See deployment tag/commit at https://github.com/ametel01/horizon-starknet/releases# Start local devnet + deploy contracts
make dev-up
# View logs
make dev-logs
# Stop devnet
make dev-down
# Fork mainnet (uses real Pragma oracle)
make dev-fork
make dev-fork-downExample: Deploy to local devnet
./deploy/scripts/deploy.sh devnet
./deploy/scripts/export-addresses.sh devnet # Export to JSON├── contracts/
│ ├── src/
│ │ ├── factory.cairo # Deploys SY/PT/YT pairs
│ │ ├── router.cairo # User entry point with slippage protection
│ │ ├── tokens/ # SY, PT, YT implementations
│ │ ├── market/ # AMM and MarketFactory
│ │ ├── libraries/ # math.cairo (WAD), market_math.cairo, errors, roles
│ │ ├── interfaces/ # Contract interfaces (ISY, IPT, IYT, IMarket, etc.)
│ │ ├── oracles/ # Pragma oracle integration
│ │ └── mocks/ # Test mocks (MockYieldToken, MockPragma, Faucet)
│ └── tests/ # Unit & integration tests
├── packages/
│ ├── frontend/ # Next.js dApp (Bun, React 19, TailwindCSS 4)
│ └── indexer/ # Event indexer (Bun, Apibara DNA, PostgreSQL)
├── deploy/
│ ├── scripts/ # deploy.sh, declare.sh, export-addresses.sh
│ ├── addresses/ # Deployed addresses (devnet.json, mainnet.json)
│ └── accounts/ # sncast account files
└── .github/workflows/ # CI pipelines (build, test, fmt)
The indexer captures all protocol events from Starknet and stores them in PostgreSQL for analytics and frontend queries.
- Runtime: Bun + Apibara DNA (Starknet indexing framework)
- Database: PostgreSQL 16 (hosted on Railway)
- Tables: 40 event tables + 23 views (9 materialized, 14 enriched/aggregated)
cd packages/indexer
# Install dependencies
bun install
# Start local infrastructure (PostgreSQL + DNA server)
bun run docker:up
# Run indexer with devnet preset
bun run dev
# Run with mainnet preset
bun run dev:mainnet
# Run tests
bun run test
# Type check + lint + format
bun run check# Generate migrations after schema changes
bun run db:generate
# Push schema directly (dev only)
bun run db:push
# Open Drizzle Studio (database GUI)
bun run db:studio
# Create/refresh materialized views
bun run db:create-views
bun run db:refresh-viewsThe production indexer runs on Railway with a PostgreSQL database.
# Install Railway CLI (one-time setup)
bun add -g @railway/cli
# Login to Railway (opens browser for authentication)
~/.bun/bin/railway login
# Link to the indexer project (select from list)
cd packages/indexer
~/.bun/bin/railway link
# Connect to PostgreSQL console
~/.bun/bin/railway connect PostgresOnce connected, you can run SQL queries directly. For example, to check table row counts:
SELECT 'router_swap' as table_name, COUNT(*) FROM router_swap
UNION ALL
SELECT 'market_swap', COUNT(*) FROM market_swap;| Table | Description |
|---|---|
factory_yield_contracts_created |
SY/PT/YT pair deployments |
market_factory_market_created |
Market deployments |
router_swap |
All swap transactions |
router_add_liquidity / router_remove_liquidity |
LP operations |
market_implied_rate_updated |
Implied rate changes |
sy_deposit / sy_redeem |
SY token operations |
yt_interest_claimed |
Yield claims |
Analytics views are refreshed periodically for dashboard queries:
- 9 Materialized views: Daily stats, current state, user positions
- 14 Enriched/aggregated views: Router events with token metadata, SY monitoring, YT interest analytics
- Example views:
market_stats,user_positions,yield_history
- Current Status: Not yet audited (Alpha stage, live on mainnet)
- Planned: Security audit in progress / scheduled post-launch
- Status: Not yet active
- Info: Will be announced at launch
IMPORTANT: Horizon is in Alpha on mainnet with real funds at risk. Carefully evaluate your risk tolerance.
- Precision Loss: All arithmetic uses WAD (10^18) fixed-point math; extreme precision inputs may exhibit rounding
- Oracle Risk: PT/YT pricing depends on accurate yield rate discovery via market AMM; illiquid markets may have poor price discovery
- Expiry Handling: At maturity, YT becomes worthless and PT redeems to underlying; ensure frontend clearly communicates expiry dates
- Flash Loan Risk: Not currently guarded against flash loan exploits in initial release; monitor governance decisions
- Upgrade Risk: All core contracts are upgradeable; future upgrades could alter behavior or introduce bugs
- Yield-Bearing Asset Risk: Underlying assets (stETH, aUSDC, etc.) carry their own smart contract, oracle, and liquidation risks
- Liquidation Risk: Users holding PT into maturity lock in time value; PT value floor is underlying asset's accounting value
- Access Control: Minting/burning PT/YT restricted to YT contract only; ensure YT contract itself is secure
- Time Decay: Market AMM uses time-based pricing; significant price swings expected near expiry
- Starknet RPC: Protocol depends on Starknet L2 RPC availability; network outages prevent state updates
- Pragma Oracle: Price feeds sourced from Pragma; oracle downtime affects market pricing
- Bridge Risk: If using cross-chain yield assets, inherits bridge security assumptions
- Non-Standard Tokens: Protocol assumes ERC20 standard; tokens with custom fee-on-transfer or rebasing mechanics untested
- Decimals: Assets with non-18-decimal values may exhibit precision issues; validate before wrapping
- All public functions have comprehensive unit + integration tests
- Slippage protection enabled on all router operations
- Expiry checks prevent minting PT/YT after maturity
- Access control on PT/YT minting (only YT contract can mint/burn)
- No pause/emergency functions
- All core contracts are owner-upgradeable for bug fixes and improvements
Report security issues to: [TBD — add security contact before launch]
Expected response time: Within 48 hours of report.
Do not disclose vulnerabilities publicly until a fix is released.
The Router contract is the recommended entry point for most integrations:
// Mint PT + YT from SY
fn mint_py_from_sy(yt: ContractAddress, receiver: ContractAddress, sy_amount: u256, min_py_out: u256)
// Redeem PT + YT back to SY (pre-expiry)
fn redeem_py_to_sy(yt: ContractAddress, receiver: ContractAddress, py_amount: u256, min_sy_out: u256)
// Redeem PT only (post-expiry)
fn redeem_pt_post_expiry(yt: ContractAddress, receiver: ContractAddress, pt_amount: u256, min_sy_out: u256)
// Trade SY → PT
fn buy_pt_from_sy(market: ContractAddress, receiver: ContractAddress, sy_in: u256, min_pt_out: u256)
// Trade PT → SY
fn sell_pt_for_sy(market: ContractAddress, receiver: ContractAddress, pt_in: u256, min_sy_out: u256)
// Add liquidity (PT/SY pair)
fn add_liquidity(market: ContractAddress, receiver: ContractAddress, sy_desired: u256, pt_desired: u256, min_lp_out: u256)
// Remove liquidity
fn remove_liquidity(market: ContractAddress, receiver: ContractAddress, lp_amount: u256, min_sy_out: u256, min_pt_out: u256)All state changes are emitted as events:
PYCreated(yt_address, sy_address, expiry, py_index)PYMinted(yt, receiver, py_amount, sy_amount)PYRedeemed(yt, receiver, py_amount, sy_out)Swap(market, buyer, sell_token, sell_amount, buy_amount, swap_fee)LiquidityAdded(market, lp_receiver, sy_in, pt_in, lp_out)LiquidityRemoved(market, lp_provider, lp_amount, sy_out, pt_out)
| Property | Value | Notes |
|---|---|---|
| Decimals | 18 (WAD) | All token amounts use 18 decimals |
| Precision | Fixed-point WAD math | Rounding toward zero in divisions |
| Slippage Protection | Required on router calls | Caller must provide min_out parameters |
| Time Units | Seconds (u64) | Expiry expressed as Unix timestamp |
| RPC Health | Dependency | Protocol depends on Starknet RPC uptime |
| Oracle Freshness | Pragma feeds | May have staleness; check oracle timestamp |
// 1. User has SY tokens and wants to split into PT + YT
let (pt_out, yt_out) = router.mint_py_from_sy(yt, receiver, sy_amount, min_py_out);
// 2. User sells YT on secondary market, keeps PT for maturity
// (YT price = market discovery of yield rate)
// 3. At expiry, redeem PT for underlying value
let sy_out = router.redeem_pt_post_expiry(yt, receiver, pt_amount, 0);All core protocol contracts are owner-upgradeable using OpenZeppelin's components:
| Contract | Authority | Purpose |
|---|---|---|
| SY | Owner | Standardized yield wrapper logic |
| PT | Owner | Principal token redemption and expiry logic |
| YT | Owner | Yield token accrual logic |
| Market | Owner | AMM pricing curves and pool logic |
| Factory | Owner | May adjust deployment validation or add new features |
| MarketFactory | Owner | May optimize market creation or parameters |
| Router | Owner | May add new operations or improve integration |
Non-upgradeable contracts: Mocks and Faucet (test infrastructure only)
Upgrade Process:
- Only owner can invoke
upgrade(new_class_hash) - No timelock currently (subject to governance review)
- Upgrades are logged on-chain as events
Owner privileges:
- Owner can upgrade all core protocol contracts
- No pause function; no fund freezing capability
- No parameter changes affecting user funds outside of upgrades
- Governance forum: TBD (community discussion on upgrade policies)
- Potential transition to multi-sig or timelock for future upgrades
Horizon is licensed under the Business Source License 1.1 (BUSL-1.1).
| Aspect | Details |
|---|---|
| Source Visibility | Code is source-available under BUSL-1.1 restrictions until Change Date |
| Commercial Use | Restricted; usage on Horizon-deployed instances allowed immediately |
| Change Date | 2028-12-19 |
| Change License | Converts to GPL-3.0 on change date |
| Summary | Source-available with competitive protection + automatic open-source transition |
For details, see the LICENSE file.
