Skip to content

Lazer Perps: Cardano perpetual futures with Pyth Lazer price feeds#104

Open
mariaelisaaraya wants to merge 20 commits intopyth-network:mainfrom
mariaelisaaraya:lazer-perps
Open

Lazer Perps: Cardano perpetual futures with Pyth Lazer price feeds#104
mariaelisaaraya wants to merge 20 commits intopyth-network:mainfrom
mariaelisaaraya:lazer-perps

Conversation

@mariaelisaaraya
Copy link

@mariaelisaaraya mariaelisaaraya commented Mar 22, 2026

Pyth Examples Contribution

Team Name: Lazer Perps (ex Lazer RWA)
Submission Name: lazer-perps
Team Members: María Elisa Araya (@ar3lisa), Bárbara Olivera (@b4rbbb)
Contact: araya3lisa@gmail.com / brb.olivera@gmail.com

Type of Contribution

  • New Example Project
  • Bug Fix
  • Documentation Update
  • Enhancement
  • Hackathon Submission

Project Information

Project/Example Name: Lazer Perps

Pyth Product Used:

  • Pyth Price Feeds
  • Pyth Entropy
  • Multiple Products
  • Other

Blockchain/Platform:

  • Cardano
  • Ethereum/EVM
  • Solana
  • Other

Description

What does this contribution do?

Perpetual futures protocol on Cardano. Traders open leveraged long/short positions on RWA assets (Gold, Silver, Oil) using USDCx as collateral. Every action (open, close, liquidate) requires a fresh Pyth Lazer price witness in the transaction. No price → no trade.

How does it integrate with Pyth?

oracle_gate.ak calls pyth.get_updates(pyth_id, self) from pyth-network/pyth-lazer-cardano to read verified prices on-chain. All validators (open_position, close_position, liquidate, pool_manager) read prices through this gate. Off-chain, orchestrator.ts fetches signed updates from Pyth Lazer and embeds them using the zero-withdrawal pattern.

What problem does it solve or demonstrate?

Demonstrates how to build a DeFi protocol (perpetual futures) that requires fresh oracle prices for every on-chain action. The oracle gate pattern ensures no trade can happen without a verified Pyth price witness.

Validators

Validator Purpose Pyth Usage
oracle_gate.ak Reads verified Pyth price pyth.get_updates()
open_position.ak Validates entry price + leverage Entry price must match oracle
close_position.ak Settles PnL at current price Exit price from oracle
liquidate.ak Liquidation price check Current price vs liq_price
pool_manager.ak Liquidity pool + OI caps

Testing & Verification

How to Test This Contribution

cd lazer/cardano/lazer-perps
npm install
cd onchain && aiken check && aiken build && cd ..
ACCESS_TOKEN=<token> CARDANO_MNEMONIC="<24 words>" npm run open-position
ACCESS_TOKEN=<token> CARDANO_MNEMONIC="<24 words>" npm run close-position
ACCESS_TOKEN=<token> CARDANO_MNEMONIC="<24 words>" npm run liquidate
ACCESS_TOKEN=<token> npm run keeper

Prerequisites

  • Node.js v24+
  • Aiken v1.1+
  • Pyth Lazer access token
  • Cardano PreProd wallet with tADA

Verified transactions on PreProd

Type Feed Tx Hash
Open position XAUT/USD ccb967d2...
Close position XAUT/USD 6532eebc...
Liquidate XAUT/USD 477bfe3f...

Deployment Information

Deployed on Cardano PreProd testnet. Pyth Policy ID: d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6

Checklist

Code Quality

  • Code follows existing patterns in the repository
  • Proper error handling implemented
  • No hardcoded values (uses environment variables)

Testing

  • Tested locally and works as expected
  • All existing functionality still works (no breaking changes)
  • 12 unit tests passing (aiken check)
  • 0 warnings, 0 errors

Additional Context

Notes for Reviewers

Direct extension of our Lazer RWA submission. Terminal output captures are in the screenshots/ folder.

On-chain (Aiken):
- lib/lazer_perps/types.ak — Position, Direction, PerpsConfig
- lib/lazer_perps/oracle_gate.ak — pyth.get_updates() price reader
- validators/open_position.ak — entry price + leverage check
- validators/close_position.ak — PnL settlement with owner sig
- validators/liquidate.ak — margin ratio check, anyone can trigger

Off-chain (TypeScript):
- orchestrator.ts — central Pyth Lazer price witness fetcher
- open_position.ts, close_position.ts, liquidate.ts — tx builders
- keeper.ts — WebSocket price monitor for liquidation opportunities
- feeds.ts — RWA market catalog

Verified tx: dbf5336fafdab45c8ee287f9409d4e81dfee6375e0c4dd42e89a6b523c557dd5
Validators parameterized with MarketConfig (feed_id, leverage_cap,
min_collateral, pool_policy_id) — deploy per market without recompiling.

lib/lazer_perps/:
- types.ak — MarketConfig, Position, PoolState, Direction, constants
- oracle_gate.ak — pyth.get_updates() price reader (unchanged)

validators/:
- open_position.ak — entry price match + leverage cap + min collateral
- close_position.ak — owner-only close + PnL formula + oracle price
- liquidate.ak — margin ratio < 80% check + 1% keeper fee
- pool_manager.ak — liquidity pool + open interest caps + settlement
PositionDatum {
  feed_id, direction, leverage, entry_price (6 decimals),
  collateral (micro USDCx), trader_pkh, opened_at (POSIXTime)
}

Updated all validators to use PositionDatum instead of Position.
PnL now computed as: size = collateral * leverage.
New lib/lazer_perps/formulas.ak with:
- compute_pnl: dir_multiplier × collateral × leverage × (exit - entry) / entry
- compute_payout: max(0, collateral + PnL)
- liquidation_price_long: entry × (1 - 1/leverage + 0.01)
- liquidation_price_short: entry × (1 + 1/leverage - 0.01)
- is_liquidatable: Long if price <= liq_price, Short if price >= liq_price

close_position.ak now computes PnL and payout on close.
liquidate.ak now uses exact liquidation price formula instead of margin ratio.
feeds.ts — market catalog with leverage caps per market
orchestrator.ts — central module:
  - fetchPriceWitness(): signed price + Pyth state resolution
  - streamPythPrice(): WebSocket stream for keeper
  - buildOpenPositionTx/buildClosePositionTx/buildLiquidateTx
  - all builders apply zero-withdrawal price witness
open_position.ts — uses orchestrator.buildOpenPositionTx
close_position.ts — uses orchestrator.buildClosePositionTx
liquidate.ts — uses orchestrator.buildLiquidateTx
keeper.ts — streams prices, computes liq prices, detects triggers

Verified tx: 38a5e8391b36f814f1fca788f10b1bb91063e801ae29fa829829d011276000fe
Add collateral.ts — USDCx as Cardano native token (policy ID + asset name).
Uses ADA on PreProd (USDCX_POLICY_ID env var for mainnet).
All amounts in micro USDCx (6 decimals).

On-chain: CollateralToken type in MarketConfig (policy_id + asset_name).
Off-chain: formatCollateral(), parseCollateral(), collateralUnit().

Verified tx: 9b6e5352bc00c7fe9869cec9abd2aef253de09894d117d218ea0f9c0a9872eb9
Tests (all passing):
- pnl_long_profit, pnl_long_loss, pnl_short_profit, pnl_short_loss
- payout_positive, payout_zero_floor
- liq_price_long_10x, liq_price_short_10x
- liquidatable_long_yes/no, liquidatable_short_yes/no

Fix: remove unused keeper_fee_bps import from liquidate.ak
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant