Swap / DEX·EVM
Perennial
Hyper-efficient perps liquidity layer on Arbitrum and Base. A two-sided LP-vs-trader model where makers provide liquidity to specific markets, with VAA-pulled Pyth prices and oracle-aware funding.
- 01perps frontends that build their own UX on top of shared liquidity
- 02vault strategies acting as makers
- 03developer-built leveraged tokens / structured products
- 04Arbitrum + Base markets with Pyth pricing
- 05low-fee derivatives integrators
- pnpm add @perennial/sdk viem
| Variable | Scope | Description |
|---|---|---|
| NEXT_PUBLIC_PERENNIAL_CHAIN_ID | Client | `42161` (Arbitrum) or `8453` (Base). |
| NEXT_PUBLIC_RPC_URL | Client | RPC for the selected chain (must support `eth_call` reliably). |
| NEXT_PUBLIC_PERENNIAL_GRAPH_URL | Client | Hosted subgraph URL — required by the SDK for trade history and stats. |
| PYTH_HERMES_URL | Client | Pyth Hermes endpoint (https://hermes.pyth.network) — Perennial pulls VAAs to settle positions. |
Use `@perennial/sdk`: `const sdk = new PerennialSDK({ chainId, rpcUrl, graphUrl, pythUrl })`. Open or modify a position with `sdk.markets.update({ market, address, sizeDelta, collateralDelta, signer })` — pass an empty `bytes` for `priceCommitment` to use the latest oracle, or fetch a Pyth VAA from Hermes and pass it for atomic settlement. Read user state via `sdk.markets.snapshots({ address, marketAddresses })` and `sdk.markets.openOrders({ address })`. Each market is a single contract; market addresses are looked up via `MarketFactory`.
- ⚑Settlement is request-based: `update()` queues a position change at the current oracle version; the actual position only updates after a Pyth price update for the next version is committed. Until then `pendingPosition` ≠ `position`.
- ⚑Funding has two components — `funding` (long/short imbalance) and `interest` (utilization). Both accrue per-block to maker LPs. UIs that show only one will under-report yield.
- ⚑Makers (LPs) can be socialized-loss when traders win net — vault providers must monitor `riskParameter.makerLimit` and the maker side's `efficiency` value.
- ⚑Liquidations require a keeper to call `update()` with a liquidation flag; small positions may sit in 'liquidatable' state briefly. Don't assume a margin breach means an instant close.
- ⚑Pyth VAA payloads are time-bounded — fetching a VAA and submitting it more than ~60 seconds later reverts with `PythErrors.StalePrice`. Always fetch immediately before sending the tx.
- ⚑Arbitrum and Base have different `MarketFactory` addresses and different listed markets — never hard-code a market address; resolve by `(asset, chainId)` from the SDK.