# Intro to Sai

Trade, earn, burn.

Welcome to **Sai.fun**—a perpetual futures trading platform built on Nibiru. With Sai, you can open leveraged positions (long or short) with low fees and low impact trades, set limit orders, stop-losses and take-profits, or earn yield in a fully onchain trading experience.

{% hint style="info" %}
Sai is in active development with an expected launch in early 2026. The team has finalized a smart contract audit on the core protocol logic, and the web application is under active development.

In the meantime, please [follow Sai on X](https://x.com/saidotfun) to help support us.
{% endhint %}

***

## Learn About Sai

Master the fundamentals of trading on Sai and understand how the platform works.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>About Sai</strong></td><td>Discover how Sai works, its architecture, and what makes it unique in the DeFi trading landscape</td><td><a href="/pages/CyH2xJQs9yWJ1S8BYNav">/pages/CyH2xJQs9yWJ1S8BYNav</a></td></tr><tr><td><strong>Trading on Sai</strong></td><td>Learn the essentials of perpetual futures trading including execution, leverage, fees, and price impact</td><td><a href="/pages/u6aM3flqkp1cImz9fmAq">/pages/u6aM3flqkp1cImz9fmAq</a></td></tr><tr><td><strong>Sai Liquidity Positions (SLP)</strong></td><td>Understand Sai Liquidity Positions and how to earn yield by providing liquidity to the platform</td><td><a href="/pages/PBVxs79KeH4fUiTOKY7k">/pages/PBVxs79KeH4fUiTOKY7k</a></td></tr></tbody></table>

### Trading Fundamentals

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Executing Trades</strong></td><td>Master market orders, limit orders, stop-losses, and take-profit strategies</td><td><a href="/pages/N7fwuHwq5PNULl7VYrzZ">/pages/N7fwuHwq5PNULl7VYrzZ</a></td></tr><tr><td><strong>Leverage and Liquidations</strong></td><td>Understand leverage, after-hours caps, and liquidation risk management</td><td><a href="/pages/WElAKhqg7d9eZtzi6Vq7">/pages/WElAKhqg7d9eZtzi6Vq7</a></td></tr><tr><td><strong>After-Hours Leverage</strong></td><td>Learn how Sai reduces carried leverage on scheduled markets after a market close</td><td><a href="/pages/DJgN70QedkYynfabvUo2">/pages/DJgN70QedkYynfabvUo2</a></td></tr><tr><td><strong>Fees</strong></td><td>Learn about trading fees, funding rates, and how to optimize your trading costs</td><td><a href="/pages/32XBiJwJX37EmF24LdXs">/pages/32XBiJwJX37EmF24LdXs</a></td></tr><tr><td><strong>Price Impact</strong></td><td>Understand how your trade size affects execution price and how to minimize slippage</td><td><a href="/pages/h849NUOQbxqurVcPOTUr">/pages/h849NUOQbxqurVcPOTUr</a></td></tr><tr><td><strong>Trading FAQ</strong></td><td>Find answers to common questions about trading on Sai</td><td><a href="/pages/KEeEVrSNvexEgdBY2jAP">/pages/KEeEVrSNvexEgdBY2jAP</a></td></tr></tbody></table>

***

## User Guides

Step-by-step walkthroughs to help you get started and maximize your Sai experience.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Getting Started</strong></td><td>Complete collection of guides for onboarding, wallet setup, acquiring collateral, and more</td><td><a href="/pages/JjjojIyKxaiBPzzLwvtg">/pages/JjjojIyKxaiBPzzLwvtg</a></td></tr><tr><td><strong>Onboarding</strong></td><td>Your first steps to accessing and using the Sai platform</td><td><a href="/pages/XBSTGOfOT1dnbU6mEVdx">/pages/XBSTGOfOT1dnbU6mEVdx</a></td></tr><tr><td><strong>Wallet Setup</strong></td><td>Learn how to connect your wallet and prepare for trading</td><td><a href="/pages/BbtbVLwFB6D5vbzJII9H">/pages/BbtbVLwFB6D5vbzJII9H</a></td></tr><tr><td><strong>Sai Referral Program</strong></td><td>Earn rewards by referring traders and save on trading fees</td><td><a href="/pages/mWNWimKC8Kkbi71TGG7X">/pages/mWNWimKC8Kkbi71TGG7X</a></td></tr><tr><td><strong>Acquiring Collateral</strong></td><td>How to obtain the necessary collateral to start trading</td><td><a href="/pages/edjOjvxVSQA6vJ8wGBqg">/pages/edjOjvxVSQA6vJ8wGBqg</a></td></tr><tr><td><strong>Choosing a Market</strong></td><td>Select the right trading market for your strategy</td><td><a href="/pages/yHmYbz1Q1squc3GVlCW6">/pages/yHmYbz1Q1squc3GVlCW6</a></td></tr></tbody></table>

***

## For Developers

Build on Sai with comprehensive technical documentation, API references, and integration guides.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Getting Started</strong></td><td>Begin integrating with Sai - includes EVM and WASM integration guides</td><td><a href="/pages/3IWKplL3iXb4MF6ssbI2">/pages/3IWKplL3iXb4MF6ssbI2</a></td></tr><tr><td><strong>Sai Core</strong></td><td>Deep dive into the core contracts including perp contract, borrowing module, and state variables</td><td><a href="/pages/xXm6xYOvbs5w2TcVCCjU">/pages/xXm6xYOvbs5w2TcVCCjU</a></td></tr><tr><td><strong>Sai Keeper</strong></td><td>Complete GraphQL API documentation for querying positions, liquidity, oracles, fees, and more</td><td><a href="/pages/d5pWF3GfDz6aBzOofbZm">/pages/d5pWF3GfDz6aBzOofbZm</a></td></tr><tr><td><strong>Use Cases</strong></td><td>Practical integration examples including Telegram bot development</td><td><a href="/pages/CmU2yaBUSUAHpLWZ6Wnk">/pages/CmU2yaBUSUAHpLWZ6Wnk</a></td></tr></tbody></table>

### Developer Resources

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>EVM Integration Guide</strong></td><td>Connect to Sai from EVM-compatible chains and applications</td><td><a href="/pages/x6GuM73eIeBDLjPxeM3A">/pages/x6GuM73eIeBDLjPxeM3A</a></td></tr><tr><td><strong>WASM Integration Guide</strong></td><td>Integrate with Sai using CosmWasm smart contracts</td><td><a href="/pages/WKMKMdJjhQKkSZuv6kK2">/pages/WKMKMdJjhQKkSZuv6kK2</a></td></tr><tr><td><strong>Contract Addresses</strong></td><td>Reference for all deployed contract addresses on mainnet and testnet</td><td><a href="/pages/Cc4tHnR3Je2ywLa2sbbC">/pages/Cc4tHnR3Je2ywLa2sbbC</a></td></tr><tr><td><strong>Perp API</strong></td><td>Query and subscribe to perpetual futures data including positions and markets</td><td><a href="/pages/jAAxsXY92TQmt1Prwyex">/pages/jAAxsXY92TQmt1Prwyex</a></td></tr></tbody></table>

***

## Why Sai?

* **Oracle settlement**: Simple and easy price behavior so there's no internal surprises. We rely on a combination of Nibiru's Oracle, which aggregates prices from leading exchanges like Binance, Bybit, and OKX through Nibiru's decentralized validator network, and custom feeds for certain markets.
* **Leverage**: Amplify your exposure with market-specific leverage limits. Scheduled markets can apply a lower after-hours effective cap to positions carried across a market close.
* **Simple On-Chain Experience**: Manage everything in one intuitive UI, leveraging one click trading, automated order triggering, etc.
* **Custom Orders**: Market, limit, stop, and advanced triggers for Take Profits and Stop Losses.

Use the sidebar navigation to jump to any topic or type a keyword (e.g. "leverage," "limit order") in the search bar.

***

## Resources

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Community</strong></td><td>Join the Sai community on social channels and stay connected</td><td><a href="/pages/oa0XskrCEgbVlYaOnehE">/pages/oa0XskrCEgbVlYaOnehE</a></td></tr><tr><td><strong>Blog</strong></td><td>Read the latest updates, announcements, and insights from the Sai team</td><td><a href="/pages/wLLNiODRQ5ZFRokPRJvE">/pages/wLLNiODRQ5ZFRokPRJvE</a></td></tr><tr><td><strong>Legal</strong></td><td>Terms of use, disclosures, and legal documentation</td><td><a href="/pages/2QMojYUnwCa1Wutu1kei">/pages/2QMojYUnwCa1Wutu1kei</a></td></tr></tbody></table>

***

## Stay Connected

**Follow Sai for the latest updates:**

<table data-header-hidden><thead><tr><th width="244"></th><th></th></tr></thead><tbody><tr><td>X / Twitter</td><td><a href="https://x.com/SaiDotFun">@SaiDotFun</a></td></tr><tr><td>Telegram News Channel</td><td><a href="https://t.me/saidotfun">t.me/saidotfun</a></td></tr><tr><td>Instagram</td><td><a href="https://instagram.com/saidotfun">instagram.com/saidotfun</a></td></tr><tr><td>TikTok</td><td><a href="https://www.tiktok.com/@saidotfun">@saidotfun</a></td></tr><tr><td>Blog</td><td><a href="https://docs.sai.fun/blogs">Sai Blog</a></td></tr></tbody></table>


# Learn | About Sai

This section is a primer to help you understand the benefits of Sai and how it works.

## What is Sai?

Sai is a perpetuals exchange powered by Nibiru. It lets anyone trade perpetual futures on a decentralized exchange (DEX) that feels as fast and smooth as a centralized exchange. The goal is simple: give traders a clean, reliable place to get exposure while keeping everything transparent and onchain.

## Technical Overview

Sai pairs two building blocks to keep markets fair and responsive: oracles (independent price feeds that track real-world markets) and an automated market maker (AMM) (liquidity you can always trade against instead of waiting for a counterparty). Running on Nibiru provides the performance backbone needed for low-latency, CEX-like trading while retaining self-custody.

Liquidity is community directed. Sai Liquidity Positions (SLPs) seed the pools that power every market. Because SLPs choose where to allocate their liquidity, they also have decisive influence over which markets launch and grow on Sai. If SLPs back a market, it can be traded on Sai.

Passive capital can earn through the protocol’s highly capital-efficient design, giving users the flexibility to allocate when they want or stay idle, without friction.

Sai will list more than just major crypto assets, with a focus on bringing equities, commodities, and real-world assets (RWAs) onchain. The aim is to expand market access while keeping the trading experience simple, fast, and transparent.

## Wallet Setup & Collateral

You can trade on Sai using an EVM-wallet such as MetaMask, Coinbase Wallet, WalletConnect, and Rabby Wallet.

| EVM-Compatible Wallets |                                              |
| ---------------------- | -------------------------------------------- |
| MetaMask               | <https://metamask.io/>                       |
| Coinbase Wallet        | <https://www.coinbase.com/wallet>            |
| WalletConnect          | <https://walletguide.walletconnect.network/> |
| Rabby                  | <https://rabby.io/>                          |

**Collateral:**

Sai accepts USDC from any EVM-chain as well as stNIBI (Nibiru)

* Bridged USDC Address (Stargate): 0x0829F361A05D993d5CEb035cA6DF3446b060970b
* Transactions on Sai require NIBI for gas (**WNIBI**: 0x0CaCF669f8446BeCA826913a3c6B96aCD4b02a97). You can obtain WNIBI using the following options:
  * [Stargate](https://stargate.finance/?srcChain=ethereum\&srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\&dstChain=nibiru\&dstToken=0xcdA5b77E2E2268D9E09c874c1b9A4c3F07b37555) (Powered by LayerZero)
  * [Hyperlane](https://nexus.hyperlane.xyz/?token=0xa582e9e96F5D58A1202ad216E89926283a5dD056\&destination=nibiru) (Nexus Bridge)

    **If you’re transferring from a Centralized Exchange (CEX):**

    Purchase ETH, USDC, or NIBI on a CEX, then send the funds to a self-custodied wallet (e.g., Rabby, MetaMask) where you control the private keys. You can then use Stargate or Hyperlane to bridge the funds to Nibiru EVM.

    *Sai will soon offer a gasless experience, soon users will not need gas to sign or run transactions on Sai.*

{% content-ref url="/pages/u6aM3flqkp1cImz9fmAq" %}
[Trading on Sai](/learn/trading)
{% endcontent-ref %}

{% content-ref url="/pages/PBVxs79KeH4fUiTOKY7k" %}
[Sai Liquidity Positions (SLP)](/learn/slp)
{% endcontent-ref %}

## User Guides

{% content-ref url="/pages/JjjojIyKxaiBPzzLwvtg" %}
[Guides | Using Sai](/guides)
{% endcontent-ref %}

## Why Build on Sai?

Sai gives builders easy to use APIs and real time streams for prices, positions, and funding, so you can ship quickly. Multiple SDKs in common languages let your team work in JavaScript or TypeScript, Python, and more. Because Sai is EVM compatible, you can reuse familiar Solidity tooling, connect standard wallets, and compose with other EVM apps without custom logic.

Nibiru provides a high performance base layer. Transactions confirm quickly, liquidations execute promptly, and oracle reads are responsive. Oracles are available out of the box to settle PnL, manage risk, and inform any app that needs reliable market data.

Liquidity is community directed through SLPs, or Sai Liquidity Positions.

Sai features strong bridge partners such as LayerZero, Stargate, and Hyperlane, making it simple to bring assets in, reach users across chains, and onboard liquidity.

{% content-ref url="/pages/3IWKplL3iXb4MF6ssbI2" %}
[Getting Started](/for-devs/dev)
{% endcontent-ref %}

***

## Sai Updates

**Stay up to date on Sai’s socials:**

* X: <https://x.com/saidotfun>
* Telegram: <https://t.me/saidotfun>
* [Sai Blog](https://docs.sai.fun/blogs)


# Trading on Sai

Sai offers a feature-rich perpetual trading experience. This section of the documentation describes the different order types and mechanisms that define each market.

{% content-ref url="/pages/N7fwuHwq5PNULl7VYrzZ" %}
[Executing Trades](/learn/trading/executing-trades)
{% endcontent-ref %}

{% content-ref url="/pages/WElAKhqg7d9eZtzi6Vq7" %}
[Leverage and Liquidations](/learn/trading/leverage-and-liquidations)
{% endcontent-ref %}

{% content-ref url="/pages/DJgN70QedkYynfabvUo2" %}
[After-Hours Leverage](/learn/trading/after-hours-leverage)
{% endcontent-ref %}

{% content-ref url="/pages/h849NUOQbxqurVcPOTUr" %}
[Price Impact](/learn/trading/price-impact)
{% endcontent-ref %}

{% content-ref url="/pages/32XBiJwJX37EmF24LdXs" %}
[Fees](/learn/trading/fees)
{% endcontent-ref %}

{% content-ref url="/pages/KEeEVrSNvexEgdBY2jAP" %}
[FAQ](/learn/trading/faq)
{% endcontent-ref %}


# Executing Trades

Discover how traders execute orders to go long on various digital assets. These traders, called "takers", use liquidity provided by the protocol to open and close perpetual positions.

## Order types

| Order Type                              | Description                                                                                                                                                                                |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Market Order**                        | Executes at the current market price (from the oracle + potential slippage). It’s the fastest way to enter a position.                                                                     |
| **Limit Order**                         | Executes only if the market crosses a specific “better” price. Example: “Go long at 50k” (the price is 51k now, so if it dips to 50k, it triggers a buy).                                  |
| **Stop Order**                          | Executes if the market crosses a specific “worse” price. Example: "Go long at 70k” (the price is 60k now; you want to catch momentum going upward).                                        |
| Trigger Order (Stop Loss / Take Profit) | Separate from entry orders—these are conditions to **close** a position automatically if price hits a threshold. A network “keeper” must send the final transaction to trigger your order. |

> **Tip**: Market, Limit, and Stop are used for **opening** a position. A Stop Loss or Take Profit is used for **closing** a position once conditions are met.

## Open a Position

1. **Choose Market & Collateral**
   * E.g., BTC/USD with “USDC” as collateral.
2. **Select a side**
   * E.g., long/short
3. **Select Leverage**
   * Type your desired multiple. The allowed range depends on the market.
   * For scheduled markets, high leverage can be reduced later by the after-hours effective leverage cap if the position is carried through a market close.
4. **Enter Order Type**
   * Market or set a limit/stop price.
5. **Confirm**
   * The contract checks your collateral, calculates fees, and *if valid*, opens your position.

**Step-by-Step Example (Market Order, Long)**

1. **Collateral**: 500 USDC (You deposit 500 tokens into the vault).
2. **Leverage**: 5x => Position Notional = 2,500 UUSD.
3. **Click “Open Long”**
   * The protocol takes your 500 collateral, checks everything, and opens a 2,500 notional BTC/USD position at the current price (minus any slippage).
4. **Fees**: Suppose 0.1% open fee => 2.5 USDC is deducted from your margin.

## Close a Position

You can fully or partially close.

* **Market Close**: Sells (or buys back) instantly at the current price.
* **Partial Close**: Specify how much leverage/collateral to remove.
* **Trigger Close** (Stop Loss / Take Profit): If price crosses your chosen threshold, a keeper can finalize the close.

**Closing Fees** apply, so plan for that. If your trade was profitable, leftover profits are credited from the vault to your wallet. If the trade was a loss, your collateral will be reduced accordingly.

## Stop Loss & Take Profit

After opening a trade, you may set or update:

* **SL**: The price to close if the market moves against you.
* **TP**: The price to close if the market moves in your favor.

Once triggered, an external call to “TriggerTrade” finalizes it. If no one triggers it, the position remains open.


# Leverage and Liquidations

> **Tip:** If you’re new to margin trading, start with lower leverage. Watch your margin ratio closely and consider using stop-loss orders to manage downside risk.

***

## Leverage Overview

### What is Leverage?

Leverage is the ratio of your *position size* to the *collateral* you deposit.

* For example, depositing $100 to open a $500 position is `$500 / $100 = 5x leverage`.

**Key Points**

* **Higher Leverage = Higher Risk**: Even small price changes can cause large profits or losses.
* **Borrowing Fees**: With larger positions, borrowing fees accumulate faster.
* **Liquidation Threshold**: Higher leverage reduces the price cushion before forced liquidation.

**How to Set Leverage**

1. **Open a Trade**: Select your desired leverage (e.g., 3x, 5x, or another value allowed by that market) in the Sai UI.
2. **Review Position Size**: Confirm the total notional value of your trade.
3. **Check Liquidation Price**: The UI estimates your liquidation price; higher leverage brings the liquidation price closer to the current market price.
4. **Update Leverage Later**: By adding or removing collateral, you can increase or decrease your effective leverage.

> **Note**: Every increase in leverage comes with greater exposure to market swings and potentially higher borrowing fees.

### Market-Specific and After-Hours Limits

Each market has its own minimum and maximum leverage. The UI shows the available range for the market and collateral you are trading.

Some scheduled markets also have an **after-hours effective leverage cap**. If a live position is carried through a scheduled market close, Sai can reduce the position's effective leverage to that lower cap. This reduces the position size used for risk, PnL, and liquidation checks. It does not add collateral to the trade.

Read [After-Hours Leverage](/learn/trading/after-hours-leverage) for the full behavior.

### Leverage Examples

* **Example A (High Leverage)**
  * Collateral: $200
  * Leverage: 5x → Position Size = $1,000
  * A 10% price rise yields roughly a $100 profit (50% gain on collateral). A 10% price drop results in a $100 loss (50% loss on collateral).
* **Example B (Lower Leverage, More Cushion)**
  * Collateral: $200
  * Leverage: 2x → Position Size = $400
  * A 10% price drop is a $40 loss (20% of your collateral). Lower leverage means more room before liquidation.

### Adjusting Leverage Mid-Position

* **Add Collateral**: Lowers your effective leverage, providing a bigger buffer against liquidation.
* **Remove Collateral**: Increases leverage, exposing you to higher risk if the market continues to move against you.

***

## Liquidation Mechanics

### What is Liquidation?

A **liquidation** automatically closes your position when your collateral can’t cover potential losses. This protects both your remaining capital (if any) and the protocol from a negative balance.

### Liquidation Threshold

Sai uses a *maintenance margin* requirement, typically a small portion of your initial margin (e.g., 10%).

* If your total equity (`PnL + Collateral`) falls below this maintenance margin, your position becomes eligible for liquidation.
* The Sai interface normally displays a **liquidation price** to show where you’d be at risk of forced closure.

**Example**:

* $100 collateral at 5x leverage = $500 position. If maintenance margin is $10, your position can only lose $90 before liquidation is triggered. A price drop beyond that threshold triggers forced closure.

### The Liquidation Process

1. **Trigger**: If your margin ratio dips below the safe threshold, a keeper (liquidator) can call `Trigger Trade` to forcibly close your position at the current market price.
2. **Close the Position**: The system sells (or covers) your position.
3. **Distribute Remaining Collateral**: After covering losses and fees, any leftover collateral is returned to you. If losses exceed your collateral, you lose most or all of your margin.

### Avoiding Liquidations

1. **Use Lower Leverage**: Lower leverage gives you a wider price cushion.
2. **Add Collateral**: If the market moves against you, extra funds reduce your liquidation risk.
3. **Set a Stop-Loss**: Stop-loss orders let you exit earlier, often preventing liquidation and limiting losses.
4. **Monitor Borrowing Fees**: Accumulated fees can reduce your available margin and move you closer to liquidation.

***

## Pro Tips

1. **Mind Volatility**: High volatility can cause rapid price swings—be extra cautious with high leverage.
2. **Watch Borrowing Fees**: Fees accumulate over time, gradually eating into your collateral.
3. **Act Preemptively**: If you see your margin ratio dropping, adding collateral or closing part of the position can prevent a forced liquidation.
4. **Track the “Est. Liquidation Price”**: If the market price moves close to this value, you’re at high risk.

***

### 5. FAQ

**Q: What is the maximum leverage I can use in Sai?**\
A: Each market (e.g., BTC-PERP, ETH-PERP) has its own max leverage, displayed in the UI. Values like 10x, 20x, or 50x depend on that market's risk parameters. Scheduled markets can also have a lower after-hours effective leverage cap for positions carried through a market close.

**Q: How do I know if I’m near liquidation?**\
A: Check your margin ratio and the displayed liquidation price in the position details. If the market price is near or past the liquidation price, a forced close can happen any moment.

***

### 6. Next Steps

1. **Try a Test Market**: Practice opening a small leveraged trade to observe how price changes affect your margin and PnL.
2. **Read About Order Types**: Learn about limit orders, stop-losses, or advanced order types if Sai supports them, to manage risk.
3. **Join the Community**: Have more questions? Need live support? Join our Discord to connect with other traders and the Sai team.

> **Trade responsibly, and best of luck with Sai!**


# After-Hours Leverage

How Sai reduces carried leverage on scheduled markets when a position remains open across a market close.

Some Sai markets follow scheduled trading sessions. Stocks and other real-world assets can close for the day, over a weekend, or during holidays. When those markets are closed, ordinary opens and voluntary closes are blocked because Sai cannot rely on the same continuous pricing and liquidation flow.

For configured markets, Sai also applies an **after-hours effective leverage cap** to live positions that were carried through the latest market close.

## What changes for traders?

If you hold a live position through a scheduled close, the contract may treat that position as if it has lower leverage than the leverage originally stored on the trade.

For example:

```
Stored trade:       100 USDC collateral at 50x leverage
After-hours cap:    5x
Effective exposure: 100 USDC collateral at 5x leverage
```

The cap reduces position size. It does not add collateral to the trade. The stored leverage can continue to appear on the position until the contract normalizes the trade, but risk and PnL calculations use the lower effective leverage once the after-hours rule applies.

## When does the cap apply?

The cap applies only when all of these are true:

* The market is configured for after-hours leverage controls.
* The trade is a live position, not a pending limit or stop order.
* The trade was opened or last risk-modified before the latest market reopen.
* The stored leverage is higher than the configured after-hours cap.

Pending limit and stop orders are not capped because they are not live exposure until they trigger into an open position.

## Normalization

Anyone can call the normal trigger path for a position that needs after-hours normalization. Keepers usually handle this.

When normalization runs, the contract:

1. Checks whether the trade should be liquidated at the returned oracle price.
2. If liquidation is not needed, reduces stored leverage to the after-hours cap.
3. Applies the after-hours risk-trigger fee path to the position size that is removed.
4. Updates open interest so the market reflects the smaller exposure.

This action does not reopen the market and does not let traders voluntarily exit while the market is closed. It is a risk maintenance action.

{% hint style="info" %}
Normalization can reduce collateral because fees are charged on the exposure that is removed. If fees consume the remaining collateral, the position can be closed by the contract.
{% endhint %}

## What should I watch in the UI?

Check the market's displayed leverage limits before opening a trade. For scheduled markets, treat high leverage near market close as temporary exposure: if the position is carried across the close, the protocol can reduce the effective leverage to the configured after-hours cap.

The safest approach is to manage leverage before scheduled closes, especially on markets where price gaps can occur between sessions.


# Fees

Sai charges some of the lowest trading fees in the perpetual futures market. Taker fees start at a 0.05% base rate, and no fee is taken out for deposits or withdrawals. When factoring in zero gas costs and available discounts, Sai is one of the most competitive exchanges for active futures traders.

## Trading Fee Reference

| Fee Type                | Rate                                                                      | Description                                                                                                                                            |
| ----------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Taker Fee               | 0.05% (Base Rate)                                                         | Charged on the total position size for taker orders.                                                                                                   |
| Taker Fee with Referral | 0.0425%                                                                   | The default and minimum referral code discount on Sai is 15% of trading fees.                                                                          |
| Gas Fees for Trading    | 0 (Free)                                                                  | Always zero on Sai's L1, Nibiru.                                                                                                                       |
| Trigger Fee             | 0.01%                                                                     | Charged only on automated orders (Limit, Stop Loss, Take Profit) to pay for the execution cost.                                                        |
| Deposits & Withdrawals  | 0 (\*)                                                                    | Always zero on Sai's L1, Nibiru. However, gas is required to bridge before depositing.                                                                 |
| Maker Fee               | 0 (Free)                                                                  | No fee is charged for maker orders.                                                                                                                    |
| Rapid-Close Fee         | Up to \~1.15% of position size on most markets, decaying to 0 by \~12 min | Extra closing fee on positions held only briefly; paid to LPs. Some protected markets use a higher peak rate. See [Rapid-Close Fee](#rapid-close-fee). |

Standard trading fees are calculated based on your total **position size** (leverage × collateral), not just your wallet balance.

#### Contents

* [Fees on Sai](#fees-on-sai)
* [Trading Fee Reference](#trading-fee-reference)
* [Key Concepts](#key-concepts)
* [Where Fees are Distributed](#where-fees-are-distributed)
* [Fee Lifecycle](#fee-lifecycle)
  * [1. Opening a Trade](#1-opening-a-trade)
  * [2. Closing a Trade](#2-closing-a-trade)
* [Rapid-Close Fee](#rapid-close-fee)
* [Liquidation Mechanics](#liquidation-mechanics)
  * [Key Difference: Collateral vs. Position](#key-difference-collateral-vs-position)
  * [The Liquidation Penalty](#the-liquidation-penalty)
  * [Priority of Payments (The "Waterfall")](#priority-of-payments-the-waterfall)
  * [Liquidation Example](#liquidation-example)
* [Insufficient Collateral](#insufficient-collateral)

## Key Concepts

* **Base Fees:** Every trade has base percentages for taker, maker, and conditional (trigger) orders.
* **Fee Multipliers:** The base fees are adjusted by two multipliers:
  1. **Tier-based:** High-volume traders earn points, which unlock a lower fee multiplier.
  2. **Referral:** Referred traders receive a discount multiplier.
* **Effective Multiplier:** The lowest of the applicable multipliers is used to calculate the final fee.
* **Liquidation Fees:** These are a fixed percentage of the collateral and are not subject to the multipliers.

## Where Fees are Distributed

* **The Vault (LPs):** Receives 25% of all Closing Fees to support platform stability and reward Liquidity Providers for their risk.
* **The Protocol (Gov):** Receives the remaining 75% of Closing Fees and 100% of Open Fees. A portion of these fees is allocated to a pool for governance stakers.
* **Referrers:** Earn a percentage of the fees generated by their referees from the governance staker portion.
* **Keepers (Bots):** Receive 20% of the Trigger Fee for executing automated orders (Limit, Stop Loss, Take Profit); the remaining 80% goes to the Protocol.

## Fee Lifecycle

### 1. Opening a Trade

When a user opens a trade, the system performs the following steps to calculate and process fees:

1. **Determine Multiplier:** It fetches the trader's fee tier and checks for a referral discount to find the **effective fee multiplier**.
2. **Calculate Fees:** The system calculates the adjusted open and trigger fees by applying the effective multiplier to the base fees.
3. **Deduct and Distribute:** The total fee is deducted from the trader's collateral and distributed. A portion goes to the trigger service provider (if applicable), a cut is allocated to the referrer, and the net amount is added to a pool for governance stakers.

### 2. Closing a Trade

When a user closes a trade, fees are handled differently depending on whether it's a normal closure or a liquidation.

* **Normal Closure:** The system determines the effective multiplier, calculates adjusted close fees, and distributes them.
* **Liquidation:** The fee is a fixed percentage of the trader's collateral, bypassing any fee tier or referral discounts. This fee is then split between the vault and governance stakers.

## Rapid-Close Fee

Sai adds an extra fee when a position is closed very soon after it opens. The fee is largest in the first seconds of a trade and decays smoothly to zero the longer the position is held. It reaches zero after about 12 minutes, so it never touches positions held past that window.

The fee protects Liquidity Providers. Very short holds can capture the brief difference between the on-chain oracle price and the live market price at the Vault's expense; a higher fee on the fastest closes removes that edge while leaving ordinary trading untouched.

### How it is calculated

The fee is a percentage of your **position size** (leverage × collateral). The rate decays exponentially with the time the position is held:

```
fee_rate(hold) = F0 × e^(−hold / τ)
```

* `F0` is the peak rate at the moment of opening (1.15% on the global curve).
* `τ` (tau) sets how fast the rate decays (about 76 seconds).
* The rate is forced to zero once a position is held past 300 blocks, or about 12 minutes.
* The charge is capped at 50% of the collateral being closed.

With the global curve the fee is approximately:

| Hold time              | Fee (of position size) | Example at 10×        | Example at 50×        |
| ---------------------- | ---------------------- | --------------------- | --------------------- |
| 0 seconds              | 1.15%                  | 11.5% of collateral   | capped at 50%         |
| \~1 minute             | \~0.51%                | \~5.1% of collateral  | \~25.7% of collateral |
| \~2 minutes            | \~0.23%                | \~2.3% of collateral  | \~11.5% of collateral |
| \~3 minutes            | \~0.10%                | \~1.0% of collateral  | \~5.1% of collateral  |
| \~5 minutes            | \~0.02%                | \~0.2% of collateral  | \~1.0% of collateral  |
| \~7 minutes            | \~0.004%               | \~0.04% of collateral | \~0.2% of collateral  |
| \~12 minutes or longer | 0                      | 0                     | 0                     |

Because the rate applies to position size, higher leverage makes the same short hold cost a larger share of your collateral. The fee is added to the standard closing fee and is paid entirely to the Vault (LPs). Fee-tier and referral discounts do not reduce it.

### When it applies

The fee applies to voluntary closes and to Take-Profit / Stop-Loss closes, including partial closes and leverage reductions. Liquidations and after-hours risk reductions are exempt.

Most markets share the global curve. A few protected markets carry a higher peak rate `F0` of 2.117%, because a larger per-update price move gives a larger short-hold edge. The protected markets are SUI, ADA, DOGE, HYPE, and ZEC. They use the same decay speed, 300-block window, and 50% collateral cap as the global curve. The live rate for any market is queryable on-chain (see [Verifying Info Onchain](#verifying-info-onchain)).

### What this means for traders

Holding a position for a few minutes or longer makes this fee negligible or zero. Opening and closing within the same minute incurs a meaningful extra fee on close that is largest in the first seconds and falls quickly as the hold grows. In on-chain queries and trade events this fee is labelled the *short-lived-trade penalty*.

## Liquidation Mechanics

Liquidation occurs when your remaining collateral can no longer safely support your open position. At that point, the protocol force-closes the trade to protect the vault from bad debt.

### Key Difference: Collateral vs. Position

Standard trading fees are based on position size. Liquidation fees are different: they are calculated from your **remaining collateral** at the time the position is liquidated. Fee-tier and referral discounts do not apply.

### The Liquidation Penalty

When a position is liquidated, a **10% liquidation penalty** is applied to the remaining collateral. The penalty is split into two equal components:

1. **Closing Component (5% of Collateral):**
   * Compensates the system for closing the position.
   * Split: 20% goes to the Vault; 80% goes to the Protocol.
2. **Trigger Component (5% of Collateral):**
   * Rewards the liquidator or keeper that triggered the liquidation.
   * Split: 20% goes to the Liquidator; 80% goes to the Protocol.

### Priority of Payments (The "Waterfall")

If a user is liquidated and their collateral is nearly empty (insufficient to cover the full fees), the smart contract pays out in a specific priority order to protect the system:

1. **First Priority → The Vault:** The Vault gets its share first to ensure Liquidity Providers are protected.
2. **Second Priority → The Liquidator:** The bot gets its reward next.
3. **Third Priority → The Protocol:** The Government receives whatever is left.

### Liquidation Example

You are liquidated while you have $100 of collateral remaining.

* **Total Penalty Calculation:** $10.00 (10% of $100).
* **Distribution:**
  * $1.00 goes to the Vault (20% of the Closing portion).
  * $1.00 goes to the Liquidator (20% of the Trigger portion).
  * $8.00 goes to the Protocol.
* **Outcome:** The liquidation fee is paid from your remaining collateral. The rest of your collateral is used to settle the forced closure, and your position collateral is zeroed.

## Insufficient Collateral

If a trader's collateral is not sufficient to cover fees and losses, the fees are still accounted for in the internal ledgers (e.g., for governance and referrers) but are not physically transferred from the trader's collateral. The losses are absorbed by the Protocol Vault.

## Verifying Info Onchain

### Claim: Default referral code discount on trading is 15%.

Query the perp contract's raw state for the storage key `referree_base_fee_multiplier`:

```
# Sai Mainnet Perp Smart Contract
PERP='nibi1ntmw2dfvd0qnw5fnwdu9pev2hsnqfdj9ny9n0nzh2a5u8v0scflq930mph'
NODE='https://rpc.nibiru.fi:443'

KEY_HEX="$(printf referree_base_fee_multiplier | xxd -p -c 256)"

nibid query wasm contract-state raw "$PERP" "$KEY_HEX" \
  --node "$NODE" \
  --output json
```

Result:

```js
{"data":"IjAuMTUi"}
```

Then decode the base64 value:

```bash
printf 'IjAuMTUi' | base64 -d
```

Output:

```
"0.15"
```

The storage item is the referral discount amount. Here "0.15" means a 15% discount.

### Claim: The rapid-close fee curve is configured on-chain.

Query the perp contract for the live curve:

```bash
# Sai Mainnet Perp Smart Contract
PERP='nibi1ntmw2dfvd0qnw5fnwdu9pev2hsnqfdj9ny9n0nzh2a5u8v0scflq930mph'
NODE='https://rpc.nibiru.fi:443'

nibid query wasm contract-state smart "$PERP" \
  '{"get_short_lived_penalty_curve":{}}' \
  --node "$NODE" --output json
```

Result:

```json
{"data":{"f0":"0.0115","tau_blocks":"31","max_window_blocks":300,"cap_pct":"0.5"}}
```

`f0` is the global peak rate (`0.0115` = 1.15%), `tau_blocks` the decay constant in blocks, `max_window_blocks` the hold past which the fee is zero (300 blocks ≈ 12 minutes at \~2.44s/block), and `cap_pct` the cap as a fraction of collateral (`0.5` = 50%). To preview the rate for a specific market and hold time, query `get_short_lived_penalty_rate` with a `market_index` and `hold_blocks`.

Protected markets can override the global peak rate. To verify the effective `F0` for a market:

```bash
nibid query wasm contract-state smart "$PERP" \
  '{"get_short_lived_penalty_f0_for_market":{"market_index":43}}' \
  --node "$NODE" --output json
```

Market 43 (HYPE) currently returns:

```json
{"data":"0.02117"}
```


# Price Impact

### Overview

The Price Impact Module is a key component of Sai, responsible for calculating and applying price impact to trades based on their size and market depth. It ensures that larger trades have a more significant effect on the execution price, reflecting real-world market dynamics and promoting fairness in the trading system. It is applied on all position open.

### Key Features

1. **Dynamic Price Impact Calculation:** Computes price impact based on trade size and market depth.
2. **Open Interest Tracking:** Maintains a record of open interest across multiple time windows.
3. **Market Depth Management:** Allows configuration of market depth parameters for each trading pair.
4. **Time-Weighted Open Interest:** Uses a time-windowed approach to calculate cumulative open interest.
5. **Separate Handling for Long and Short Positions:** Calculates price impact differently for long and short trades.

### Price Impact Calculation

The price impact is calculated using the following general formula:

```
Price Impact = (Start OI + Trade Size / 2) / One Percent Depth
```

Where:

* **Start OI:** The starting open interest for the relevant direction (long/short)
* **Trade Size:** The size of the trade being executed
* **One Percent Depth:** The amount of trading volume required to move the price by 1%

#### Example

Let's consider a long trade with the following parameters:

* Open Price: $1000
* Trade Size: $100,000
* Current Open Interest: $500,000
* One Percent Depth: $1,000,000

1. Calculate price impact percentage:

   ```
   Price Impact % = (500,000 + 100,000 / 2) / 1,000,000 / 100 = 0.55%
   ```
2. Calculate price impact:

   ```
   Price Impact = $1000 * 0.55% = $5.50
   ```
3. Apply price impact:

   ```
   Price After Impact = $1000 + $5.50 = $1005.50
   ```


# FAQ

**Q1: How do I partially close a position?**\
A: Use **Decrease Position Size**. You specify how much collateral or leverage to reduce. The rest remains open.

**Q2: Why didn’t my limit order trigger at the exact price I set?**\
A: Slippage and real-time Oracle updates can cause execution at a slightly worse or better price. Also, an external keeper must execute the trigger.

**Q3: Who triggers my Stop Loss or Take Profit?**\
A: Anyone can call “TriggerTrade.” Typically, bots or keepers monitor the price and do it for small “trigger fees.”

**Q4: How do Borrowing Fees get charged?**\
A: Borrowing fees accrue while your trade is open and are subtracted when you close the position or reduce its size.

**Q5: I see a ‘Close Only’ or ‘Paused’ mode. What does that mean?**\
A: The protocol can temporarily restrict new positions (Close Only) or fully pause trading in emergencies. You can still close existing trades in Close Only mode.


# Sai Liquidity Positions (SLP)

Sai Liquidity Positions (SLP) vaults create a passive and automatic way to earn trading fees on Sai. By depositing into an SLP vault, liquidity providers support the perpetual markets on Sai.

## SLP Vault Mechanics

Sai employs a single-collateral model, where each SLP vault supports liquidity for a specific **deposit asset**. By pooling liquidity in this way, Sai helps ensure efficient capital utilization and deep market liquidity, benefitting both traders and liquidity providers.

For instance, markets like BTC:USD, ETH:USD, and SOL:USD that all have USDC as the deposit asset share the same SLP vault, a vault for USDC, while markets that have a deposit asset like stNIBI would have a separate SLP vault.

**SLP shares** represent your portion of the liquidity in a vault. These are regular, fungible tokens on Nibiru that can be redeemed for your proportional share of the vault's assets and accrued fees, subject to a withdrawal lock period.

## Why is there a Withdrawal Lock?

In the Sai protocol, SLP vaults, which stand as the foundational liquidity source for the platform, require a withdrawal lock to ensure the entire ecosystem remains fair, stable, and secure for everyone involved.

Think of these SLP vaults as the "house" or the collective pot of capital that enables traders to open positions on the Sai exchange app. Because they play such a vital role, the withdrawal lock acts as a necessary "safety buffer" for the following reasons:

### Preventing "Front-Running" (Fairness)

In a decentralized system, some information takes a moment to be officially recorded on the blockchain. Without a lock, a savvy user might see a large trader about to win a massive payout and try to withdraw their funds from the SLP vaults at the very last second to avoid "sharing" in that payout. The withdrawal lock ensures that everyone in the vault is on a level playing field and that no one can "game the system" by jumping out right before the vault has to fulfill its obligations to traders.

### Maintaining "House" Stability

SLP vaults are designed to always have enough "fuel" (liquidity) to support the traders on the platform. If everyone could pull their money out instantly during a period of market volatility, it could cause a "bank run" effect. The lock creates a predictable schedule, allowing the protocol to manage its reserves effectively and ensure there is always enough liquidity to keep the Sai exchange app running smoothly.

### Accurate Health Checks (Epochs)

The protocol calculates the "health" of the SLP vaults (how much they have versus how much they owe traders) in specific time chunks called Epochs. Because checking the status of every single open trade in real-time is incredibly complex, the protocol takes "snapshots" at the end of these periods. The withdrawal lock aligns your exit with these snapshots so the vault can accurately calculate exactly how much your share is worth based on the most recent, verified data.

Sai prioritizes the long-term safety of all depositors over the convenience of an instant exit.

## Risks

**Price Delta Risk**: SLPs are the counterparty to trades on each market. When traders profit, their winnings are received from Sai Liquidity Providers (SLPs). And when traders incur losses, the losses are sent to SLPs.

**Smart Contract Risk**: Collateral coming from bridged tokens has an inherent risk depending on the security of the bridge. Similarly, liquid staking tokens come from external smart contract applications. While this has not been any reason for concern thus far, we include it here for completeness.


# Guides | Using Sai

Learn Sai by example. A collection of guides and walkthroughs on how to perform common tasks on Sai.fun and Nibiru.

Sai is a very young protocol, and the web application is currently under active development. We are finalizing a smart contract audit of the core protocol code with plans to launch in early 2025.

In the meantime, please [follow Sai on X](https://x.com/saidotfun) to help support us.

## Available Guides

### Getting Started

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Onboarding</strong></td><td>Get started with Sai - your complete guide to setting up and accessing the platform for the first time</td><td><a href="/pages/XBSTGOfOT1dnbU6mEVdx">/pages/XBSTGOfOT1dnbU6mEVdx</a></td></tr><tr><td><strong>Wallet Setup</strong></td><td>Learn how to set up and connect your wallet to start trading on Sai</td><td><a href="/pages/BbtbVLwFB6D5vbzJII9H">/pages/BbtbVLwFB6D5vbzJII9H</a></td></tr><tr><td><strong>Acquiring Collateral</strong></td><td>Discover how to obtain the necessary collateral to begin trading on the platform</td><td><a href="/pages/edjOjvxVSQA6vJ8wGBqg">/pages/edjOjvxVSQA6vJ8wGBqg</a></td></tr></tbody></table>

### Trading Essentials

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Choosing a Market</strong></td><td>Understand how to select the right trading market based on your strategy and risk tolerance</td><td><a href="/pages/yHmYbz1Q1squc3GVlCW6">/pages/yHmYbz1Q1squc3GVlCW6</a></td></tr><tr><td><strong>Sai Referral</strong></td><td>Earn rewards and save on fees with Sai's referral program - invite traders and receive rewards</td><td><a href="/pages/mWNWimKC8Kkbi71TGG7X">/pages/mWNWimKC8Kkbi71TGG7X</a></td></tr></tbody></table>


# Onboarding

Welcome to Sai! This guide will walk you through the essential steps to start trading.

## Prerequisites

Before you begin, make sure you have:

* A supported wallet (see [Wallet Setup](/guides/wallet-setup))
* Collateral tokens (see [Acquiring Collateral](/guides/acquiring-collateral))
* Gas tokens for transactions

## Quick Start

1. **Connect Your Wallet** → [Wallet Setup](/guides/wallet-setup)
   * Set up your EVM or WasmVM compatible wallet
   * Configure your network for Nibiru
2. **Get Collateral** → [Acquiring Collateral](/guides/acquiring-collateral)
   * Learn how to bridge or purchase collateral tokens
   * Understand valid collateral types
3. **Choose a Market** → [Choosing a Market](/guides/choosing-market)
   * Explore available trading pairs
   * Understand oracle pricing
4. **Understand Leverage** → [Leverage & Risk Management](https://github.com/NibiruChain/sai-docs/blob/main/trading/leverage-and-liquidations.md)
   * Learn how collateral and leverage work
   * Understand liquidation mechanics

## Need Help?

* Having wallet issues? See [Wallet Setup](/guides/wallet-setup)
* Can't bridge assets? Check [Acquiring Collateral](/guides/acquiring-collateral)
* Questions about trading? Visit [FAQ](/learn/trading/faq)


# Wallet Setup

## EVM-Compatible Wallets

For trading on Sai EVM, use:

| Wallet          | Link                                         |
| --------------- | -------------------------------------------- |
| MetaMask        | <https://metamask.io/>                       |
| Coinbase Wallet | <https://www.coinbase.com/wallet>            |
| WalletConnect   | <https://walletguide.walletconnect.network/> |
| Rabby           | <https://rabby.io/>                          |

## WasmVM-Compatible Wallets

For trading on Nibiru's native chain, use:

| Wallet       | Link                           |
| ------------ | ------------------------------ |
| Keplr        | <https://www.keplr.app/>       |
| Leap         | <https://www.leapwallet.io/>   |
| Cosmostation | <https://www.cosmostation.io/> |

## Network Configuration

Ensure your wallet is configured for [Nibiru](https://nibiru.fi/docs/wallets/).


# Acquiring Collateral

Sai accepts two types of collateral:

* **USDC.e** (bridged USDC)
* **stNIBI** (staked Nibiru tokens)

You'll also need **NIBI** for gas fees to perform transactions on Sai.

***

## Getting NIBI (Gas Token)

NIBI is required for all transactions on Sai. You can acquire it through:

### Centralized Exchanges (CEX)

| Exchange | Link                                  |
| -------- | ------------------------------------- |
| ByBit    | [Buy NIBI](https://www.bybit.com/en/) |
| Kucoin   | [Buy NIBI](https://www.kucoin.com)    |
| Gate.io  | [Buy NIBI](https://www.gate.io)       |
| MEXC     | [Buy NIBI](https://www.mexc.com/)     |

### Decentralized Exchanges (DEX)

| DEX        | Network | Link                                                                                                                                                              |
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Oku        | EVM     | [Trade on Oku](https://oku.trade/swap?inputChain=nibiru\&inToken=0x0829F361A05D993d5CEb035cA6DF3446b060970b\&outToken=0x0CaCF669f8446BeCA826913a3c6B96aCD4b02a97) |
| Osmosis    | WasmVM  | [Trade on Osmosis](https://app.osmosis.zone)                                                                                                                      |
| Astrovault | WasmVM  | [Trade on Astrovault](https://astrovault.io/)                                                                                                                     |

### Bridging

| Bridge   | Link                                                                                                                                                                             |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Nibiru   | [Bridge](https://app.nibiru.fi/bridge)                                                                                                                                           |
| Stargate | [Bridge](https://stargate.finance/?srcChain=ethereum\&srcToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\&dstChain=nibiru\&dstToken=0x0829F361A05D993d5CEb035cA6DF3446b060970b) |

***

## Getting stNIBI (Staked Collateral)

stNIBI is Nibiru's liquid staking token and serves as collateral on Sai.

### Step 1: Acquire NIBI

Follow the steps above to get NIBI tokens.

### Step 2: Liquid Stake NIBI

1. Go to [app.nibiru.fi/stake](https://app.nibiru.fi/stake)
2. Connect your wallet (works with both EVM and WasmVM wallets)
3. Stake your NIBI to receive stNIBI

### Step 3: Cross-Chain Asset Conversion (Optional)

If you need to move stNIBI between EVM and WasmVM networks:

1. Go to [app.nibiru.fi/portfolio](https://app.nibiru.fi/portfolio)
2. Select the stNIBI asset from your table
3. Use the **FunToken** conversion to:
   * Convert stNIBI ERC20 (EVM) → stNIBI Bank Coin (WasmVM)
   * Convert stNIBI Bank Coin (WasmVM) → stNIBI ERC20 (EVM)

### Alternative: Trade stNIBI on DEX

Acquire stNIBI directly from decentralized exchanges:

| DEX        | Network | Link                                                                                                                                                              |
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Oku        | EVM     | [Trade stNIBI](https://oku.trade/swap?inputChain=nibiru\&inToken=0x0829F361A05D993d5CEb035cA6DF3446b060970b\&outToken=0xcA0a9Fb5FBF692fa12fD13c0A900EC56Bb3f0a7b) |
| Stargate   | EVM     | [Trade stNIBI](https://stargate.finance)                                                                                                                          |
| Astrovault | WasmVM  | [Trade stNIBI](https://astrovault.io/)                                                                                                                            |
| Skip Swap  | WasmVM  | [Trade stNIBI](https://app.skip.money)                                                                                                                            |

***

## Getting USDC.e (Bridged USDC)

USDC.e is ERC20-standard USDC bridged to Nibiru.

### Bridge from Other Chains

Use these bridging protocols to get USDC.e:

| Source        | Bridge               | Link                                                                                                                                                                                       |
| ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Base USDC     | Stargate (LayerZero) | [Bridge Base USDC](hhttps://stargate.finance/?srcChain=base\&srcToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\&dstChain=nibiru\&dstToken=0x0829F361A05D993d5CEb035cA6DF3446b060970b)    |
| Arbitrum USDC | Stargate (LayerZero) | [Bridge ARB USDC](https://stargate.finance/?srcChain=arbitrum\&srcToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831\&dstChain=nibiru\&dstToken=0x0829F361A05D993d5CEb035cA6DF3446b060970b)  |
| Avalache USDC | Stargate (LayerZero) | [Bridge AVAX USDC](https://stargate.finance/?srcChain=arbitrum\&srcToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831\&dstChain=nibiru\&dstToken=0x0829F361A05D993d5CEb035cA6DF3446b060970b) |

### Cross-Chain Asset Conversion (Optional)

If you need to move USDC.e between EVM and WasmVM networks:

1. Go to [app.nibiru.fi/portfolio](https://app.nibiru.fi/portfolio)
2. Select the USDC.e asset from your table
3. Use the **FunToken** conversion to:
   * Convert USDC.e ERC20 (EVM) → USDC bank coin (WasmVM)
   * Convert USDC bank coin (WasmVM) → USDC.e ERC20 (EVM)

***

## Quick Reference

| Collateral | Acquisition       | Staking Required | Cross-Chain Support |
| ---------- | ----------------- | ---------------- | ------------------- |
| NIBI       | CEX, DEX, Bridge  | No               | Yes (via FunToken)  |
| stNIBI     | Stake NIBI or DEX | Yes              | Yes (via FunToken)  |
| USDC.e     | Bridge or CEX     | No               | Yes (via FunToken)  |

***

## Need Help?

* Issues with bridging? Check [Wallet Setup](/guides/wallet-setup)
* Questions about staking? See [Nibiru Docs](https://nibiru.fi/docs)
* Having trouble? Visit [FAQ](/learn/trading/faq)


# Choosing a Market

## Available Markets

Sai supports various perpetual markets:

* BTC-USD (Bitcoin)
* ETH-USD (Ethereum)
* And more...

## How to Select a Market

1. Open the **sai.fun DApp**
2. Go to the **Trade** section
3. Click the **Market dropdown**
4. Select your desired market (e.g., *BTC-USD*)
5. Review the **Oracle Price** for the current quote

## Understanding Markets

Each market shows:

* **Current Price**: Live price from the oracle
* **24h Volume**: Trading activity
* **Open Interest**: Total leveraged positions


# Sai Referral Program

Earn rewards and save on fees with Sai's referral program

* [How Referrals Work on Sai](#how-referrals-work-on-sai)
  * [Overview](#overview)
  * [Earn by Inviting Traders](#earn-by-inviting-traders)
    * [How It Works](#how-it-works)
      * [1) Create a Referral Code](#1-create-a-referral-code)
      * [2) Redeem a Referral Code](#2-redeem-a-referral-code)
  * [Reward Structure](#reward-structure)
    * [Referral Reward Tiers](#referral-reward-tiers)
  * [Trading Fee Discounts](#trading-fee-discounts)
  * [Get Started](#get-started)
    * [1. Create a referral code and share it with your audience](#1-create-a-referral-code-and-share-it-with-your-audience)
    * [2. Redeem a referral code to start saving on trading fees](#2-redeem-a-referral-code-to-start-saving-on-trading-fees)
    * [3. Trade, track your activity on the leaderboard, and climb tiers over time](#3-trade-track-your-activity-on-the-leaderboard-and-climb-tiers-over-time)
  * [Need Help?](#need-help)

## Overview

Sai's referral program offers a win-win opportunity for both referrers and traders:

1. **Redeem a referral code** to receive trading fee discounts on every trade
2. **Share a referral code** to earn a percentage of trading fees generated by traders you refer

{% hint style="info" %}
**Key Benefit:** Referrers earn rewards while referred traders save on fees—everyone wins!
{% endhint %}

## Earn by Inviting Traders

When traders use your referral code to trade on Sai, you earn a share of the trading fees they generate. At the same time, traders who redeem your code receive discounted trading fees, creating a mutually beneficial relationship.

### How It Works

#### 1) Create a Referral Code

* **Create and share:** Generate a unique referral code or link and share it with your community through social media, Discord, Telegram, or any platform
* **Earn rewards:** When someone trades using your code, you automatically earn a percentage of the trading fees they pay
* **No impact on your fees:** Your own trading fees remain unaffected—you only earn rewards from referred traders' activity on Sai

{% hint style="success" %}
**Pro Tip:** The more active traders you refer, the more you earn. Focus on quality referrals who will actively trade on the platform.
{% endhint %}

#### 2) Redeem a Referral Code

* **Instant savings:** Traders who redeem a code receive a percentage discount on trading fees for every trade, up to a certain amount (e.g., $10,000,000 in total trading volume)
* **Automatic application:** The discount applies automatically to all your trades—no need to manually apply it each time
* **No volume requirements:** Unlike some platforms, the discount is not tied to your personal trading volume

## Reward Structure

Sai offers a competitive reward structure designed to incentivize platform growth and reward active community builders. When a user redeems your code, you receive a baseline reward share on the revenue generated by their trading fees.

### Referral Reward Tiers

Referrers can earn an increasing share of referred traders' fees based on their tier level:

| Tier   | Reward Rate | Eligibility           |
| ------ | ----------- | --------------------- |
| Tier 0 | 25%\*       | Entry level           |
| Tier 1 | 35%         | Active referrers      |
| Tier 2 | 40%         | High-volume referrers |
| Tier 3 | 50%         | Exclusive partners    |

{% hint style="info" %}
**Promotional rate:** The Tier 0 reward rate of 25% is promotional and will be adjusted after May 20, 2026.
{% endhint %}

{% hint style="warning" %}
**VIP Partnership:** To qualify for top-tier rewards, please contact the Sai team to discuss partnership opportunities.
{% endhint %}

## Trading Fee Discounts

Trading fee discounts **do not stack**. Traders automatically receive whichever discount is higher:

* **Volume-based discounts:** Earned from 30-day trading activity on the platform
* **Referral code discount:** 15% off trading fees when redeeming a valid referral code

This ensures you always get the best possible rate on your trades.

## Get Started

Follow these simple steps to start earning or saving today:

### 1. Create a referral code and share it with your audience

<figure><picture><source srcset="/files/WbtXjkqV4KI2frxDzCJ8" media="(prefers-color-scheme: dark)"><img src="/files/WbtXjkqV4KI2frxDzCJ8" alt="Create a referral code on Sai"></picture><figcaption><p>Navigate to the Referrals page to create your unique code</p></figcaption></figure>

### 2. Redeem a referral code to start saving on trading fees

<figure><picture><source srcset="/files/BfybrwrIC0vnuzEn8c8H" media="(prefers-color-scheme: dark)"><img src="/files/BfybrwrIC0vnuzEn8c8H" alt="Redeem a referral code on Sai"></picture><figcaption><p>Enter a referral code to unlock instant trading fee discounts</p></figcaption></figure>

### 3. Trade, track your activity on the leaderboard, and climb tiers over time

Monitor your referral performance and earnings directly from your dashboard. As your referred traders become more active, you'll automatically progress through reward tiers and earn higher rewards.

***

## Need Help?

If you have questions about the referral program or need assistance getting started, reach out to our community.

**Resources:**

* 🐦 [X / Twitter](https://x.com/SaiDotFun) - @SaiDotFun
* 📢 [Telegram News Channel](https://t.me/saidotfun) - t.me/saidotfun
* 📸 [Instagram](https://instagram.com/saidotfun) - instagram.com/saidotfun
* 🎵 [TikTok](https://www.tiktok.com/@saidotfun) - @saidotfun

***

*Last updated: February 2026*


# Getting Started

Build on Sai with comprehensive technical documentation, API references, and integration guides.

Build powerful applications on top of Sai's perpetual futures protocol. Whether you're creating trading bots, analytics dashboards, or custom integrations, this documentation will guide you through the process.

## Quick Start

Choose your integration path based on your development environment:

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>EVM Integration Guide</strong></td><td>Start here if you're building on EVM chains. Covers environment setup, contract connections, and basic operations like opening positions and managing collateral</td><td><a href="/pages/x6GuM73eIeBDLjPxeM3A">/pages/x6GuM73eIeBDLjPxeM3A</a></td></tr><tr><td><strong>WASM Integration Guide</strong></td><td>For WASM-based chains and clients. Learn integration patterns with language-specific examples and deployment guidance</td><td><a href="/pages/WKMKMdJjhQKkSZuv6kK2">/pages/WKMKMdJjhQKkSZuv6kK2</a></td></tr></tbody></table>

***

## Sai Core

Comprehensive documentation of the Sai smart contracts, modules, and architecture.

### Core Modules

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Perp Contract</strong></td><td>Main perpetual futures contract handling position management, collateral, and settlement</td><td><a href="/pages/CnCG6He36sdAh0t6mVAQ">/pages/CnCG6He36sdAh0t6mVAQ</a></td></tr><tr><td><strong>Borrowing Module</strong></td><td>Borrowed funds management, interest rates, and liquidation mechanics</td><td><a href="/pages/b5Yoh7EqOvFi8DajOqK0">/pages/b5Yoh7EqOvFi8DajOqK0</a></td></tr><tr><td><strong>Price Impact Module</strong></td><td>How large trades affect pricing, slippage calculation, and execution costs</td><td><a href="/pages/oIXiIdPoelXFFUwpf7D9">/pages/oIXiIdPoelXFFUwpf7D9</a></td></tr></tbody></table>

### Reference Documentation

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>State Variables</strong></td><td>Complete reference of all smart contract state variables and their purposes</td><td><a href="/pages/8vcCjjPMps4y8aQcoA7O">/pages/8vcCjjPMps4y8aQcoA7O</a></td></tr><tr><td><strong>Contract Addresses Reference</strong></td><td>Mainnet and testnet deployment addresses for all Sai contracts</td><td><a href="/pages/Cc4tHnR3Je2ywLa2sbbC">/pages/Cc4tHnR3Je2ywLa2sbbC</a></td></tr><tr><td><strong>WASM &#x26; EVM Integration</strong></td><td>Platform-specific details for direct smart contract integration</td><td><a href="/pages/evVIrw02t9H2IFuGuWEw">/pages/evVIrw02t9H2IFuGuWEw</a></td></tr></tbody></table>

***

## Sai Keeper

Query and subscribe to real-time data from the Sai protocol. Perfect for dashboards, bots, and analytics tools.

### Getting Started

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Core Concepts</strong></td><td>Understand indexing, subscriptions, and how Keeper structures protocol data</td><td><a href="/pages/wkWgBSrkIGBpygzvw8tl">/pages/wkWgBSrkIGBpygzvw8tl</a></td></tr><tr><td><strong>Client Setup</strong></td><td>Get your GraphQL client configured and authenticated in minutes</td><td><a href="/pages/9bUolmzdEVNrHHABKeij">/pages/9bUolmzdEVNrHHABKeij</a></td></tr></tbody></table>

### API Reference

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Perp API</strong></td><td>Query positions, orders, trades, and perpetual-specific data</td><td><a href="/pages/jAAxsXY92TQmt1Prwyex">/pages/jAAxsXY92TQmt1Prwyex</a></td></tr><tr><td><strong>Liquidity Provider (LP) API</strong></td><td>Access liquidity pool data, provider positions, and performance metrics</td><td><a href="/pages/nSP7xqSeaRwEXjWyOrYz">/pages/nSP7xqSeaRwEXjWyOrYz</a></td></tr><tr><td><strong>Oracle API</strong></td><td>Get price feeds and oracle data used throughout the protocol</td><td><a href="/pages/qtCydifRnuxRW8Y4L1SC">/pages/qtCydifRnuxRW8Y4L1SC</a></td></tr><tr><td><strong>Fees API</strong></td><td>Query fee structures, accruals, and historical fee data</td><td><a href="/pages/IW1ai9xwPD43gLU5zd4A">/pages/IW1ai9xwPD43gLU5zd4A</a></td></tr></tbody></table>

### Examples & Advanced Usage

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Examples: Queries</strong></td><td>Copy-paste ready GraphQL queries for common use cases</td><td><a href="/pages/3bJuD90rJtFUL5eL7OLe">/pages/3bJuD90rJtFUL5eL7OLe</a></td></tr><tr><td><strong>Examples: Subscriptions</strong></td><td>See how to subscribe to real-time updates and build event-driven applications</td><td><a href="/pages/DTpzlxWT14kpG0oP7Adj">/pages/DTpzlxWT14kpG0oP7Adj</a></td></tr><tr><td><strong>Filters &#x26; Pagination</strong></td><td>Master filtering, sorting, and pagination for efficient large-scale data retrieval</td><td><a href="/pages/OH8OSiYwwfzuuIXbykP1">/pages/OH8OSiYwwfzuuIXbykP1</a></td></tr></tbody></table>

***

## Use Cases

Practical integration examples and real-world applications built on Sai.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Telegram Bot Integration</strong></td><td>Build a Telegram trading bot with position monitoring, alerts, and order execution</td><td><a href="/pages/bzk5b2zHGvi61jze31O7">/pages/bzk5b2zHGvi61jze31O7</a></td></tr></tbody></table>

***

## Developer Resources

**Need help or want to connect with other builders?**

* 🐦 [X / Twitter](https://github.com/NibiruChain/sai-docs/blob/main/dev/__https:/x.com/SaiDotFun__/README.md) - @SaiDotFun
* 📢 [Telegram News Channel](https://github.com/NibiruChain/sai-docs/blob/main/dev/__https:/t.me/saidotfun__/README.md) - t.me/saidotfun
* 📸 [Instagram](https://github.com/NibiruChain/sai-docs/blob/main/dev/__https:/instagram.com/saidotfun__/README.md) - instagram.com/saidotfun
* 🎵 [TikTok](https://github.com/NibiruChain/sai-docs/blob/main/dev/__https:/www.tiktok.com/@saidotfun__/README.md) - @saidotfun
* ▶️ [YouTube](https://github.com/NibiruChain/sai-docs/blob/main/dev/__https:/www.youtube.com/@saidotfun__/README.md) - @saidotfun

**Additional Resources:**

* [Contract Addresses](/for-devs/sai-core/contracts) - Quick reference for all deployed contracts
* [GraphQL Playground](https://sai-keeper.nibiru.fi/) - Test queries interactively

***

{% hint style="success" %}
**Ready to build?** Start with the [EVM Integration Guide](/for-devs/dev/evm-guide) or [WASM Integration Guide](/for-devs/dev/wasm-guide) based on your development stack.
{% endhint %}


# EVM Integration Guide

How to drive Sai using an **EVM signer** through the EVM Interface contract. You pass a JSON‑encoded WASM message (`wasmMsgBytes`) along with token amounts/addresses.

***

## 0. Getting Started

### Prerequisites

* Node.js ≥ 18
* pnpm, npm, or yarn

### New Project

```bash
mkdir sai-evm && cd sai-evm
pnpm init -y
pnpm add ethers bignumber.js
pnpm add -D typescript ts-node @types/node dotenv
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext
```

> If you also want to call the Cosmos side in the same app, add: `@nibiruchain/nibijs @cosmjs/cosmwasm-stargate @cosmjs/stargate cosmjs-types`.

### Environment Setup

Create a `.env` file with your configuration:

```bash
# Testnet-1
EVM_RPC=https://evm-rpc.testnet-1.nibiru.fi/

# Or Mainnet
# EVM_RPC=https://evm-rpc.nibiru.fi/

PRIVATE_KEY=0x... # Your private key (keep this secret!)
INTERFACE_ADDR=0x... # Contract address from saiEvmInterfaceFromChainType(chainType)
```

### Bootstrap Provider + Signer

```ts
// src/evm.ts
import "dotenv/config"
import { ethers } from "ethers"

export function makeSigner() {
  const rpc = process.env.EVM_RPC!
  const pk = process.env.PRIVATE_KEY!
  const provider = new ethers.JsonRpcProvider(rpc)
  const signer = new ethers.Wallet(pk, provider)
  return { provider, signer }
}
```

### Interface ABI

Import the ABI that exposes these methods: `openTrade`, `executeSimpleFunctions`, `deposit`, `makeWithdrawRequest`, `redeem`, and `executeVaultSimpleFunctions`.

```ts
import PerpVaultEvmInterfaceAbi from "./PerpVaultEvmInterface.json" // Place your ABI file here
```

### Gas Estimation Helpers

```ts
const GAS_BUFFER_NUMERATOR = 11n
const GAS_BUFFER_DENOMINATOR = 10n
const FALLBACK_GAS_LIMIT = 5_000_000n
const FALLBACK_GAS_PRICE = ethers.parseUnits("1", "gwei")

const withGasBuffer = (g: bigint) =>
  g === 0n ? 1n : (g * GAS_BUFFER_NUMERATOR) / GAS_BUFFER_DENOMINATOR

async function estimateGasWithFallback(
  fn: () => Promise<bigint>,
  info?: unknown
) {
  try {
    const est = await fn()
    return { gasLimit: withGasBuffer(est) }
  } catch (error) {
    console.warn("Gas estimation failed, using fallback", info)
    return { gasLimit: FALLBACK_GAS_LIMIT, gasPrice: FALLBACK_GAS_PRICE }
  }
}

const toTxOverrides = ({
  gasLimit,
  gasPrice,
}: {
  gasLimit: bigint
  gasPrice?: bigint
}) => (gasPrice === undefined ? { gasLimit } : { gasLimit, gasPrice })
```

***

## 1. Collateral / Payment Tokens

### Decimal Handling

* **BANK units** use **6 decimals** (Cosmos side)
* **ERC‑20 units** use the token's decimals (typically 6)

### Testnet-1

| Token      | ERC-20 Address                               |
| ---------- | -------------------------------------------- |
| **USDC**   | `0xAb68f1D1d91854383fd4Df9016E3040D03e8191a` |
| **stNIBI** | `0xCae3d404AFB50016154a4B18091351065154E9bD` |

### Mainnet

| Token      | ERC-20 Address                               |
| ---------- | -------------------------------------------- |
| **USDC**   | `0x0829F361A05D993d5CEb035cA6DF3446b060970b` |
| **stNIBI** | `0xcA0a9Fb5FBF692fa12fD13c0A900EC56Bb3f0a7b` |

### Converting Between Decimals

When combining BANK and ERC-20 amounts, ensure both use BANK units (6 decimals) before summing:

```ts
const toBankUnits = (
  amt: bigint,
  sourceDecimals: number,
  bankDecimals: number = 6
): bigint => {
  if (sourceDecimals === bankDecimals) return amt
  const diff = Math.abs(sourceDecimals - bankDecimals)
  const factor = 10n ** BigInt(diff)
  return sourceDecimals > bankDecimals ? amt / factor : amt * factor
}
```

***

## 2. Perps – Open Trade

### Method Signature

```ts
contract.openTrade(
  wasmMsgBytes,           // JSON-encoded WASM message with is_evm_origin: true
  collateralIndex,        // Token index (e.g., 0 for first collateral)
  totalAmountBankUnits,   // Total amount in BANK units
  useErc20Amount,         // ERC-20 amount in token decimals
  overrides                // Gas overrides
)
```

### Example

```ts
import { ethers } from "ethers"
import BigNumber from "bignumber.js"
import PerpVaultEvmInterfaceAbi from "./PerpVaultEvmInterface.json"
import { makeSigner } from "./evm"

async function openTrade() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = {
    open_trade: {
      market_index: "MarketIndex(0)",
      leverage: "5",
      long: true,
      collateral_index: "TokenIndex(0)",
      trade_type: "trade",
      open_price: "70000",
      tp: undefined,
      sl: undefined,
      slippage_p: "1",
      is_evm_origin: true, // IMPORTANT: Set to true for EVM origin
    },
  }

  const bankDecimals = 6
  const erc20Decimals = 6
  const displayAmount = new BigNumber("100")
  const bankAmount = ethers.parseUnits(displayAmount.toString(), bankDecimals)
  const erc20Amount = ethers.parseUnits(displayAmount.toString(), erc20Decimals)

  const toBankUnits = (
    amt: bigint,
    ercDec: number,
    bankDec: number
  ): bigint => {
    if (ercDec === bankDec) return amt
    const diff = Math.abs(ercDec - bankDec)
    const factor = 10n ** BigInt(diff)
    return ercDec > bankDec ? amt / factor : amt * factor
  }

  const totalBank = bankAmount + toBankUnits(erc20Amount, erc20Decimals, bankDecimals)
  const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const gas = await estimateGasWithFallback(() =>
    iface.openTrade.estimateGas(wasmMsgBytes, 0, totalBank, erc20Amount)
  )

  const tx = await iface.openTrade(
    wasmMsgBytes,
    0,
    totalBank,
    erc20Amount,
    toTxOverrides(gas)
  )
  console.log("Transaction submitted:", tx.hash)

  const receipt = await tx.wait()
  console.log("Mined in block:", receipt?.blockNumber)
}
```

***

## 3. Perps – Close Trade

Close an open position:

```ts
async function closeTrade() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = { close_trade: { trade_index: "UserTradeIndex(0)" } }
  const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const gas = await estimateGasWithFallback(() =>
    iface.executeSimpleFunctions.estimateGas(wasmMsgBytes)
  )

  const tx = await iface.executeSimpleFunctions(wasmMsgBytes, toTxOverrides(gas))
  const receipt = await tx.wait()
  console.log("Trade closed in block:", receipt?.blockNumber)
}
```

***

## 4. Referral – Create & Redeem Codes

### Create a Referral Code

```ts
async function createReferralCode() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = { create_referrer_code: { code: "MYCODE" } }
  const bytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const gas = await estimateGasWithFallback(() =>
    iface.executeSimpleFunctions.estimateGas(bytes)
  )

  const tx = await iface.executeSimpleFunctions(bytes, toTxOverrides(gas))
  await tx.wait()
  console.log("Referral code created")
}
```

### Redeem a Referral Code

```ts
async function redeemReferralCode() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = { redeem_referrer_code: { code: "PARTNER" } }
  const bytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const gas = await estimateGasWithFallback(() =>
    iface.executeSimpleFunctions.estimateGas(bytes)
  )

  const tx = await iface.executeSimpleFunctions(bytes, toTxOverrides(gas))
  await tx.wait()
  console.log("Referral code redeemed")
}
```

***

## 5. Vault – Deposit

Deposit collateral into a vault:

```ts
async function depositToVault() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = { deposit: {} }
  const bytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const bankDecimals = 6
  const erc20Decimals = 6
  const bankAmount = ethers.parseUnits("250", bankDecimals)
  const erc20Amount = ethers.parseUnits("250", erc20Decimals)

  const toBankUnits = (
    amt: bigint,
    ercDec: number,
    bankDec: number
  ): bigint => {
    if (ercDec === bankDec) return amt
    const diff = Math.abs(ercDec - bankDec)
    const factor = 10n ** BigInt(diff)
    return ercDec > bankDec ? amt / factor : amt * factor
  }

  const totalBank = bankAmount + toBankUnits(erc20Amount, erc20Decimals, bankDecimals)

  const vaultAddr = "<bech32 vault address>"
  const collateralErc20 = "<erc20 token address>"

  const gas = await estimateGasWithFallback(() =>
    iface.deposit.estimateGas(
      bytes,
      totalBank,
      erc20Amount,
      vaultAddr,
      collateralErc20,
      true
    )
  )

  const tx = await iface.deposit(
    bytes,
    totalBank,
    erc20Amount,
    vaultAddr,
    collateralErc20,
    true,
    toTxOverrides(gas)
  )
  await tx.wait()
  console.log("Deposited to vault")
}
```

***

## 6. Vault – Make Withdraw Request

Request to withdraw funds from a vault:

```ts
async function makeWithdrawRequest() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = { make_withdraw_request: {} }
  const bytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const sharesBank = 1_000_000n // 1.0 shares in BANK units
  const sharesFromErc20 = 0n
  const totalShares = sharesBank + sharesFromErc20

  const vaultAddr = "<vault address>"

  const gas = await estimateGasWithFallback(() =>
    iface.makeWithdrawRequest.estimateGas(vaultAddr, bytes, totalShares, sharesFromErc20)
  )

  const tx = await iface.makeWithdrawRequest(
    vaultAddr,
    bytes,
    totalShares,
    sharesFromErc20,
    toTxOverrides(gas)
  )
  await tx.wait()
  console.log("Withdraw request created")
}
```

***

## 7. Vault – Redeem

Redeem shares from a vault:

```ts
async function redeemVaultShares() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = { redeem: { shares: "500000" } } // 0.5 shares
  const bytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const vaultAddr = "<vault address>"
  const sendToEvm = true

  const gas = await estimateGasWithFallback(() =>
    iface.redeem.estimateGas(bytes, vaultAddr, "500000", sendToEvm)
  )

  const tx = await iface.redeem(
    bytes,
    vaultAddr,
    "500000",
    sendToEvm,
    toTxOverrides(gas)
  )
  await tx.wait()
  console.log("Shares redeemed")
}
```

***

## 8. Vault – Cancel Withdraw Request

Cancel a pending withdraw request:

```ts
async function cancelWithdrawRequest() {
  const { signer } = makeSigner()
  const iface = new ethers.Contract(
    process.env.INTERFACE_ADDR!,
    PerpVaultEvmInterfaceAbi.abi,
    signer
  )

  const msg = { cancel_withdraw_request: { unlock_epoch: 123 } }
  const bytes = ethers.toUtf8Bytes(JSON.stringify(msg))

  const vaultAddr = "<vault address>"

  const gas = await estimateGasWithFallback(() =>
    iface.executeVaultSimpleFunctions.estimateGas(bytes, vaultAddr)
  )

  const tx = await iface.executeVaultSimpleFunctions(
    bytes,
    vaultAddr,
    toTxOverrides(gas)
  )
  await tx.wait()
  console.log("Withdraw request cancelled")
}
```

***

## Error Handling & Troubleshooting

### Common Issues

| Issue                            | Solution                                                                           |
| -------------------------------- | ---------------------------------------------------------------------------------- |
| **"No EVM signer"**              | Ensure `makeSigner()` is called and `PRIVATE_KEY` is set in `.env`                 |
| **Gas estimation fails**         | The function automatically falls back to `5,000,000` gas limit and `1 gwei` price  |
| **Transaction reverts on-chain** | Use `debug_traceTransaction` on your archive RPC to inspect the deepest call error |
| **Decimal conversion errors**    | Always convert ERC-20 and BANK amounts to the same decimal base before summing     |
| **Invalid INTERFACE\_ADDR**      | Verify the address matches your network (Testnet-1 vs Mainnet)                     |

### Best Practices

* Always use an **EVM signer** (provider alone cannot send transactions)
* Apply gas buffering to avoid "out of gas" errors
* Validate that `is_evm_origin: true` is set in all trade messages
* Convert token decimals correctly before combining BANK and ERC-20 amounts
* Store `PRIVATE_KEY` securely and never commit it to version control


# WASM Integration Guide

End-to-end instructions for interacting with **Sai** from the Cosmos (WASM) side using `NibiruTxClient`.

***

## 0. Getting Started

### Prerequisites

* Node.js ≥ 18
* pnpm, npm, or yarn

### New Project

```bash
mkdir sai-wasm && cd sai-wasm
pnpm init -y
pnpm add @nibiruchain/nibijs @cosmjs/cosmwasm-stargate @cosmjs/stargate cosmjs-types bignumber.js
pnpm add -D typescript ts-node @types/node dotenv
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext
```

### Environment Setup

Create a `.env` file with your configuration:

```bash
# Testnet-1
NIBI_RPC=https://rpc.testnet-1.nibiru.fi/

# Or Mainnet
# NIBI_RPC=https://rpc.nibiru.fi/

MNEMONIC="word1 word2 ... word12"  # Your wallet mnemonic (keep this secret!)
```

### Bootstrap Client

```ts
// src/wasmClient.ts
import "dotenv/config"
import { NibiruTxClient } from "@nibiruchain/nibijs"

export async function makeNibiruTxClient() {
  const rpc = process.env.NIBI_RPC!
  const mnemonic = process.env.MNEMONIC!
  const client = await NibiruTxClient.connectWithSignerFromMnemonic(rpc, mnemonic)
  const account = (await client.signer.getAccounts())[0]
  return { client, bech32: account.address }
}
```

***

## 1. Collateral / Payment Tokens

### Decimal Handling

Default decimals for BANK amounts are **6**. Always scale display amounts to BANK units by multiplying by 10^6.

### Testnet-1

| Token      | Bank Denom                                              |
| ---------- | ------------------------------------------------------- |
| **USDC**   | `tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/usdc`   |
| **stNIBI** | `tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/stnibi` |

### Mainnet

| Token      | Bank Denom                                                                   |
| ---------- | ---------------------------------------------------------------------------- |
| **USDC**   | `erc20/0x0829F361A05D993d5CEb035cA6DF3446b060970b`                           |
| **stNIBI** | `tf/nibi1udqqx30cw8nwjxtl4l28ym9hhrp933zlq8dqxfjzcdhvl8y24zcqpzmh8m/ampNIBI` |

### Converting Display to BANK Units

```ts
import BigNumber from "bignumber.js"

const displayAmount = new BigNumber("100") // 100 USDC
const bankAmount = displayAmount.times(1e6).integerValue().toString()
// Result: "100000000" (100 * 10^6)
```

***

## 2. Perps – Open Trade

### Message Structure

```json
{
  "open_trade": {
    "market_index": "MarketIndex(N)",
    "leverage": "string (e.g., '5')",
    "long": true,
    "collateral_index": "TokenIndex(N)",
    "trade_type": "trade|limit|stop",
    "open_price": "<market or limit price>",
    "slippage_p": "1",
    "tp": "optional take-profit",
    "sl": "optional stop-loss",
    "is_evm_origin": false
  }
}
```

### Rules

* **Market trades** require `open_price` set to the current market price
* **Limit/Stop trades** require `open_price` set to the trigger price

### Example

```ts
import BigNumber from "bignumber.js"
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"
import { makeNibiruTxClient } from "./wasmClient"

async function openTrade() {
  const { client, bech32 } = await makeNibiruTxClient()

  // Contract addresses (from chain config)
  const saiPerp = "<saiContracts.perp>" // bech32 address
  const bankDenom = "tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/usdc" // Testnet-1 USDC

  // Convert 100 USDC to BANK units
  const display = new BigNumber("100")
  const amount = display.times(1e6).integerValue().toString() // "100000000"

  const msg = {
    open_trade: {
      market_index: "MarketIndex(0)",
      leverage: "5",
      long: true,
      collateral_index: "TokenIndex(0)",
      trade_type: "trade",
      open_price: "70000",
      slippage_p: "1",
      is_evm_origin: false,
    },
  }

  const res = await client.wasmClient.execute(
    bech32,
    saiPerp,
    msg,
    "auto",
    undefined,
    [Coin.fromPartial({ denom: bankDenom, amount })]
  )

  console.log("Transaction hash:", res.transactionHash)
  console.log("Gas used:", res.gasUsed)
}
```

***

## 3. Perps – Close Trade

Close an open position:

```ts
import { makeNibiruTxClient } from "./wasmClient"

async function closeTrade() {
  const { client, bech32 } = await makeNibiruTxClient()
  const saiPerp = "<saiContracts.perp>"

  const res = await client.wasmClient.execute(
    bech32,
    saiPerp,
    {
      close_trade: {
        trade_index: "UserTradeIndex(0)",
      },
    },
    "auto"
  )

  console.log("Trade closed in tx:", res.transactionHash)
}
```

***

## 4. Referral – Create & Redeem Codes

### Create a Referral Code

```ts
async function createReferralCode() {
  const { client, bech32 } = await makeNibiruTxClient()
  const saiPerp = "<saiContracts.perp>"

  const res = await client.wasmClient.execute(
    bech32,
    saiPerp,
    {
      create_referrer_code: {
        code: "MYCODE",
      },
    },
    "auto"
  )

  console.log("Referral code created in tx:", res.transactionHash)
}
```

### Redeem a Referral Code

```ts
async function redeemReferralCode() {
  const { client, bech32 } = await makeNibiruTxClient()
  const saiPerp = "<saiContracts.perp>"

  const res = await client.wasmClient.execute(
    bech32,
    saiPerp,
    {
      redeem_referrer_code: {
        code: "PARTNER",
      },
    },
    "auto"
  )

  console.log("Referral code redeemed in tx:", res.transactionHash)
}
```

***

## 5. Vault – Deposit

Deposit collateral into a vault:

```ts
import BigNumber from "bignumber.js"
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"
import { makeNibiruTxClient } from "./wasmClient"

async function depositToVault() {
  const { client, bech32 } = await makeNibiruTxClient()

  const vaultAddr = "<vault bech32 address>"
  const bankDenom = "tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/usdc" // Testnet-1 USDC

  // Convert 250 USDC to BANK units
  const amount = new BigNumber("250")
    .times(1e6)
    .integerValue()
    .toString()

  const res = await client.wasmClient.execute(
    bech32,
    vaultAddr,
    { deposit: {} },
    "auto",
    undefined,
    [Coin.fromPartial({ denom: bankDenom, amount })]
  )

  console.log("Deposited to vault in tx:", res.transactionHash)
}
```

***

## 6. Vault – Make Withdraw Request

Request to withdraw funds from a vault:

```ts
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"
import { makeNibiruTxClient } from "./wasmClient"

async function makeWithdrawRequest() {
  const { client, bech32 } = await makeNibiruTxClient()

  const vaultAddr = "<vault bech32 address>"

  // First, query the vault to get the share denom
  const shareDenom = await client.wasmClient.queryContractSmart(vaultAddr, {
    get_vault_share_denom: {},
  })

  console.log("Share denom:", shareDenom)

  // Request to withdraw 1.0 share (1,000,000 in BANK units)
  const res = await client.wasmClient.execute(
    bech32,
    vaultAddr,
    { make_withdraw_request: {} },
    "auto",
    undefined,
    [Coin.fromPartial({ denom: shareDenom, amount: "1000000" })]
  )

  console.log("Withdraw request created in tx:", res.transactionHash)
}
```

***

## 7. Vault – Redeem

Redeem shares from a vault:

```ts
import { makeNibiruTxClient } from "./wasmClient"

async function redeemVaultShares() {
  const { client, bech32 } = await makeNibiruTxClient()

  const vaultAddr = "<vault bech32 address>"

  // Redeem 0.5 shares (500,000 in BANK units)
  const res = await client.wasmClient.execute(
    bech32,
    vaultAddr,
    {
      redeem: {
        shares: "500000",
      },
    },
    "auto"
  )

  console.log("Shares redeemed in tx:", res.transactionHash)
}
```

***

## 8. Vault – Cancel Withdraw Request

Cancel a pending withdraw request:

```ts
import { makeNibiruTxClient } from "./wasmClient"

async function cancelWithdrawRequest() {
  const { client, bech32 } = await makeNibiruTxClient()

  const vaultAddr = "<vault bech32 address>"

  const res = await client.wasmClient.execute(
    bech32,
    vaultAddr,
    {
      cancel_withdraw_request: {
        unlock_epoch: 123, // Replace with actual unlock epoch
      },
    },
    "auto"
  )

  console.log("Withdraw request cancelled in tx:", res.transactionHash)
}
```

***

## Query Examples

### Query Vault Share Denom

```ts
async function getVaultShareDenom() {
  const { client } = await makeNibiruTxClient()
  const vaultAddr = "<vault bech32 address>"

  const shareDenom = await client.wasmClient.queryContractSmart(vaultAddr, {
    get_vault_share_denom: {},
  })

  console.log("Share denom:", shareDenom)
  return shareDenom
}
```

### Query User Trades

```ts
async function getUserTrades() {
  const { client, bech32 } = await makeNibiruTxClient()
  const saiPerp = "<saiContracts.perp>"

  const trades = await client.wasmClient.queryContractSmart(saiPerp, {
    get_user_trades: {
      user: bech32,
    },
  })

  console.log("User trades:", trades)
  return trades
}
```

### Query Vault Info

```ts
async function getVaultInfo() {
  const { client } = await makeNibiruTxClient()
  const vaultAddr = "<vault bech32 address>"

  const info = await client.wasmClient.queryContractSmart(vaultAddr, {
    get_info: {},
  })

  console.log("Vault info:", info)
  return info
}
```

***

## Error Handling & Troubleshooting

### Common Issues

| Issue                         | Solution                                                                   |
| ----------------------------- | -------------------------------------------------------------------------- |
| **"No account found"**        | Ensure your mnemonic is valid and `NibiruTxClient` is properly initialized |
| **"Insufficient funds"**      | Verify you have enough tokens (in BANK units) in your wallet               |
| **"Invalid denom"**           | Double-check the `bankDenom` matches your network (Testnet-1 vs Mainnet)   |
| **"Contract not found"**      | Ensure the contract address is correct for your network                    |
| **Decimal conversion errors** | Always multiply display amounts by 10^6 before passing to WASM             |
| **Transaction timeout**       | Increase the timeout or check RPC connectivity                             |

### Best Practices

* Always ensure the `NibiruTxClient` is connected before executing transactions
* Validate that amounts are non-negative integers in BANK units
* Use `"auto"` for gas estimation unless you have specific gas requirements
* Store your mnemonic securely and never commit it to version control
* Always query contract state before making assumptions about parameters
* Test on Testnet-1 before moving to Mainnet

### Debugging Tips

* Check transaction details: `client.getTx(transactionHash)`
* Inspect error messages: Transaction results include detailed error reasons
* Use logs: Add `console.log()` statements to trace execution flow
* Monitor gas: The transaction response includes gas used and fees paid


# Sai Core

Sai's perpetual exchange app is implemented in Rust, a powerful language for secure, sandboxed programming, that compiles into Wasm bytecode. 5 key contracts manage everything on Sai, fully onchain.

### How Sai the Sai Smart Contracts Work

* **`perp` (Perpetuals trading core)**:
  * Maintains markets, positions, margin, and liquidations.
  * Calculates execution price with price impact, applies trading/borrowing/funding fees, and updates PnL.
  * Enforces risk constraints (maintenance margin, max leverage, after-hours leverage controls, funding payments).
  * Entry points (conceptually): open/close position, modify collateral, place/execute orders, liquidate under-margined positions.
  * Reads prices from `oracle`, collects/streams fees, and settles PnL against `vault`.
* **EVM interface (`PerpVaultEvmInterface.sol`)**:
  * Lets MetaMask/EVM users interact with the Wasm contracts.
  * Handles token conversion and cross-VM calls to `perp` and `vault`.
  * Mirrors the essential user flows (deposit, withdraw, open/close) for EVM wallets.
* **`vault` (Liquidity provider pool)**: [Implements SLP Vaults](https://github.com/NibiruChain/sai-docs/blob/main/dev/learn/slp.md).
  * Aggregates LP liquidity to underwrite trader PnL.
  * Issues and accounts for shares against total assets; supports deposits and epoch-based withdrawals.
  * Absorbs trader PnL: when traders win, the vault pays out; when traders lose, the vault accrues profits.
  * Coordinates with `vault-token-minter` for share token mint/burn and with `perp` for settlement transfers.
* **`oracle` (Price feed and permissions)**:
  * Stores and serves the canonical mark price per market.
  * Controls who can update prices and under what conditions.
  * Provides prices to `perp` for margin checks, execution, funding, and liquidation thresholds.
* **`vault-token-minter` (Token factory bridge for shares)**:
  * Controls minting/burning of vault share tokens via Nibiru’s token factory.
  * Enforces whitelist/ownership checks to prevent unauthorized supply changes.
  * Called by `vault` to mint on deposit and burn on withdrawal/redemption.

### Interaction model

1. **Liquidity lifecycle**
   1. LPs deposit assets into `vault` → `vault` mints shares via `vault-token-minter`.
   2. LPs request withdrawals → `vault` queues and later exits via epochs, burning shares on completion.
2. **Trading lifecycle**
   1. Trader requests an action (EVM or Wasm): open/close/modify position in `perp`.
   2. `perp` fetches price from `oracle`, computes price impact, fees, and checks margin/leverage constraints.
   3. `perp` updates position state, accrues funding/borrowing fees, and computes PnL deltas.
3. **Settlement path**
   1. Trader losses → profits accrue to `vault`.
   2. Trader gains → `perp` sources payout from `vault`.
4. **Funding and price alignment**
   1. `perp` periodically computes funding based on price deltas between mark and index; longs/shorts pay/receive accordingly.
   2. Funding transfers are accounted in positions and net settle via `vault` over time.
5. **Liquidations**
   1. If a position’s margin fraction drops below maintenance, `perp` allows a liquidator to close the position.
   2. `perp` calculates close price (with impact), applies penalties/fees, and settles PnL with `vault`.
   3. Any residual collateral after penalties is returned to the trader; bad debt is socialized to `vault` within configured limits.

#### **Access and safety**

1. Ownership/roles restrict sensitive ops: oracle price updates, mint permissions, parameter changes.
2. `perp` enforces invariants (max leverage, after-hours effective leverage, min maintenance, fee bounds).
3. `vault` enforces epoch rules and share/accounting correctness.
4. Cross-VM calls are designed to avoid re-entrancy and maintain atomic accounting on state-changing operations.

### Data and accounting at a glance

* **`perp` state**: markets, position records (size, entry price, collateral, accumulated funding/borrowing), pending orders, parameters.
* **`vault` state**: total assets, total shares, per-account share balances, withdrawal epochs/queues.
* **`oracle` state**: price per market with update metadata.
* **`vault-token-minter` state**: mint/burn permissions and token metadata.

### Typical end-to-end flows

* Open a long:
  * User calls `perp.open_position` → `perp` gets price from `oracle`, applies price impact and fees, locks collateral, records position.
* Close a long in profit:
  * `perp` computes PnL at current `oracle` price (with impact), deducts fees, and transfers profit from `vault` to user.
* LP exit:
  * User requests withdraw → `vault` queues for epoch → on execution, `vault` burns shares via `vault-token-minter` and releases assets.
* The contracts’ business roles are: `perp` (trade logic and risk), `vault` (liquidity and PnL sink/source), `oracle` (truthful prices), `vault-token-minter` (controlled share supply), with an EVM facade enabling wallet-native access.

### Sai and the EVM

Sai is written as a Multi VM app with contracts written in both Rust and Solidity. This lets the application benefit from the security benefits of working with Wasm while remaining fully capable of tapping into the flexibility and familiarity that comes with the Ethereum Virtual Machine (EVM).


# Perp Contract

An in-depth look at the Sai Perp (perpetual futures) contract implementation.

* **Events** are not covered in this guide.
* **Borrowing logic** is described thoroughly here in the [Perp: Borrowing Module](/for-devs/sai-core/perp-borrowing) specification.
* **Fees logic** is described thoroughly in [./fees/README.md](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/fees/README.md).
* **Price impact** logic is described thoroughly in [Perp: Price Impact Module](/for-devs/sai-core/perp-price-impact).
* **After-hours leverage** controls are described in [Perp: After-Hours Leverage](/for-devs/sai-core/after-hours-leverage).

***

## Table of Contents

* [Developer Documentation: Perpetual Futures Contract](#developer-documentation-perpetual-futures-contract)
  * [Table of Contents](#table-of-contents)
  * [Overview](#overview)
  * [Repository Layout](#repository-layout)
  * [Key Concepts](#key-concepts)
    * [Trade Lifecycle](#trade-lifecycle)
    * [Contract Entry Points](#contract-entry-points)
    * [Index Types](#index-types)
    * [Inter-Contract Interactions](#inter-contract-interactions)
  * [Messages (`ExecuteMsg` and `AdminExecuteMsg`)](#messages-executemsg-and-adminexecutemsg)
  * [Trade Flow in Detail](#trade-flow-in-detail)
    * [Open a Trade](#open-a-trade)
    * [Close a Trade](#close-a-trade)
    * [Modify a Trade (Stop/Limit/SL/TP/Leverage)](#modify-a-trade-stoplimitsltpleverage)
  * [Additional Modules](#additional-modules)
    * [Borrowing Reference](#borrowing-reference)
    * [Fees Reference](#fees-reference)
    * [Price Impact Reference](#price-impact-reference)
  * [Further Notes](#further-notes)

***

## Overview

This contract implements **perpetual futures** trading features in CosmWasm. It allows:

* **Opening** and **closing** of leveraged positions (long/short).
* **Limit** and **stop** orders for trade entry.
* Dynamic **stop-loss (SL)** and **take-profit (TP)** updates.
* **Partial** position modifications (increase or decrease collateral/leverage).
* Scheduled-market **after-hours leverage** controls for live positions carried through a market close.
* Automatic **borrowing fee** accrual (see [Borrowing Reference](#borrowing-reference)).
* Dynamic **fee** calculations (see [Fees Reference](#fees-reference)).
* **Price impact** adjustments based on open interest (see [Price Impact Reference](#price-impact-reference)).

***

## Repository Layout

Key files and folders relevant to this contract:

* **`./contract.rs`**
  * High-level instantiation, migration, and execution entry points.
  * Handles top-level dispatch for `ExecuteMsg::Admin { .. }`.
* **`./msgs.rs`**
  * Defines public `ExecuteMsg` messages for external users.
  * Defines admin messages (`AdminExecuteMsg`) for privileged ops.
* **`./trade.rs`**, **`./update_position_size.rs`**, **`./exec_update_leverage.rs`**
  * Main **trade** management logic: open/close trades, partial close, update SL/TP, leverage updates, and after-hours risk normalization.
* **`./borrowing/`**
  * [**Reference** here](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/borrowing/README.md).
  * Borrowing data structures, open-interest tracking, and fee accrual.
* **`./fees/`**
  * [**Reference** here](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/fees/README.md).
  * Fee calculations, tier logic, distribution of fees to vaults.
* **`./price_impact/`**
  * [**Reference** here](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/price_impact/README.md).
  * Logic to handle large trades moving the market price (slippage).
* **`./trading/state.rs`**, **`./trading/utils.rs`**
  * Core state definitions for trades (like `Trade`, `TradeInfo`, `TradingActivated`).
  * Helper methods for trade validation, order type checks, slippage logic, and exposure-limiting.

You will see references to modules such as:

* `borrowing`, `fees`, `price_impact`, `pairs`, `trading` - each in its own folder.
* `keys.rs` - containing typed indexes (e.g., `MarketIndex`, `TokenIndex`, etc.).
* `entry.rs` - collects logic for certain "entry-level" operations like opening/closing trades.

***

## Key Concepts

### Trade Lifecycle

1. **Open**
   * Create a `Trade` struct with details on leverage, collateral, direction (long/short).
   * Fees are charged on open.
2. **Position Live**
   * Accrues **borrowing fees** over time (borrowed from `borrowing` module).
   * Market can move: position PnL is conceptual, realized only on close.
3. **Close**
   * Reverse the open interest effect, finalize fees, distribute collateral to user or vault if loss.
4. **Stop/Limit/TP/SL**
   * The contract allows storing limit orders or setting stop-loss/take-profit boundaries.
   * Typically triggered by an off-chain or keeper system calling `TriggerTrade`.

### Contract Entry Points

* **`instantiate`**\
  Initializes ownership, oracle references, default settings for `OI_WINDOWS_SETTINGS`.
* **`execute`**\
  Dispatches user calls to `ExecuteMsg`. Distinguishes between normal user ops vs. admin messages.
* **`migrate`**\
  For contract upgrade logic.

### Index Types

`./keys.rs` defines typed indexes used throughout storage:

* **`MarketIndex(u16)`**: Unique ID for a trading market/pair.
* **`GroupIndex(u16)`**: Group of markets for borrowing fee grouping.
* **`TokenIndex(u16)`**: Represents a collateral token.
* **`UserTradeIndex(u64)`**: Uniquely identifies a trade for a given user.
* **`TradeInfoIndex(u64)`**: Additional info index for the same trade (like creation block, slippage settings, etc.).

These typed indexes help ensure type safety and consistent 2-byte or 8-byte key usage in storage.

### Inter-Contract Interactions

Key interactions are:

* **Oracle** (`oracle_address`)
  * Queried for `GetExchangeRate` to determine the price of base vs. quote assets.
  * Also used for collateral price in USD.
* **Vault** (`VAULT_ADDRESSES`)
  * Receives or sends collateral during partial close, liquidation, or exit.
  * Manages user margin deposits (collateral).

No direct "execute calls" are done to other modules like borrowing/fees; these modules are integrated inside this contract's logic. However, certain distributions are done via `BankMsg::Send` or calling the vault's `ExecuteMsg::ReceiveAssets`.

***

## Messages (`ExecuteMsg` and `AdminExecuteMsg`)

User commands are in [`./msgs.rs`](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/msgs.rs). Notable fields (you can see the file for all arguments):

* **`OpenTrade { market_index: MarketIndex, leverage: Decimal, long: bool, ... }`**\
  Opens a brand new position with specific parameters.
* **`CloseTrade { trade_index: UserTradeIndex }`**\
  Closes a currently open trade at market price.
* **`UpdateOpenLimitOrder { trade_index, price, tp, sl, slippage_p }`**\
  Edits the price or SL/TP for a limit order that hasn't become a live position yet.
* **`TriggerTrade { trader: String, trade_index: UserTradeIndex, order_type: PendingOrderType }`**\
  Activates a limit/stop/TP/SL/liq order if conditions are met. The contract can also route this into after-hours leverage normalization for eligible carried positions.
* **`UpdateLeverage { trade_index, new_leverage }`**\
  Adjusts leverage on an existing position (increasing or decreasing collateral).
* **`IncreasePositionSize`** / **`DecreasePositionSize`**\
  Allows partial modifications in position size or adding/removing collateral.

**Admin** operations (`AdminExecuteMsg`) revolve around:

* Setting markets (`SetMarkets`)
* Updating Oracle or Vault addresses
* Configuring fees or borrowing groups
* Configuring after-hours leverage controls through `UpdateAfterHours`
* Withdrawing protocol funds
* Pausing/trading activation toggles

These admin messages **reference** logic from:

* [Borrowing Admin Ops](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/borrowing/README.md#key-components)
* [Fees Admin Ops](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/fees/README.md#key-components)
* [Price Impact Admin Ops](https://github.com/NibiruChain/sai-docs/blob/main/sai-core/smart-contracts/src/price_impact/README.md#open-interest-management)

***

## Trade Flow in Detail

### Open a Trade

1. **User** calls **`ExecuteMsg::OpenTrade`** with parameters like:

   ```rust
   OpenTrade {
       market_index: MarketIndex(0),
       leverage: Decimal::from_str("5.0")?,
       long: true,
       collateral_index: TokenIndex(1),
       trade_type: TradeType::Trade,
       open_price: Decimal::from_str("1000")?,
       tp: Some(...),
       sl: Some(...),
       slippage_p: Decimal::from_str("0.03")?,
   }
   ```
2. **Funds** must be included in the message for the specified `collateral_index`.
3. The contract:
   * **Fetches** the user's deposit from `info.funds`.
   * **Validates** leverage, min collateral, exposure limits, slippage, etc.
   * **Charges** open fees (see [Fees module](#fees-reference)).
   * **Applies** borrowing logic to keep track of open interest (see [Borrowing module](#borrowing-reference)).
   * **Stores** the new trade in `TRADES[(User, UserTradeIndex)]`.

### Close a Trade

1. **User** calls **`CloseTrade { trade_index }`** to close an open position.
2. The contract:
   * Updates the position's final fees (borrowing + close fees).
   * Returns leftover collateral or collects more from user if losing more than stored.
   * Removes open interest from Borrowing storage.
   * Deletes or marks the trade as closed in `TRADES`.

### Modify a Trade (Stop/Limit/SL/TP/Leverage)

Several helper messages exist to alter the trade during its lifetime:

* **`TriggerTrade { order_type: PendingOrderType }`**\
  Usually invoked by keepers. This makes a limit/stop order active, triggers SL/TP or liquidation closure, or normalizes after-hours leverage when the contract determines that risk action is needed.
* **`UpdateOpenLimitOrder`**\
  Edits a not-yet-triggered limit or stop order's parameters (price, SL, TP, etc.).
* **`UpdateTp` / `UpdateSl`**\
  Adjust the take-profit or stop-loss boundaries for an already open "live" trade.
* **`UpdateLeverage { new_leverage }`**\
  Increases or decreases the position's leverage.\
  If you reduce leverage, you must send additional collateral.\
  If you increase leverage, the system returns collateral to you.

> Under the hood, these modifications recalculate fees, re-check liquidation price, update Borrowing open interest if necessary, and more.

For scheduled markets with after-hours controls, leverage updates, SL/TP updates, and position-size increases first materialize any required after-hours normalization. See [Perp: After-Hours Leverage](/for-devs/sai-core/after-hours-leverage).

***

## Additional Modules

Below are references to the specialized submodules for advanced logic:

### Borrowing Reference

See **`./borrowing/README.md`** for how:

* Borrowing fees accumulate over time.
* The system enforces maximum open interest (OI) constraints on a **Group** or **Pair** basis.
* Each trade references a **BorrowingPairGroup** for historical group changes.

### Fees Reference

See **`./fees/README.md`** for how:

* Fees are computed on open, close, partial close, trigger orders, and liquidation.
* Fees are distributed among governance, trigger reward, and vault.
* Fee tiers reduce fees for high-volume traders.

### Price Impact Reference

See **`./price_impact/README.md`** for how:

* `PAIR_DEPTHS` sets the "1% market depth" for each pair (above/below).
* Large trades face an execution price penalty or improvement.
* Time-windowed open interest approach is used to smooth out short-term spikes.

***

## Further Notes

* **Contract Ownership**:\
  Updated via nibiru-ownable's `UpdateOwnership` admin message.
* **Validation**:\
  Full validations across multiple modules ensure no negative exposure or insufficient collateral.
* **Extensibility**:\
  Additional messages or fee logic can be integrated by referencing the same approach for typed storage, slippage checks, and Borrowing/Fees hooks.

That covers the **core** perpetual futures logic in this repository. For further details on borrowing, fees, or price impact calculations, please **reference** their dedicated READMEs in `./borrowing`, `./fees`, and `./price_impact`.


# Perp: Borrowing Module

Borrowing fees and groups in the Sai Perp (perpetual futures) contract.

The **Borrowing Module** is a crucial part of the perpetual futures trading system. Its primary responsibilities include:

* **Calculating and accumulating borrowing fees** for leveraged positions.
* **Tracking open interest (OI)** for trading pairs and borrowing groups.
* **Enforcing exposure limits** to ensure trading activity remains within defined thresholds.

This document explains how the borrowing process works—from how **borrowing groups** are structured to the logic that updates them. It also covers the lifecycle of borrowing fees, from when a trade is opened until it is closed or liquidated.

***

## Key Components

### Data Structures

1. **BorrowingData**
   * **Purpose**: Stores fee rates and the accumulated fees for both pairs and groups.
   * **Fields**:
     * `fee_per_block`: Base fee rate per block.
     * `acc_fee_long`: Accumulated fee for long positions.
     * `acc_fee_short`: Accumulated fee for short positions.
     * `acc_last_updated_block`: Block number of the most recent fee update.
     * `fee_exponent`: Adjusts sensitivity for fee calculation based on open interest imbalances.
2. **BorrowingPairGroup**
   * **Purpose**: Captures the historical group associations for borrowing pairs.
   * **Fields**:
     * `group_index`: Identifier for the borrowing group.
     * `block`: Block number at which the group association was set.
     * `initial_acc_fee_long`: Initial accumulated fee for long positions.
     * `initial_acc_fee_short`: Initial accumulated fee for short positions.
     * `prev_group_acc_fee_long`: Previous group's accumulated fee for long positions.
     * `prev_group_acc_fee_short`: Previous group's accumulated fee for short positions.
     * `pair_acc_fee_long`: Accumulated fee for the pair's long positions at the time of group change.
     * `pair_acc_fee_short`: Accumulated fee for the pair's short positions at the time of group change.
3. **OpenInterest**
   * **Purpose**: Tracks open interest (total position sizes) for both pairs and groups.
   * **Fields**:
     * `long`: Total open interest for long positions.
     * `short`: Total open interest for short positions.
     * `max`: Maximum allowable open interest.
4. **BorrowingInitialAccFees**
   * **Purpose**: Records initial accumulated fees for a trade at the moment it opens.
   * **Fields**:
     * `acc_pair_fee`: Pair's accumulated fee when the trade opened.
     * `acc_group_fee`: Group's accumulated fee when the trade opened.
     * `block`: Block number when the trade was opened.

### Storage Maps

* **`PAIRS`**: Stores `BorrowingData` for each trading pair.
* **`PAIR_GROUPS`**: Stores a list of `BorrowingPairGroup` records for each pair (capturing historical group changes).
* **`PAIR_OIS`**: Stores `OpenInterest` for each pair.
* **`GROUPS`**: Stores `BorrowingData` for each borrowing group.
* **`GROUP_OIS`**: Stores `OpenInterest` for each borrowing group.
* **`INITIAL_ACC_FEES`**: Stores `BorrowingInitialAccFees` for each trade.

***

## Borrowing Groups

### What is a Borrowing Group?

A **Borrowing Group** is a set of trading pairs that share common borrowing parameters. It simplifies fee management and exposure control by allowing:

* **Collective fee adjustments**: Update borrowing fees for multiple pairs simultaneously.
* **Shared exposure limits**: Manage cumulative open interest across all pairs in the group.

### Role of Borrowing Groups

* **Fee Adjustments**: Updates borrowing fees in response to market conditions—affecting all pairs in the group at once.
* **Exposure Control**: Aggregates open interest to ensure the group's overall risk remains within acceptable limits.
* **Historical Fee Tracking**: Logs when pairs change groups so that fee calculations remain accurate for open trades.

***

## How Borrowing Groups Are Updated

### Setting Borrowing Group Parameters

Use `set_borrowing_group_params` to create or update a borrowing group's parameters:

* **Key Parameters**:
  * `fee_per_block`: Base borrowing fee rate per block.
  * `fee_exponent`: Exponent to scale fees based on open interest imbalances.
  * `max_oi`: Maximum allowable open interest for the group.
  * `current_block`: Current block number (for fee updates).
* **Process**:
  1. **Validation**: Ensures `fee_exponent` is within allowed bounds (1% to 300%).
  2. **Update Accumulated Fees**: Calls `set_group_pending_acc_fees` to synchronize the group's fees up to `current_block`.
  3. **Store New Data**: Updates the group's parameters in `GROUPS`.
  4. **Adjust Open Interest Limit**: Sets `max_oi` in `GROUP_OIS`.

### Changing a Pair's Borrowing Group

When calling `set_borrowing_pair_params` to place a pair in a new borrowing group:

* **Key Parameters**:
  * `group_index`: The new group index to assign to the pair.
* **Process**:
  1. **Fee Synchronization**: Updates fees for both the old and new groups via `set_group_pending_acc_fees`.
  2. **Open Interest Update**: Removes the pair's open interest from the old group and adds it to the new group using `update_group_oi`.
  3. **Record Group Change**: Appends a new `BorrowingPairGroup` entry to `PAIR_GROUPS`, capturing relevant fee data and the block number.
  4. **Update Pair Data**: Adjusts any pair-specific borrowing parameters in `PAIRS`.

### Updating Open Interest

Open interest is recalculated whenever trades open or close:

* **Key Functions**:
  * `update_pair_oi`: Updates pair-level open interest in `PAIR_OIS`.
  * `update_group_oi`: Updates group-level open interest in `GROUP_OIS`.
* **Process**:
  * **Opening a Trade**: Increases the `long` or `short` OI depending on the trade's direction.
  * **Closing a Trade**: Decreases the `long` or `short` OI accordingly.

***

## Logic Behind Updates

### Accumulating Borrowing Fees

Borrowing fees grow over time, affected by OI imbalances:

* **Fee Formula**:

  ```
  delta = fee_per_block * (current_block - acc_last_updated_block) * (net_oi / max_oi) ^ fee_exponent
  ```

  * `fee_per_block`: Base fee rate per block.
  * `current_block - acc_last_updated_block`: Blocks elapsed since the last fee update.
  * `net_oi`: Absolute difference between `long` and `short` OI.
  * `max_oi`: Maximum allowed open interest (for the pair or group).
  * `fee_exponent`: Exponent that increases fees exponentially with larger OI imbalances.
* **Side-Specific Fee Accumulation**:
  * Fees accrue only on the side (long or short) that has the larger open interest, incentivizing a more balanced market.

### Handling Trades

#### Opening a Trade

When a new trade is opened:

* **Function**: `handle_trade_borrowing`
  * **Fee Synchronization**: Calls `set_pair_pending_acc_fees` and `set_group_pending_acc_fees` to update fees.
  * **Open Interest**: Updates pair and group OI via `update_pair_oi` and `update_group_oi`.
  * **Initial Fee Recording**: Invokes `reset_trade_borrowing_fees` to log the accumulated fees at the time the trade opens.

#### Closing a Trade

When an existing trade closes:

* **Function**: `handle_trade_borrowing`
  * **Fee Synchronization**: Updates fees up to the current block.
  * **Open Interest**: Reduces the pair and group OI (long or short) accordingly.
* **Borrowing Fee Calculation**:
  * **Function**: `get_trade_borrowing_fees`
    * Computes the difference between the fees at closure vs. the `INITIAL_ACC_FEES` from trade open.
    * Accounts for any group changes that happened during the trade.

### Adjusting for Group Changes

If a pair moves to a new group while a trade is open:

* **Challenge**: The position's borrowing fees must reflect fees from both the old group and the new group.
* **Solution**:
  * Use `get_borrowing_pair_group_acc_fees_deltas` to determine the fee delta across group transitions.
  * Sum the larger of (pair delta, group delta) for each period to get total fees.

### Calculating Liquidation Price with Fees

To calculate liquidation prices, including borrowing fees:

* **Function**: `get_trade_liquidation_price_with_fees`
  * **Includes**:
    * Closing fees (e.g., close fee, trigger order fee)
    * Borrowing fees from `get_trade_borrowing_fees`
  * **Purpose**: Computes the final liquidation price after deducting all fees.

***

## Process Flow Summary

A high-level look at how trades interact with the Borrowing Module:

1. **Trade Opening**
   * **User Action**: Opens a new trade.
   * **System**:
     1. Uses `handle_trade_borrowing` to sync fees and update OI.
     2. Stores initial accrued fees in `INITIAL_ACC_FEES`.
2. **During Trade**
   * **System**:
     * Continues to accumulate fees over time (not updated in storage unless an event triggers an update, like another trade).
3. **Trade Closing**
   * **User Action**: Closes an existing trade.
   * **System**:
     1. Calls `handle_trade_borrowing` to update fees and OI.
     2. Uses `get_trade_borrowing_fees` to compute the total borrowing fees.
     3. Finalizes the trade, deducting any closing fees and borrowing fees.
4. **Parameter Updates**
   * **Admin Action**: Updates group or pair parameters.
   * **System**:
     1. Syncs fees up to the current block.
     2. Updates open interest if a pair's group changes.

***

## Detailed Function Explanations

### `handle_trade_borrowing`

**Purpose**: Coordinates the borrowing logic whenever a trade opens or closes.

**Workflow**:

1. **Pending Fees Update**: Invokes `set_pair_pending_acc_fees` and `set_group_pending_acc_fees`.
2. **Open Interest Update**: Adjusts OI via `update_pair_oi` and `update_group_oi`.
3. **Reset Initial Fees** (for new trades): Stores current fees in `INITIAL_ACC_FEES`.

### `get_trade_borrowing_fees`

**Purpose**: Calculates total borrowing fees for a trade upon closure.

**Workflow**:

1. **Load Initial Fees**: Retrieves `INITIAL_ACC_FEES` for the trade.
2. **Compute Current Fees**: Uses `get_direction_borrowing_pair_pending_acc_fees` and `get_direction_borrowing_group_pending_acc_fees`.
3. **Group Transition Handling**: Applies `get_borrowing_pair_group_acc_fees_deltas` for fee periods affected by group changes.
4. **Final Fee Calculation**: `(Current Acc Fees - Initial Acc Fees) * (Trade Collateral * Leverage)`.

### `get_borrowing_pending_acc_fees`

**Purpose**: Calculates the additional fees that accumulated between the last update and the current block.

**Workflow**:

1. **Identify OI Imbalance**: Determines whether long or short positions exceed the other.
2. **Calculate Fee Delta**: Uses the fee formula to calculate newly accrued fees.
3. **Add Fee Delta**: Applies the calculated delta to either `acc_fee_long` or `acc_fee_short`.

### `reset_trade_borrowing_fees`

**Purpose**: Logs the borrow-related fees at the moment a trade is opened.

**Workflow**:

* Saves the current `BorrowingData` for both pair and group into `INITIAL_ACC_FEES`, including the current block number.

***

## Example Scenario

Consider a pair that transitions from **Group A** to **Group B** while a trade is open:

1. **Block 100**
   * **Trade Opened**:
     * Trader opens a long position on Pair X.
     * Pair X is in **Group A**.
     * Accumulated Fees: 2% for the pair, 1% for the group.
     * Initial fees are recorded in `INITIAL_ACC_FEES`.
2. **Block 150**
   * **Group Change**:
     * Pair X is moved from **Group A** to **Group B**.
     * System updates all relevant fees for Group A up to Block 150, then assigns Pair X to Group B.
     * The group transition is recorded in `PAIR_GROUPS`.
3. **Block 200**
   * **Trade Closed**:
     * System updates fees for Pair X and Group B up to Block 200.
     * It calculates borrowing fees via `get_trade_borrowing_fees`:
       * Considers the 2%/1% from **Group A** until Block 150.
       * Includes the new rates from **Group B** from Block 150 to 200.
     * Final settlement occurs, including close fees and borrowing fees.

***

## Visualization of Logic Flow

```mermaid
graph TD

A[Open Trade] --> B[handle_trade_borrowing]
B --> C[Set Pending Accumulated Fees for Pair and Group]
B --> D[Update Open Interest for Pair and Group]
B --> E[reset_trade_borrowing_fees]

E --> G[Store Initial Accumulated Fees]

subgraph Pair and Group Fees
C --> F[get_borrowing_pending_acc_fees]
end

subgraph Open Interest Updates
D --> H[update_pair_oi and update_group_oi]
end
```

***

## Key Takeaways

The Borrowing Module is vital to mastering the perpetual futures platform. It ensures accurate fee accrual, consistent open interest tracking, and robust exposure management.

* **Borrowing Groups**: Centralize exposure limits and streamline fee adjustments for multiple pairs.
* **Accumulated Fees**: Updated based on time, OI imbalances, and fee parameters.
* **Open Interest**: Tracked at both the pair and group level, essential for risk control.
* **Trade Lifecycle**:
  * **Opening** a trade sets the initial fees.
  * **Closing** a trade calculates total borrowing fees and updates open interest.

By carefully managing how trades interact with pairs and borrowing groups, the module ensures fairness, clarity, and security for leveraged traders and the platform as a whole.


# Perp: After-Hours Leverage

Contract behavior for scheduled-market after-hours effective leverage controls.

Sai Perp supports an after-hours risk control for scheduled markets. It reduces effective exposure for live positions carried through a market close, where ordinary trading, continuous liquidation, and fresh exit pricing are not available in the same way as during open market hours.

This page reflects the behavior merged in `sai-perps` main through the after-hours leverage rollout and follow-up fixes.

## Product Rule

Markets enabled in `AFTER_HOURS_MARKETS` use the global cap stored in `AFTER_HOURS_LEVERAGE` for live trades that were carried through the latest scheduled close.

```
effective_leverage = min(Trade.leverage, AFTER_HOURS_LEVERAGE)
```

The cap applies to filled `TradeType::Trade` positions only. Pending limit or stop orders are not clamped because they are not live exposure until triggered.

Ordinary user opens and voluntary closes remain blocked while a market is closed. The after-hours trigger path is a maintenance action: it can liquidate or normalize risk, but it does not reopen the market for discretionary exits.

## State

The relevant storage is:

| State                          | Type                     | Purpose                                                          |
| ------------------------------ | ------------------------ | ---------------------------------------------------------------- |
| `AFTER_HOURS_LEVERAGE`         | `Item<Decimal>`          | Global effective leverage cap for configured after-hours markets |
| `AFTER_HOURS_MARKETS`          | `Map<MarketIndex, bool>` | Markets where the after-hours cap applies                        |
| `MarketInfo.block_last_closed` | `Option<u64>`            | Block where the market last entered `Closed`                     |
| `MarketInfo.block_last_opened` | `Option<u64>`            | Block where the market last re-entered `Open`                    |
| `MarketInfo.last_close_price`  | `Option<Decimal>`        | Best-effort close snapshot kept as market metadata               |

`last_close_price` is not the settlement price for after-hours risk checks. After-hours liquidation and normalization use the oracle price returned by the normal price query.

## Eligibility

`get_effective_leverage` returns stored `Trade.leverage` unless all of these are true:

* The trade is open and has `trade_type == TradeType::Trade`.
* The market is enabled in `AFTER_HOURS_MARKETS`.
* `block_last_closed` and `block_last_opened` are initialized.
* The trade was not opened or risk-modified in the latest reopen block or later.
* `Trade.leverage` is above `AFTER_HOURS_LEVERAGE`.

The reopen-block boundary is inclusive: a trade with `TradeInfo.created_block >= MarketInfo.block_last_opened` keeps its stored leverage.

## Trigger Behavior

The contract can route `TriggerTrade` to after-hours risk handling. The after-hours path:

1. Loads the returned oracle price from the normal price query.
2. Checks liquidation first at the stored-leverage liquidation price.
3. If liquidation is required, follows the liquidation close path.
4. If liquidation is not required, reduces stored leverage to the effective cap.
5. Calls `process_closing_fees` on the removed position-size delta with order type `after_hours_risk_trigger`.
6. Updates open interest and emits `sai/perp/trigger_trade/after_hours`.

No catalyst reward is paid on normalization. The caller is helping synchronize risk, not executing a trader's TP/SL order.

If the normalization fee consumes the remaining collateral, the contract closes the trade after the fee and risk updates.

## Oracle Freshness

After-hours liquidation and normalization intentionally use the returned oracle price even when the market has been closed long enough for the normal freshness window to expire. The narrow exception exists so high-leverage carried exposure can still be reduced while scheduled markets are closed.

Ordinary opens, voluntary closes, and normal TP/SL trigger paths still use the normal market-state and freshness guards. They do not fall back to `last_close_price`.

## Query Semantics

Contract queries preserve stored state and expose derived risk state:

* `get_trade` returns stored `Trade`, including stored `Trade.leverage`.
* `get_trade_data` returns `needs_after_hours_trigger: true` when the trade is eligible for after-hours normalization.
* `get_trade_pnl` evaluates carried trades using effective leverage.
* `GetAfterHoursLeverage`, `IsAfterHoursMarket`, and `ListAfterHoursMarkets` expose the active cap and enabled markets.

Clients should not infer after-hours state from stored leverage alone. Use the derived query fields when deciding whether a keeper or UI refresh should submit `TriggerTrade`.

## Lazy Cleanup Before Mutations

When a user later mutates a carried trade whose stored leverage is above the after-hours effective cap, the contract first materializes the after-hours normalization. The requested mutation then operates on the normalized trade.

This guard runs before leverage updates, TP/SL updates, and position-size increases. Position-size decreases still prioritize liquidation checks before the resize proceeds.


# Perp: Price Impact Module

The Price Impact Module calculates and applies price impact to trades based on their size and market depth. This module ensures that larger trades more significantly affect the execution price, reflec

## Key Features

* **Dynamic Calculation:** Computes price impact based on trade size and market depth.
* **Open Interest Tracking:** Records open interest across multiple time windows.
* **Market Depth Management:** Allows configuration of market depth parameters per trading pair.
* **Time-Weighted Open Interest:** Uses a time-windowed approach to calculate cumulative open interest.
* **Long/Short Handling:** Calculates price impact differently for long and short trades.

## Components

### State

* `OI_WINDOWS_SETTINGS`: Settings for open interest windows.
* `WINDOWS`: Stores open interest data for specific time windows.
* `PAIR_DEPTHS`: Market depth information for trading pairs.
* `TRADE_LAST_WINDOW_OI`: Tracks the last window's open interest for trades.

### Functions

* `get_trade_price_impact`: Calculates price impact for a trade.
* `add_price_impact_open_interest`: Updates open interest when a trade is opened.
* `remove_price_impact_open_interest`: Updates open interest when a trade is closed.
* `get_price_impact_oi`: Retrieves current price impact open interest.

## Price Impact Calculation

The price impact is calculated using this formula:

$$Price\ Impact\ % = \frac{(Start\ OI + \frac{Trade\ Size}{2})}{One\ Percent\ Depth}$$

* **Start OI**: Starting open interest for the relevant direction (long/short).
* **Trade Size**: Size of the trade.
* **One Percent Depth**: Trading volume needed to move the price by 1%.

### Calculation Process

1. **Fetch Market Depth**: The module retrieves the market depth from `PAIR_DEPTHS`.
2. **Calculate Current Open Interest**: It sums open interest across multiple time windows, giving more weight to recent windows.
3. **Compute Price Impact Percentage**:

   ```rust
   let price_impact_p = Decimal::from_ratio(
       start_open_interest_usd + trade_open_interest_usd.checked_div(2_u64.into())?,
       one_percent_depth_usd
   ).checked_div(Decimal::from_atomics(100_u64, 0)?)?;
   ```
4. **Apply Price Impact**: The calculated impact is applied to the trade's execution price:

   ```rust
   let price_after_impact = if long {
       open_price + price_impact
   } else {
       open_price - price_impact
   };
   ```

### Example

Let's look at a long trade with these values:

* **Open Price**: $1000
* **Trade Size**: $100,000
* **Current Open Interest**: $500,000
* **One Percent Depth**: $1,000,000

1. **Calculate price impact percentage**:

   ```
   Price Impact % = (500,000 + 100,000 / 2) / 1,000,000 / 100 = 0.55%
   ```
2. **Calculate price impact**:

   ```
   Price Impact = $1000 * 0.55% = $5.50
   ```
3. **Apply price impact**:

   ```
   Price After Impact = $1000 + $5.50 = $1005.50
   ```

## Open Interest Management

The module uses a time-windowed approach for open interest.

1. **Window Configuration**: `OI_WINDOWS_SETTINGS` defines the duration and count of windows.
2. **Adding OI**: `add_price_impact_open_interest` updates the current window's open interest when a trade is opened.
3. **Removing OI**: `remove_price_impact_open_interest` adjusts open interest when a trade is closed.
4. **Time-Weighted Calculation**: Open interest from multiple recent windows is used for a more stable metric.

## Usage

1. Initialize `PAIR_DEPTHS` with market depth values.
2. Configure `OI_WINDOWS_SETTINGS`.
3. Call `get_trade_price_impact` to calculate price impact for a trade.
4. After execution, call `add_price_impact_open_interest` or `remove_price_impact_open_interest` to update open interest.

## Configuration

Fine-tune the module by adjusting these parameters:

* `one_percent_depth_above_usd` and `one_percent_depth_below_usd` in `PAIR_DEPTHS`.
* `windows_count` and `windows_duration` in `OI_WINDOWS_SETTINGS`.

## Future Improvements

* Implement dynamic adjustment of market depth.
* Add support for asymmetric price impact.
* Introduce more advanced time-weighting algorithms.


# Perp: State Variables

This document describes the state variables in the perpetual futures trading smart contract. Variables are organized by functional module for clarity and ease of reference.

## Borrowing

| Variable           | Type                                                               | Description                                              |
| ------------------ | ------------------------------------------------------------------ | -------------------------------------------------------- |
| `PAIRS`            | `Map<(TokenIndex, MarketIndex), BorrowingData>`                    | Stores borrowing data for trading pairs                  |
| `PAIR_GROUPS`      | `Map<(TokenIndex, MarketIndex), Vec<BorrowingPairGroup>>`          | Tracks historical group associations for borrowing pairs |
| `PAIR_OIS`         | `Map<(TokenIndex, MarketIndex), OpenInterest>`                     | Stores open interest data for pairs                      |
| `GROUPS`           | `Map<(TokenIndex, GroupIndex), BorrowingData>`                     | Stores borrowing data for groups                         |
| `GROUP_OIS`        | `Map<(TokenIndex, GroupIndex), OpenInterest>`                      | Stores open interest data for groups                     |
| `INITIAL_ACC_FEES` | `Map<(TokenIndex, Addr, UserTradeIndex), BorrowingInitialAccFees>` | Tracks initial accumulated fees for trades               |

The borrowing module manages leveraged trading fees and risk exposure:

* **`PAIRS` and `GROUPS`**: Store borrowing-specific parameters including fee rates and accumulated fees for calculating trading costs. Note: This borrowing `GROUPS` contains `BorrowingData` and is distinct from the trading pairs `GROUPS` which contains general group information.
* **`PAIR_GROUPS`**: Maintains historical group associations for each pair. When pairs change groups, this ensures accurate fee calculations across different fee structures during a trade's lifetime.
* **`PAIR_OIS` and `GROUP_OIS`**: Track open interest for pairs and groups to enforce exposure limits and calculate dynamic borrowing rates based on market imbalances.
* **`INITIAL_ACC_FEES`**: Records accumulated fees when a trade opens, providing a reference point for calculating total borrowing fees at closure.

## Fees

| Variable                  | Type                                       | Description                                  |
| ------------------------- | ------------------------------------------ | -------------------------------------------- |
| `FEE_TIERS`               | `Item<[FeeTier; 8]>`                       | Stores the fee tier structure                |
| `PENDING_GOV_FEES`        | `Map<TokenIndex, Uint128>`                 | Tracks pending governance fees               |
| `VAULT_CLOSING_FEE_P`     | `Item<Decimal>`                            | Stores the vault closing fee percentage      |
| `TRADER_DAILY_INFOS`      | `Map<Addr, HashMap<u64, TraderDailyInfo>>` | Stores daily trading information for traders |
| `PAIR_VOLUME_MULTIPLIERS` | `Map<MarketIndex, Decimal>`                | Stores volume multipliers for pairs          |
| `TRADER_INFOS`            | `Map<Addr, TraderInfo>`                    | Stores general information about traders     |

### Referrer System

| Variable                       | Type                                | Description                                  |
| ------------------------------ | ----------------------------------- | -------------------------------------------- |
| `REFERRER_CODES`               | `Map<String, Addr>`                 | Maps referrer codes to referrer addresses    |
| `REFERRER_CODES_REVERSE`       | `Map<Addr, String>`                 | Maps referrer addresses to their codes       |
| `USER_REFERRERS`               | `Map<&Addr, Addr>`                  | Maps users to their referrers                |
| `REFERRER_FEES`                | `Map<(TokenIndex, &Addr), Uint128>` | Tracks fees earned by referrers              |
| `REFERRER_FEE_PERCENTAGE`      | `Item<[Decimal; 4]>`                | Fee percentages for different referrer tiers |
| `REFERRER_FEE_TIER`            | `Map<Addr, u8>`                     | Maps referrers to their fee tier levels      |
| `REFERREE_BASE_FEE_MULTIPLIER` | `Item<Decimal>`                     | Base fee multiplier for referees             |

The fees module implements dynamic tiered fee structures and referral programs:

**Core Fee Management:**

* **`FEE_TIERS`**: Defines fee levels based on trading activity to attract high-volume traders.
* **`PENDING_GOV_FEES`**: Accumulates governance fees for transparent distribution.
* **`VAULT_CLOSING_FEE_P`**: Sets the percentage of closing fees allocated to vault insurance/liquidity.
* **`TRADER_DAILY_INFOS` and `TRADER_INFOS`**: Track daily and cumulative trading data to calculate fee tiers over rolling periods.
* **`PAIR_VOLUME_MULTIPLIERS`**: Weight trading volume differently across markets to incentivize specific pairs or balance liquidity.

**Referral System:** The referral system rewards users for platform growth through a comprehensive affiliate program:

* **`REFERRER_CODES` and `REFERRER_CODES_REVERSE`**: Enable bidirectional mapping between referrer codes and addresses.
* **`USER_REFERRERS`**: Maps users to their referring addresses.
* **`REFERRER_FEES`**: Accumulates earnings for each referrer across token types.
* **`REFERRER_FEE_PERCENTAGE` and `REFERRER_FEE_TIER`**: Support multi-tier referrer levels with different fee percentages.
* **`REFERREE_BASE_FEE_MULTIPLIER`**: Provides base discounts for referred users.

## Trading Pairs and Groups

| Variable                   | Type                                  | Description                                             |
| -------------------------- | ------------------------------------- | ------------------------------------------------------- |
| `PERP_MARKETS`             | `Map<MarketIndex, MarketInfo>`        | Stores information about trading pairs                  |
| `GROUPS`                   | `Map<GroupIndex, Group>`              | Stores information about trading groups                 |
| `FEES`                     | `Map<FeeIndex, Fee>`                  | Stores fee information for trading pairs                |
| `PAIR_CUSTOM_MAX_LEVERAGE` | `Map<MarketIndex, Decimal>`           | Stores custom maximum leverage for pairs                |
| `AFTER_HOURS_LEVERAGE`     | `Item<Decimal>`                       | Stores the global after-hours effective leverage cap    |
| `AFTER_HOURS_MARKETS`      | `Map<MarketIndex, bool>`              | Marks markets where after-hours leverage controls apply |
| `ORACLE_ADDRESS`           | `Item<Addr>`                          | Stores the address of the oracle contract               |
| `VAULT_ADDRESSES`          | `Map<(GroupIndex, TokenIndex), Addr>` | Stores vault addresses for each group/token combination |

This module defines the core trading infrastructure:

* **`PERP_MARKETS`**: Stores trading pair information including base/quote assets, oracle indices, and associated group/fee indices.
* **`GROUPS`**: Defines leverage limits and group-wide parameters for categorizing pairs with similar risk profiles. Note: This differs from the borrowing `GROUPS` which contains `BorrowingData`.
* **`FEES`**: Contains fee structures (open/close/trigger fees) for each pair, enabling market-specific fee adjustments.
* **`PAIR_CUSTOM_MAX_LEVERAGE`**: Provides pair-specific leverage limits that override group settings for volatile or illiquid markets.
* **`AFTER_HOURS_LEVERAGE` and `AFTER_HOURS_MARKETS`**: Apply a lower effective leverage cap to live positions carried through a scheduled market close on configured markets.
* **`ORACLE_ADDRESS`**: Points to the oracle contract for price feeds used in trade execution and liquidations.
* **`VAULT_ADDRESSES`**: Maps vault contract addresses to group/token combinations for multi-vault asset management.

`MarketInfo` also records scheduled-session metadata: `block_last_closed`, `block_last_opened`, and `last_close_price`. The after-hours leverage guard uses the block fields to decide whether a live trade was carried through the latest close. It does not use `last_close_price` as the after-hours settlement price.

## Price Impact and Open Interest

| Variable               | Type                                                      | Description                                       |
| ---------------------- | --------------------------------------------------------- | ------------------------------------------------- |
| `OI_WINDOWS_SETTINGS`  | `Item<OiWindowsSettings>`                                 | Stores settings for open interest windows         |
| `WINDOWS`              | `Map<(WindowDuration, MarketIndex, WindowIndex), PairOi>` | Stores open interest data for specific windows    |
| `PAIR_DEPTHS`          | `Map<MarketIndex, PairDepth>`                             | Stores market depth information for pairs         |
| `TRADE_LAST_WINDOW_OI` | `Map<(Addr, TradeInfoIndex), Uint128>`                    | Tracks the last window's open interest for trades |

This module manages market dynamics and prevents excessive price impact:

* **`OI_WINDOWS_SETTINGS`**: Defines parameters for tracking open interest over time windows to calculate dynamic market impact.
* **`WINDOWS`**: Stores historical open interest data for calculating cumulative trade impact over specific time periods. This time-windowed approach prevents market manipulation.
* **`PAIR_DEPTHS`**: Contains market liquidity information used to calculate price impact for large trades, protecting against sudden price movements.
* **`TRADE_LAST_WINDOW_OI`**: Tracks each trade's contribution to open interest in its last active window for accurate updates when trades close or modify.

## Trading

| Variable                    | Type                                     | Description                                     |
| --------------------------- | ---------------------------------------- | ----------------------------------------------- |
| `COLLATERALS`               | `Map<TokenIndex, CollateralInfo>`        | Stores information about collateral types       |
| `USER_TRADE_INDEX`          | `Map<Addr, UserTradeIndex>`              | Tracks the next trade index for each user       |
| `TRADES`                    | `Map<(User, UserTradeIndex), Trade>`     | Stores individual trade information             |
| `TRADE_INFOS`               | `Map<(User, TradeInfoIndex), TradeInfo>` | Stores additional information about trades      |
| `TRADER_STORED`             | `Map<User, bool>`                        | Tracks whether a trader's information is stored |
| `USER_LIVE_TRADE_COUNTS`    | `Map<User, u64>`                         | Tracks trade counts for users                   |
| `MINIMUM_POSITION_SIZE_USD` | `Item<Decimal>`                          | Stores the minimum position size in USD         |
| `TRADING_ACTIVATED`         | `Item<TradingActivated>`                 | Stores the current trading activation status    |

The trading module forms the protocol's core functionality:

**Trade Management:**

* **`COLLATERALS`**: Defines accepted collateral types with name, active status, and denomination for multi-asset support.
* **`USER_TRADE_INDEX`**: Tracks the next available trade index per user for unique identification and prevents index collisions.
* **`TRADES` and `TRADE_INFOS`**: Store comprehensive trade data including position sizes, leverage, prices, and timestamps for fee calculations.

**Optimization and Controls:**

* **`TRADER_STORED` and `USER_LIVE_TRADE_COUNTS`**: Optimization mechanisms that reduce gas costs and enable efficient trade ID assignment.
* **`MINIMUM_POSITION_SIZE_USD`**: Sets minimum position thresholds to ensure economic viability and prevent dust trades.
* **`TRADING_ACTIVATED`**: Critical safety feature supporting multiple activation levels (fully active, close-only, paused) for emergency control.


# Contract Addresses Reference

Quick reference for all Sai Protocol contract addresses on Testnet and Mainnet.

## Table of Contents

* [Testnet Contracts](#testnet-contracts)
* [Mainnet Contracts](#mainnet-contracts)
* [Payment Tokens](#payment-tokens)

***

## Testnet Contracts

### Core Wasm Contracts

| Contract         | Address                                                           |
| ---------------- | ----------------------------------------------------------------- |
| **Perp Manager** | `nibi1qtkcns647w959cj9x2yytateu6dgscfnfkraywwa443pr2erak0s5ux7e5` |
| **Oracle**       | `nibi1mqlrsvfhm5vzsz0wxr6mh8pzxzpz6dd4g7nuyycjf6gy5zc53fvq3lq2fz` |

### LP Vaults (Testnet)

| Vault Type  | Vault Address                                                     | Vault Minter Address                                              |
| ----------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- |
| **Vault 1** | `nibi1jkuszyvgufu5kghmzp47gg02j4jyvtv22hs4vtmefg3wuzqayefqukhv7t` | `nibi174snnesyj442lxhn53xqcffh7us3f6q3se2sswe5cmmj78majj0sskdk3h` |
| **Vault 2** | `nibi1rsp225zym8x4pdcjdazl8jhg9jvxlptutdkynuvkamas8jjv8j5s22s86m` | `nibi1qa4cj932sm2rsuvz0vn8sz9qzarrctj693nnwhkqnjnvekan460shr3v0a` |

### EVM Interface Contract

| Network     | Address                                      |
| ----------- | -------------------------------------------- |
| **Testnet** | `0x282F097930E24e4fba97EE8687d15Ed1f298ad19` |

***

## Mainnet Contracts

### Core Wasm Contracts

| Contract         | Address                                                           |
| ---------------- | ----------------------------------------------------------------- |
| **Perp Manager** | `nibi1ntmw2dfvd0qnw5fnwdu9pev2hsnqfdj9ny9n0nzh2a5u8v0scflq930mph` |
| **Oracle**       | `nibi1xfwyfwtdame6645lgcs4xvf4u0hpsuvxrcelfwtztu0pv7n4l6hqw5a8gj` |

### LP Vaults (Mainnet - Crypto Group)

| Collateral | Vault Address                                                     | Vault Minter Address                                              |
| ---------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- |
| **USDC**   | `nibi193m2a00pmdsvkcvugrfewqzhtq6k0srkjzvxp2sk357vlpspx5vqxu8d7p` | `nibi15hcra03vdaaveslz6lekm02kuwkce23uj0u5u9ryaggjh4h5dlysfdmfvj` |
| **stNIBI** | `nibi1mrplvu3scplnrgns96kg0j8pk3l2p9c7eaz0qdedx0kt3vmcujyqrjkfej` | `nibi1vp45yer9anh4p99hgkuy4rx6mqt8cqwglfe5zzve22av67yl0k9qjreyv4` |

### EVM Interface Contract

| Network     | Address                                      |
| ----------- | -------------------------------------------- |
| **Mainnet** | `0x9F48A925Dda8528b3A5c2A6717Df0F03c8b167c0` |

***

## Payment Tokens

### Mainnet Payment Tokens

| Token                  | Symbol | EVM Address                                  | Bank Denom                                                                   | Decimals | Logo                                                                                                  |
| ---------------------- | ------ | -------------------------------------------- | ---------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| **USDC**               | USDC   | `0x0829F361A05D993d5CEb035cA6DF3446b060970b` | `erc20/0x0829F361A05D993d5CEb035cA6DF3446b060970b`                           | 6        | [🔗](https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/002_usdc.png)       |
| **Liquid Staked NIBI** | stNIBI | `0xcA0a9Fb5FBF692fa12fD13c0A900EC56Bb3f0a7b` | `tf/nibi1udqqx30cw8nwjxtl4l28ym9hhrp933zlq8dqxfjzcdhvl8y24zcqpzmh8m/ampNIBI` | 6        | [🔗](https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/001_stnibi-evm.png) |

### Testnet Payment Tokens

| Token                  | Symbol | EVM Address                                  | Bank Denom                                              | Decimals | Logo                                                                                                  |
| ---------------------- | ------ | -------------------------------------------- | ------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| **USDC**               | USDC   | `0xAb68f1D1d91854383fd4Df9016E3040D03e8191a` | `tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/usdc`   | 6        | [🔗](https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/002_usdc.png)       |
| **Liquid Staked NIBI** | stNIBI | `0xCae3d404AFB50016154a4B18091351065154E9bD` | `tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/stnibi` | 6        | [🔗](https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/001_stnibi-evm.png) |

***

## Usage Notes

### Wasm Contracts

* Use these addresses when interacting directly with Cosmos SDK/Wasm contracts
* Required for CosmJS or nibijs client libraries
* Format: `nibi1...` (Bech32 encoded)

### EVM Interface Contracts

* Use these addresses when interacting via EVM-compatible wallets (MetaMask, etc.)
* Provides EVM-style interface to underlying Wasm contracts
* Format: `0x...` (Ethereum hex address)

### Payment Tokens

* **EVM Address**: Use for ERC20 interactions via Web3/Ethers.js
* **Bank Denom**: Use for Cosmos SDK transactions
* **Contract Address**: Same as Bank Denom, used in Wasm contract calls
* All tokens use **6 decimals**

### Vault Structure

* **Vault**: Main vault contract that holds assets
* **Vault Minter**: Contract that mints/burns vault shares

***

## Quick Copy

### Testnet

```bash
# Core Contracts
PERP_MANAGER="nibi1qtkcns647w959cj9x2yytateu6dgscfnfkraywwa443pr2erak0s5ux7e5"
ORACLE="nibi1mqlrsvfhm5vzsz0wxr6mh8pzxzpz6dd4g7nuyycjf6gy5zc53fvq3lq2fz"
EVM_INTERFACE="0x282F097930E24e4fba97EE8687d15Ed1f298ad19"

# Payment Tokens
USDC_EVM="0xAb68f1D1d91854383fd4Df9016E3040D03e8191a"
STNIBI_EVM="0xCae3d404AFB50016154a4B18091351065154E9bD"
```

### Mainnet

```bash
# Core Contracts
PERP_MANAGER="nibi1ntmw2dfvd0qnw5fnwdu9pev2hsnqfdj9ny9n0nzh2a5u8v0scflq930mph"
ORACLE="nibi1xfwyfwtdame6645lgcs4xvf4u0hpsuvxrcelfwtztu0pv7n4l6hqw5a8gj"
EVM_INTERFACE="0x9F48A925Dda8528b3A5c2A6717Df0F03c8b167c0"

# Payment Tokens
USDC_EVM="0x0829F361A05D993d5CEb035cA6DF3446b060970b"
STNIBI_EVM="0xcA0a9Fb5FBF692fa12fD13c0A900EC56Bb3f0a7b"
```


# WASM & EVM Integration

## Sai Contracts – WASM & EVM Integration

A practical, copy‑paste friendly reference for integrating with Sai’s perpetuals and SLP vaults from **Cosmos (WASM)** and **EVM** wallets. It documents all calls shown in the code you shared, with params, expected preconditions, and example snippets.

> **Environments:** Mainnet and Testnet-2 are both supported. Token routes and contract addresses differ by network; see the **Collateral / Payment Tokens** section.

***

### Quick Glossary

* **BANK**: Cosmos-side coins (e.g., `tf/...` denoms).
* **ERC-20**: EVM-side tokens (e.g., `0x...`).
* **Interface (EVM)**: A single EVM contract that accepts a JSON-encoded WASM message and forwards it cross‑stack to Sai’s modules.
* **Perp**: The perpetuals contract/router on the Cosmos side (referenced via `saiContracts.perp`).
* **Vault**: SLP vault contracts (Cosmos bech32 addresses) that accept deposits/withdrawals/redeems.

***

### Collateral / Payment Tokens

These are the routes supported in the shared code (`usd`, `stnibi`). Values are used for **funds** on WASM calls and for **ERC‑20 params** on EVM interface calls.

#### Mainnet

| Route    | Symbol | Display Name                   | bankDenom                                                                    | evmAddr                                      | Default ERC‑20 Decimals |
| -------- | ------ | ------------------------------ | ---------------------------------------------------------------------------- | -------------------------------------------- | ----------------------- |
| `usd`    | USDC   | USDC                           | `erc20/0x0829F361A05D993d5CEb035cA6DF3446b060970b`                           | `0x0829F361A05D993d5CEb035cA6DF3446b060970b` | 6                       |
| `stnibi` | stNIBI | Liquid Staked Nibiru (Wrapped) | `tf/nibi1udqqx30cw8nwjxtl4l28ym9hhrp933zlq8dqxfjzcdhvl8y24zcqpzmh8m/ampNIBI` | `0xcA0a9Fb5FBF692fa12fD13c0A900EC56Bb3f0a7b` | 6                       |

#### Testnet‑2

| Route    | Symbol | Display Name                   | bankDenom                                               | evmAddr                                      | Default ERC‑20 Decimals |
| -------- | ------ | ------------------------------ | ------------------------------------------------------- | -------------------------------------------- | ----------------------- |
| `usd`    | USDC   | USDC                           | `tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/usdc`   | `0xAb68f1D1d91854383fd4Df9016E3040D03e8191a` | 6                       |
| `stnibi` | stNIBI | Liquid Staked Nibiru (Wrapped) | `tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/stnibi` | `0xCae3d404AFB50016154a4B18091351065154E9bD` | 6                       |

> **Tip:** When calling WASM, you spend BANK units (scaled by token decimals, 6 by default). On EVM, you pass both a **BANK portion** and an **ERC‑20 portion**; these are combined for total amounts.

***

### Shared Concepts & Types

* **Signers**
  * *WASM signer* `{ fromBech32Addr, runner: NibiruTxClient }`
  * *EVM signer* `{ fromHexAddr, runner?: ContractRunner, signer?: Signer }` (must have `signer` to send tx)
* **TradeType Derivation**
  * `Market → "trade"`
  * `Limit/Stop` is derived by comparing `limitPriceUsd` to `open_price`:
    * Long: `limit > open_price` → `stop`, else `limit`
    * Short: `limit < open_price` → `stop`, else `limit`
* **Gas Strategy (EVM)**
  * Estimate with `contract.method.estimateGas(...)` and pad by ×1.1
  * Fallbacks: `gasLimit = 5_000_000`, `gasPrice = 1 gwei` if estimation fails
* **ExecuteResult (normalized)**
  * EVM receipts are converted to a CosmJS‑like `ExecuteResult` with `height`, `transactionHash`, `gasUsed/Wanted`, and `events` (topics/data echoed per log).
* **Explorer Links**
  * UI examples compute `blockUrl` and `txUrl` from `height` and `hash`.

***

## EVM Integration (via Interface Contract)

All EVM flows call a single **Interface** contract (obtained via `saiEvmInterfaceFromChainType(chainType)`). Each call sends a JSON-encoded WASM message (`wasmMsgBytes`) together with token amounts/addresses. Always ensure an EVM **signer** is available.

### 1. Open Perp Trade

**Method:** `openTrade(wasmMsgBytes, collateralIndex, totalAmountBankUnits, useErc20Amount)`

**wasmMsg (JSON):**

```json
{
  "open_trade": {
    "market_index": "MarketIndex(N)",
    "leverage": "string",
    "long": true,
    "collateral_index": "TokenIndex(N)",
    "trade_type": "trade|limit|stop",
    "open_price": "<market price or limit price>",
    "tp": "optional string",
    "sl": "optional string",
    "slippage_p": "1",
    "is_evm_origin": true
  }
}
```

**Amounting:**

* Compute BANK portion in **6‑dec units**.
* Compute ERC‑20 portion in **token decimals** (default 6; use `collateral.evmDefaultDecimals` if present).
* `totalAmount` = `bankAmount + evmAmount` (on‑chain Interface handles bridging/combining).

**Preconditions:**

* `orderType === "Market"` requires `open_price`.
* `collateral` must be present; `amtTrade` must be > 0.
* If using smart allocation, split requested amount across BANK/ERC‑20 per wallet balances and preference.

### 2 Close Perp Trade

**Method:** `executeSimpleFunctions(wasmMsgBytes)`

**wasmMsg:**

```json
{ "close_trade": { "trade_index": "UserTradeIndex(<N>)" } }
```

### 3. Referral: Create Code

**Method:** `executeSimpleFunctions(wasmMsgBytes)`

**wasmMsg:**

```json
{ "create_referrer_code": { "code": "<your_code>" } }
```

### 4. Referral: Redeem Code

**Method:** `executeSimpleFunctions(wasmMsgBytes)`

**wasmMsg:**

```json
{ "redeem_referrer_code": { "code": "<partner_code>" } }
```

### 5. Vault: Deposit

**Method:**

```javascript
contract.deposit(
  wasmMsgBytes,                    // { "deposit": {} }
  depositAmountTotalBankUnits,     // BANK units (includes ERC‑20 converted -> BANK)
  useErc20Amount,                  // ERC‑20 units (token decimals)
  vaultAddress,                    // bech32 vault contract
  collateralErc20,                 // ERC‑20 address
  true,                            // sendToEvm on triggers
  overrides
)
```

**Notes:**

* Convert ERC‑20 portion to BANK units before summing into `depositAmountTotal` if needed (decimals may differ).

### 6. Vault: Make Withdraw Request

**Method:**

```javascript
contract.makeWithdrawRequest(
  vaultAddress,
  wasmMsgBytes,     // { "make_withdraw_request": {} }
  totalShares,      // BANK units to lock (sharesBANK + sharesFromERC20)
  useErc20Amount,   // shares to bridge from ERC‑20 (BANK-scaled already)
  overrides
)
```

### 7. Vault: Redeem

**Method:**

```javascript
contract.redeem(
  wasmMsgBytes,              // { "redeem": { "shares": "..." } }
  vaultAddress,
  shares,                    // BANK units
  sendToEvm,                 // boolean
  overrides
)
```

### 8. Vault: Cancel Withdraw Request

**Method:** `executeVaultSimpleFunctions(wasmMsgBytes, vaultAddress)`

**wasmMsg:**

```json
{ "cancel_withdraw_request": { "unlock_epoch": <number> } }
```

***

## WASM (Cosmos) Integration

WASM calls use the `NibiruTxClient` (`signer.runner.wasmClient`). Funds are passed as Cosmos `Coin { denom, amount }` where `amount` is **BANK units** (1e6‑scaled by default).

### 1. Open Perp Trade

**Client:** `wasmClient.execute(sender, saiContracts.perp, msg, fee, memo, funds)`

**Msg:**

```json
{
  "open_trade": {
    "market_index": "MarketIndex(N)",
    "leverage": "string",
    "long": true,
    "collateral_index": "TokenIndex(N)",
    "trade_type": "trade|limit|stop",
    "open_price": "<market price or limit price>",
    "slippage_p": "1",
    "tp": "optional string",
    "sl": "optional string",
    "is_evm_origin": false
  }
}
```

**Funds:**

```json
[{ "denom": "<collateral.bankDenom>", "amount": "<BANK units>" }]
```

**Preconditions:**

* For `Market`, `open_price` is required.
* For `Limit/Stop`, `limitPriceUsd` is required.

**Optional ERC‑20 → BANK bridging**

* If the current BANK balance is insufficient, prepend a `MsgConvertEvmToCoin` to convert ERC‑20 to BANK for the deficit.

### 2. Close Perp Trade

**Client:** `execute(sender, saiContracts.perp, { close_trade: { trade_index: "UserTradeIndex(N)" } }, fee)`

### 3. Referral: Create Code

**Client:** `execute(sender, saiContracts.perp, { create_referrer_code: { code } }, fee)`

### 4. Referral: Redeem Code

**Client:** `execute(sender, saiContracts.perp, { redeem_referrer_code: { code } }, fee)`

### 5. Vault: Deposit

**Client:** `execute(sender, vaultSelection.address, { deposit: {} }, fee, memo, [funds])`

**Funds:**

```json
[{ "denom": "<collateral.bankDenom>", "amount": "<BANK units>" }]
```

### 6. Vault: Make Withdraw Request

**Share Denom Discovery:** Query once before executing:

```json
{ "get_vault_share_denom": {} }
```

**Client:**

* Execute with funds in **share denom**:

```json
[{ "denom": "<shareDenom>", "amount": "<shares BANK units>" }]
```

**Msg:** `{ "make_withdraw_request": {} }`

### 7. Vault: Redeem

**Client:** `execute(sender, vaultAddress, { redeem: { shares: "..." } }, fee)` (no funds)

### 8. Vault: Cancel Withdraw Request

**Client:** `execute(sender, vaultAddress, { cancel_withdraw_request: { unlock_epoch } }, fee)`

***

### Amounts & Decimals (Gotchas)

* **Default decimals = 6** for BANK. Always scale UI amounts: `display × 10^decimals`.
* When mixing BANK and ERC‑20 on **EVM deposit/withdraw**, convert across decimals before summing.
* Validate that amounts are **finite, non‑negative, non‑zero** after scaling.

***

### Error Handling & UX Patterns

* **Signer presence**: EVM calls require `signer` (not just a runner). WASM requires `runner`.
* **Toast/Notifications**: Use pending/success/failure to surface status. On success, show `height` and `hash` with explorer links.
* **Debugging (EVM)**: If a tx reverts, use archive RPC’s `debug_traceTransaction` to extract the deepest `calls[-1].error`.

***

### Minimal Pseudocode Examples

> Below are slimmed examples you can adapt; they assume your app has already selected `chainType`, `saiContracts`, `interfaceAddress`, `collateral`, etc.

#### Open Trade (EVM)

```ts
const msg = { open_trade: { /* ...see schema above... */ is_evm_origin: true } }
const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(msg))
const gas = await estimateGasWithFallback(
  () => contract.openTrade.estimateGas(wasmMsgBytes, collateralIndex, totalAmount, useErc20),
  { msg }
)
const tx = await contract.openTrade(wasmMsgBytes, collateralIndex, totalAmount, useErc20, toTxOverrides(gas))
const res = await tx.wait() // -> map to ExecuteResult if desired
```

#### Open Trade (WASM)

```ts
await wasm.execute(
  bech32,
  saiContracts.perp,
  { open_trade: { /* ... */, is_evm_origin: false } },
  "auto",
  undefined,
  [ { denom: collateral.bankDenom, amount: scaledAmount } ]
)
```

#### Vault Deposit (EVM)

```ts
const msg = { deposit: {} }
const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(msg))
const bankTotal = bankAmount + toBankUnits(erc20Amount, erc20Dec, 6)
const tx = await contract.deposit(wasmMsgBytes, bankTotal, erc20Amount, vaultAddr, collateral.evmAddr, true, overrides)
await tx.wait()
```

#### Make Withdraw Request (WASM)

```ts
const shareDenom = await wasm.queryContractSmart(vaultAddr, { get_vault_share_denom: {} })
await wasm.execute(
  bech32,
  vaultAddr,
  { make_withdraw_request: {} },
  "auto",
  undefined,
  [ { denom: shareDenom, amount: shares } ]
)
```

***

### Preconditions Checklist (per call)

* **Open Trade**: amount > 0; Market→`open_price` present; Limit/Stop→`limitPriceUsd` present; `collateral.index` parseable; signer ready.
* **Close Trade**: `userTradeIndex` set; signer ready.
* **Create/Redeem Referral**: `code` non‑empty; signer ready.
* **Vault Deposit**: amounts properly scaled; convert ERC‑20 portion to BANK for totals on EVM; signer ready.
* **Withdraw Request**: shares in BANK units; (WASM) share denom fetched; signer ready.
* **Redeem**: shares in BANK units; `sendToEvm` flag set (EVM path); signer ready.
* **Cancel Withdraw Request**: `unlock_epoch` set; signer ready.

***

### Appendix: Utility Notes

* **`estimateGasWithFallback`** pads gas by 10% and falls back to `5_000_000 / 1 gwei` if estimation fails.
* **`receiptToExecuteResult`** normalizes EVM receipts to a CosmJS‑like shape.
* **`MsgConvertEvmToCoin`** (WASM path) can optionally be appended if BANK funds are insufficient.
* **Smart Allocation** (optional): split requested amount across BANK / ERC‑20 based on balances and user preference; then scale per‑side and stitch back together.


# Sai Keeper

Sai Keeper is a GraphQL API that indexes Sai Protocol's on-chain data, enabling developers to query trades, liquidity pools, prices, and fees without directly accessing the blockchain.

Sai Keeper is a GraphQL API that provides real-time and historical data for the Sai protocol on Nibiru Chain. It enables developers to query perpetual trades, liquidity pool data, oracle prices, and fee analytics.

## What is Sai Keeper?

Sai Keeper is the data indexing and query layer for Sai Protocol. It processes on-chain events and makes them accessible through a fast, flexible GraphQL API.

**Key capabilities:**

* Query perpetual trading positions and history
* Access liquidity pool data and APY metrics
* Retrieve oracle price feeds
* Analyze fee structures and protocol revenue
* Subscribe to real-time updates via WebSockets

## Environments

### Testnet

* **GraphQL Endpoint**: `https://sai-keeper.testnet-2.nibiru.fi/graphql`
* **Interactive Playground**: `https://sai-keeper.testnet-2.nibiru.fi/`
* **Purpose**: Development and testing with test data
* Use for development and testing

### Mainnet

* **GraphQL Endpoint**: `https://sai-keeper.nibiru.fi/graphql`
* **Interactive Playground**: `https://sai-keeper.nibiru.fi/`
* **Purpose**: Production applications with real data
* Use for production applications

### Interactive User Interface

Both Testnet and Mainnet endpoints provide an **interactive GraphQL Playground** where you can:

* **Test all queries** with live data
* **Test subscriptions** with real-time updates
* **Explore the schema** with built-in documentation
* **View query history** and save queries
* **Debug responses** with formatted JSON

Simply visit the endpoint URLs in your browser to access the playground!

## Quick Start

### 1. Explore the API

Visit the GraphQL Playground to explore the schema interactively:

* **Testnet**: <https://sai-keeper.testnet-2.nibiru.fi/>
* **Mainnet**: <https://sai-keeper.nibiru.fi/>

### 2. Make Your First Query

Try this simple query in the playground:

```graphql
query GetTokenPrices {
  oracle {
    tokenPricesUsd(limit: 5) {
      token {
        symbol
        name
      }
      priceUsd
      lastUpdatedBlock {
        block
        block_ts
      }
    }
  }
}
```

### 3. Set Up Your Client

Install a GraphQL client library:

**JavaScript/TypeScript:**

```bash
npm install @apollo/client graphql
# or
npm install urql graphql
```

**Python:**

```bash
pip install gql[all]
```

**Rust:**

```toml
[dependencies]
graphql_client = "0.13"
```

## Core Data Domains

Sai Keeper organizes data into four main domains:

### 1. Perp (Perpetuals)

Query and subscribe to perpetual trading data:

* Open and closed positions
* Trade history and events
* Market borrowing rates

### 2. LP (Liquidity Pools)

Access liquidity provider information:

* Vault metrics (TVL, APY, share price)
* User deposits and shares
* Withdrawal requests
* Revenue tracking

### 3. Oracle

Get token price data:

* Real-time token prices in USD
* Token metadata
* Price update timestamps

### 4. Fee

Analyze fee data:

* Transaction-level fees
* Daily fee statistics
* Protocol and trader fee summaries
* Fee type breakdowns (opening/closing)

## GraphQL Basics

If you're new to GraphQL, here are the essentials:

### Queries

Request specific data:

```graphql
query {
  perp {
    trades(where: { trader: "nibi1abc..." }, limit: 10) {
      id
      isOpen
      leverage
    }
  }
}
```

### Subscriptions

Get real-time updates:

```graphql
subscription {
  perpTrades(where: { trader: "nibi1abc..." }) {
    id
    isOpen
    leverage
  }
}
```

### Variables

Parameterize your queries:

```graphql
query GetTrades($trader: String!, $limit: Int) {
  perp {
    trades(where: { trader: $trader }, limit: $limit) {
      id
    }
  }
}
```

Variables:

```json
{
  "trader": "nibi1abc...",
  "limit": 10
}
```

## Architecture Overview

```
┌─────────────────┐
│   Your App      │
│  (Frontend/     │
│   Backend)      │
└────────┬────────┘
         │
         │ GraphQL
         │ Query/Subscribe
         │
┌────────▼────────┐
│  Sai Keeper     │
│  GraphQL API    │
└────────┬────────┘
         │
         │ Indexes
         │
┌────────▼────────┐
│  Nibiru Chain   │
│  (Blockchain)   │
└─────────────────┘
```

Sai Keeper indexes blockchain data and provides it through a fast, queryable GraphQL interface. This means you don't need to query the blockchain directly or maintain your own indexer.

## What You Can Build

* **Trading Platforms**: Full-featured perpetual trading interfaces
* **Portfolio Trackers**: Monitor positions, PnL, and performance
* **Analytics Dashboards**: Visualize protocol metrics and user activity
* **Automated Bots**: Build trading algorithms with real-time data
* **LP Management Tools**: Optimize positions strategies

## Next Steps

* **New to GraphQL?** → Read [Core Concepts](/for-devs/sai-keeper/core-concepts)
* **Ready to code?** → Check out [Client Setup](/for-devs/sai-keeper/client-setup)
* **Want examples?** → See [Query Examples](/for-devs/sai-keeper/examples-queries)
* **Need API details?** → Browse the API references:
  * [Perp API](/for-devs/sai-keeper/api-perp)
  * [LP API](/for-devs/sai-keeper/api-lp)
  * [Oracle API](/for-devs/sai-keeper/api-oracle)
  * [Fee API](/for-devs/sai-keeper/api-fees)


# Core Concepts

Understanding these core concepts will help you effectively use the Sai Keeper API.

## Table of Contents

* [Protocol Overview](#protocol-overview)
* [Data Models](#data-models)
* [Perpetual Trading](#perpetual-trading)
* [Liquidity Pools](#liquidity-pools)
* [Oracle System](#oracle-system)
* [Fee Structure](#fee-structure)
* [Blocks and Timestamps](#blocks-and-timestamps)

## Protocol Overview

Sai Protocol is a decentralized perpetual trading platform on Nibiru Chain. The protocol is implemented in Rust and compiled into WebAssembly (Wasm) bytecode, with 5 key contracts managing all operations fully on-chain.

**Key components:**

* **Perpetual Markets**: Trade with leverage without expiration dates
* **Liquidity Vaults**: Users provide liquidity to earn yield
* **Oracle Prices**: Reliable price feeds for all tradeable assets
* **Fee System**: Transparent fee structure for all operations

## Data Models

### Hierarchy

```txt
Query/Subscription
├── perp (Perpetuals)
│   ├── trades
│   ├── tradeHistory
│   └── borrowings
├── lp (Liquidity Pools)
│   ├── vaults
│   ├── deposits
│   └── withdrawRequests
├── oracle (Price Feeds)
│   ├── tokens
│   └── tokenPricesUsd
└── fee (Fee Analytics)
    ├── feeTransactions
    ├── feeDailyStats
    └── summaries
```

## Perpetual Trading

### What is a Perpetual?

A perpetual contract (perp) is a derivative that lets you trade an asset with leverage without an expiration date. Unlike futures, perpetuals use a funding rate mechanism (called borrowing fees in Sai) to keep prices anchored to spot prices.

### Trade Lifecycle

1. **Opening**: Trader opens a position (long or short)
2. **Active**: Position accumulates borrowing fees based on market conditions
3. **Closing**: Position closed by:
   * User action
   * Take Profit (TP) trigger
   * Stop Loss (SL) trigger
   * Liquidation (if losses exceed threshold)

### Key Concepts

**Leverage**: Multiply your position size

```txt
Position Value = Collateral × Leverage
```

**Long vs Short**:

* **Long**: Profit when price increases
* **Short**: Profit when price decreases

**Open Interest (OI)**: Total value of open positions

* `oiLong`: Long positions open interest
* `oiShort`: Short positions open interest
* `oiMax`: Maximum allowed open interest

**Borrowing Fees**: Charged per hour based on OI imbalance

* Higher fees on the side with more open interest
* Helps balance long/short positions
* Converted to APR: `feesPerHour × 24 × 365 × 100`

### Trade Types

* **Market Trade** (`trade`): Instant execution at current price
* **Limit Order** (`limit`): Execute when price reaches target
* **Stop Order** (`stop`): Trigger when price hits stop level

### PnL Calculation

```txt
Unrealized PnL = Position Value × (Current Price - Open Price) / Open Price

For Long:  PnL = positive when price increases
For Short: PnL = positive when price decreases
```

**Example:**

* Long 10x position with $1,000 collateral = $10,000 position value
* Entry: $50,000 | Current: $55,000
* PnL = $10,000 × ($55,000 - $50,000) / $50,000 = $1,000 (10%)

### Liquidation

A position gets liquidated when losses approach the collateral amount:

```txt
Liquidation Price (Long) = Entry Price × (1 - 1/Leverage + Maintenance Margin)
Liquidation Price (Short) = Entry Price × (1 + 1/Leverage - Maintenance Margin)
```

**Example (10x leverage, 5% maintenance margin):**

* Long entry at $50,000 → Liquidation at \~$47,500
* Short entry at $50,000 → Liquidation at \~$52,500

## Liquidity Pools

### How LP Works

Liquidity Providers (LPs) deposit assets into vaults. These vaults:

* Serve as counterparty to traders
* Earn fees from trading activity
* Share profits when traders lose
* Cover losses when traders profit

**Risk/Reward:**

* LPs earn trading fees and a share of trader losses
* LPs bear the risk when traders are profitable
* Net profit tracked in `revenueInfo`

### Vault Metrics

**TVL (Total Value Locked)**: Total assets in the vault

```txt
TVL = Available Assets + Assets in Open Positions
```

**Share Price**: Value of one vault share

```txt
Share Price = Total Vault Value / Total Shares Outstanding
```

* Increases when vault is profitable
* Decreases when vault incurs losses
* Initial share price typically starts at 1.0

**APY (Annual Percentage Yield)**: Annualized return

```txt
APY = (Net Profit / TVL) × (365 / Epoch Duration Days) × 100
```

* Calculated from historical revenue
* Updates based on vault performance
* Can fluctuate with market conditions

### Deposit/Withdrawal Flow

1. **Deposit**: User deposits → receives shares

   ```txt
   Shares Received = Deposit Amount / Current Share Price
   ```
2. **Withdrawal Request**: User requests withdrawal
   * Shares locked until next epoch
   * `unlockEpoch` indicates when withdrawal available
   * Can set `autoRedeem: true` for automatic redemption
3. **Withdrawal Execution**: After epoch ends
   * User redeems shares for assets
   * Value = `Shares × Current Share Price`

### Epochs

Time periods for LP operations:

* **Duration**: Check `epochDurationDays` or `epochDurationHours`
* **Current Epoch**: Track with `currentEpoch`
* **Epoch Start**: Unix timestamp of epoch start
* **Purpose**: Prevents instant withdrawal attacks, ensures fair pricing

### Revenue Info

Each vault tracks detailed revenue metrics:

* `RevenueCumulative`: Total revenue earned all-time
* `NetProfit`: Current net profit (revenue - liabilities)
* `TraderLosses`: Profits from trader losses
* `ClosedPnl`: Realized PnL from closed positions
* `CurrentEpochPositiveOpenPnl`: Current unrealized gains
* `Liabilities`: Current liabilities (trader unrealized profits)
* `Rewards`: Additional rewards received

## Oracle System

### Price Feeds

Oracle provides reliable, up-to-date prices for all tokens:

**Update Mechanism**:

* Prices updated on-chain via oracle validators
* Each update recorded with block number and timestamp
* Multiple price sources aggregated for accuracy
* Updates occur every few blocks (varies by token)

**Token Types**:

* **Bank tokens** (`TokenType.bank`): Native Cosmos SDK tokens
* **ERC20 tokens** (`TokenType.erc20`): Ethereum-compatible tokens

### Using Prices

Always check `lastUpdatedBlock` to ensure price freshness:

```graphql
{
  oracle {
    tokenPricesUsd(where: { tokenId: 1 }) {
      priceUsd
      lastUpdatedBlock {
        block
        block_ts
      }
    }
  }
}
```

**Best Practice:**

```javascript
const ageSeconds = (Date.now() - new Date(lastUpdatedBlock.block_ts).getTime()) / 1000
if (ageSeconds > 60) {
  console.warn('Price may be stale')
}
```

## Fee Structure

### Fee Types

**Opening Fees** (`FeeType.OPENING`):

* Charged when opening a position
* Based on position size
* Components: vault fee, gov fee

**Closing Fees** (`FeeType.CLOSING`):

* Charged when closing a position
* Based on position size
* Components: vault fee, gov fee, trigger fee (if triggered by keeper)

### Fee Components

Every fee transaction breaks down into:

```txt
Total Fee = Vault Fee + Gov Fee + Trigger Fee
```

* **Vault Fee**: Goes to LP vault (largest portion, typically 80-90%)
* **Gov Fee**: Protocol governance fee (typically 10-20%)
* **Trigger Fee**: Reward for triggering stop/limit orders (if applicable)

**Example Fee Breakdown:**

```txt
Position Size: $10,000
Opening Fee: 0.1% = $10
├─ Vault Fee: $8 (80%)
├─ Gov Fee: $2 (20%)
└─ Total: $10
```

### Fee Multiplier

Dynamic multiplier based on market conditions:

```txt
Actual Fee = Base Fee × Fee Multiplier
```

* Higher multipliers during high volatility or risk periods
* Typical range: 1.0x to 2.0x
* Protects LP vaults during risky conditions
* Tracked in `avgFeeMultiplier` for analytics

### Bad Debt

When a position is liquidated but collateral is insufficient:

```txt
Bad Debt = Total Loss - Collateral Available
```

* Bad debt is tracked per transaction
* Absorbed by the protocol/vault
* Monitored in fee analytics
* Rare occurrence with proper risk management

## Blocks and Timestamps

### Block Structure

```graphql
type Block {
  block: Int!         # Block height/number
  block_ts: Time!     # Block timestamp (RFC3339 format)
}
```

**Usage:**

* `block`: Sequential number, increases monotonically
* `block_ts`: Human-readable timestamp
* Every important event (trade open/close, deposit, etc.) includes block info

### Time Type

The `Time` scalar represents timestamps in RFC3339 format:

```txt
2024-11-03T10:30:00Z
```

### Filtering by Time

Use `TimeFilter` for date ranges:

```graphql
{
  fee {
    feeTransactions(
      filter: {
        fromDate: "2024-01-01T00:00:00Z"
        toDate: "2024-01-31T23:59:59Z"
      }
    ) {
      id
    }
  }
}
```

### Block vs Timestamp

* **Block height**: Sequential, deterministic, never changes
* **Timestamp**: Human-readable, approximate (block times can vary)
* Most queries support both

**Best Practice**: Use timestamps for date ranges, blocks for precise event ordering.

## Key Terminology

### Trading Terms

* **Collateral**: Asset deposited to open positions
* **Margin**: Same as collateral in perpetuals context
* **Liquidation**: Forced position closure when losses exceed threshold
* **Funding Rate**: Periodic payment between longs and shorts (borrowing fees in Sai)
* **Mark Price**: Oracle price used for PnL calculations
* **Index Price**: Spot price from oracle

### LP Terms

* **TVL**: Total Value Locked in a vault
* **Share Price**: Value of one vault share token
* **Epoch**: Time period for LP operations
* **APY**: Annual Percentage Yield
* **Revenue**: Earnings from trading fees and trader losses

### Fee Terms

* **Opening Fee**: Fee charged when opening a position
* **Closing Fee**: Fee charged when closing a position
* **Fee Multiplier**: Dynamic multiplier applied to base fees
* **Bad Debt**: Losses exceeding available collateral

## Data Freshness

* **Real-time**: Subscriptions provide live updates as events occur
* **Query Latency**: Typically <100ms for queries
* **Block Time**: \~1-2 seconds on Nibiru Chain
* **Oracle Updates**: Varies by token, typically every few blocks
* **Cache**: Some data cached briefly for performance

## Smart Contract Architecture

Sai Protocol consists of 5 key Wasm contracts:

1. **Perp Manager**: Handles perpetual trading operations
2. **LP Vault**: Manages positions and withdrawals
3. **Oracle**: Provides price feeds
4. **Fee Manager**: Processes and distributes fees
5. **Governance**: Protocol parameter management

All contracts are fully on-chain and written in Rust for security and performance.

## Next Steps

* Learn about [Perp API](/for-devs/sai-keeper/api-perp) for trading data
* Explore [LP API](/for-devs/sai-keeper/api-lp) for liquidity pool data
* Check [Oracle API](/for-devs/sai-keeper/api-oracle) for price feeds
* Understand [Fee API](/for-devs/sai-keeper/api-fees) for fee analytics


# Perp API

Query and subscribe to perpetual trading data including positions, trade history, market borrowing rates, and referrals.

## Table of Contents

* [Perp API Reference](#perp-api-reference)
  * [Table of Contents](#table-of-contents)
  * [Queries](#queries)
    * [trade](#trade)
    * [trades](#trades)
    * [tradeHistory](#tradehistory)
    * [borrowing](#borrowing)
    * [borrowings](#borrowings)
  * [Subscriptions](#subscriptions)
    * [perpTrades](#perptrades)
    * [perpTradeHistory](#perptradehistory)
    * [perpBorrowing](#perpborrowing)
    * [perpBorrowings](#perpborrowings)
  * [Types Reference](#types-reference)
    * [PerpTrade](#perptrade)
    * [PerpTradeState](#perptradestate)
    * [PerpBorrowing](#perpborrowing-1)
    * [Enums](#enums)

## Queries

### trade

Get a specific trade by ID and trader address.

**Signature:**

```graphql
trade(id: Int!, trader: String!): PerpTrade
```

**Parameters:**

* `id`: Trade ID (integer)
* `trader`: Trader's wallet address (required)

**Returns:** `PerpTrade` object or null if not found

**Example:**

```graphql
query GetTrade {
  perp {
    trade(id: 42, trader: "nibi1abc...") {
      id
      isOpen
      isLong
      leverage
      collateralAmount
      openPrice
      closePrice
      perpBorrowing {
        baseToken { symbol }
        collateralToken { symbol }
        marketId
      }
      openBlock {
        block
        block_ts
      }
      closeBlock {
        block
        block_ts
      }
      state {
        pnlCollateral
        pnlPct
        liquidationPrice
        positionValue
      }
    }
  }
}
```

**Use Cases:**

* Display single trade details
* Track specific position status
* Calculate current PnL

***

### trades

List trades with filtering, pagination, and ordering.

**Signature:**

```graphql
trades(
  where: PerpTradesFilter!
  limit: Int
  offset: Int
  order_by: PerpTradesOrder
  order_desc: Boolean
): [PerpTrade!]!
```

**Parameters:**

* `where`: Filter object (required)
  * `trader`: Trader address (required)
  * `isOpen`: Filter by open/closed status
  * `perpMarketId`: Filter by market ID
  * `perpCollateralId`: Filter by collateral token ID
* `limit`: Max results (default: 100)
* `offset`: Skip results for pagination
* `order_by`: Sort field (`sequence` only)
* `order_desc`: Sort descending (default: false)

**Returns:** Array of `PerpTrade` objects

**Example - Get Open Positions:**

```graphql
query GetOpenPositions($trader: String!) {
  perp {
    trades(
      where: {
        trader: $trader
        isOpen: true
      }
      limit: 50
      order_desc: true
    ) {
      id
      isLong
      leverage
      collateralAmount
      openPrice
      perpBorrowing {
        baseToken { symbol }
        marketId
      }
      state {
        pnlCollateral
        pnlPct
        liquidationPrice
      }
    }
  }
}
```

**Example - Filter by Market:**

```graphql
query GetBTCTrades($trader: String!) {
  perp {
    trades(
      where: {
        trader: $trader
        perpMarketId: 1  # BTC market
      }
      limit: 20
    ) {
      id
      isOpen
      openPrice
      closePrice
    }
  }
}
```

**Use Cases:**

* List user's open positions
* Display trading history
* Filter by specific markets
* Build position dashboard

***

### tradeHistory

Get trade events and position changes.

**Signature:**

```graphql
tradeHistory(
  where: PerpTradeHistoryFilter!
  limit: Int
  offset: Int
  order_by: PerpTradeHistoryOrder
  order_desc: Boolean
): [PerpTradeHistoryItem!]!
```

**Parameters:**

* `where`: Filter object (required)
  * `trader`: Trader address (required)
* `limit`: Max results (default: 100)
* `offset`: Skip results
* `order_by`: Sort by `sequence` or `trade_id`
* `order_desc`: Sort descending

**Returns:** Array of `PerpTradeHistoryItem` objects

**Trade Change Types:**

* `position_opened`: New position created
* `position_closed_user`: User closed position
* `position_closed_sl`: Stop loss triggered
* `position_closed_tp`: Take profit triggered
* `position_liquidated`: Position liquidated
* `sl_updated`: Stop loss updated
* `tp_updated`: Take profit updated
* `limit_order_created`: Limit order placed
* `stop_order_created`: Stop order placed
* `order_triggered`: Order executed
* `order_closed_user`: Order cancelled

**Example:**

```graphql
query GetTradeHistory($trader: String!) {
  perp {
    tradeHistory(
      where: { trader: $trader }
      limit: 50
      order_by: sequence
      order_desc: true
    ) {
      id
      tradeChangeType
      block {
        block
        block_ts
      }
      trade {
        id
        isLong
        leverage
      }
      realizedPnlCollateral
      realizedPnlPct
    }
  }
}
```

**Use Cases:**

* Build activity feed
* Track position modifications
* Calculate realized PnL history
* Audit trade actions

***

### borrowing

Get detailed borrowing information for a specific market and collateral.

**Signature:**

```graphql
borrowing(collateralId: Int!, marketId: Int!): PerpBorrowing
```

**Parameters:**

* `collateralId`: Collateral token ID
* `marketId`: Market ID

**Returns:** `PerpBorrowing` object or null

**Example:**

```graphql
query GetMarketInfo {
  perp {
    borrowing(collateralId: 1, marketId: 1) {
      marketId
      baseToken { symbol name }
      quoteToken { symbol }
      collateralToken { symbol }
      price
      oiLong
      oiShort
      oiMax
      feesPerHourLong
      feesPerHourShort
      openFeePct
      closeFeePct
      triggerOrderFeePct
      maxLeverage
      minLeverage
      minPositionSizeUSD
      visible
    }
  }
}
```

**Key Fields Explained:**

* `oiLong` / `oiShort`: Open interest for longs/shorts
* `oiMax`: Maximum allowed open interest
* `feesPerHourLong` / `feesPerHourShort`: Borrowing rate (per hour)
* `openFeePct` / `closeFeePct`: Opening/closing fee percentages
* `maxLeverage` / `minLeverage`: Standard market leverage limits
* `minPositionSizeUSD`: Minimum position size in USD

{% hint style="info" %}
For scheduled markets, `maxLeverage` is not the same as the after-hours effective leverage cap. If your integration needs to detect carried positions that require after-hours normalization, use the perp contract's after-hours queries and `get_trade_data.needs_after_hours_trigger`.
{% endhint %}

**Use Cases:**

* Display market information
* Calculate funding rates
* Show available leverage
* Check market capacity (OI limits)

***

### borrowings

List all available markets with basic information.

**Signature:**

```graphql
borrowings(
  where: PerpBorrowingsFilter
  limit: Int
  offset: Int
  order_by: PerpBorrowingsOrder
  order_desc: Boolean
): [PerpBorrowingShortInfo!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `marketId`: Filter by market ID
  * `collateralId`: Filter by collateral ID
* `limit`: Max results
* `offset`: Skip results
* `order_by`: Sort by `base_token_name`
* `order_desc`: Sort descending

**Returns:** Array of `PerpBorrowingShortInfo` objects

**Example:**

```graphql
query ListMarkets {
  perp {
    borrowings(
      order_by: base_token_name
      limit: 20
    ) {
      marketId
      baseToken { symbol name logoUrl }
      quoteToken { symbol }
      collateralToken { symbol }
      visible
    }
  }
}
```

**Use Cases:**

* List all tradeable markets
* Build market selector
* Filter visible markets

***

## Subscriptions

Subscribe to real-time updates for perpetual trading data.

### perpTrades

Subscribe to trade updates for a specific trader.

**Signature:**

```graphql
subscription {
  perpTrades(where: SubPerpTradesFilter!): [PerpTrade!]!
}
```

**Example:**

```graphql
subscription WatchTrades($trader: String!) {
  perpTrades(where: { trader: $trader }) {
    id
    isOpen
    leverage
    state {
      pnlCollateral
      pnlPct
    }
  }
}
```

***

### perpTradeHistory

Subscribe to trade events for a specific trader.

**Signature:**

```graphql
subscription {
  perpTradeHistory(where: SubPerpTradeHistoryFilter!): [PerpTradeHistoryItem!]!
}
```

**Example:**

```graphql
subscription WatchTradeEvents($trader: String!) {
  perpTradeHistory(where: { trader: $trader }) {
    id
    tradeChangeType
    realizedPnlCollateral
    block { block_ts }
  }
}
```

***

### perpBorrowing

Subscribe to borrowing rate updates for a specific market.

**Signature:**

```graphql
subscription {
  perpBorrowing(collateralId: Int!, marketId: Int!): PerpBorrowing
}
```

**Example:**

```graphql
subscription WatchMarket($collateralId: Int!, $marketId: Int!) {
  perpBorrowing(collateralId: $collateralId, marketId: $marketId) {
    price
    oiLong
    oiShort
    feesPerHourLong
    feesPerHourShort
  }
}
```

***

### perpBorrowings

Subscribe to updates for all markets.

**Signature:**

```graphql
subscription {
  perpBorrowings: [PerpBorrowingShortInfo!]!
}
```

**Example:**

```graphql
subscription WatchAllMarkets {
  perpBorrowings {
    marketId
    baseToken { symbol }
    visible
  }
}
```

***

## Types Reference

### PerpTrade

```graphql
type PerpTrade {
  id: Int!
  trader: String!
  isOpen: Boolean!
  isLong: Boolean!
  tradeType: PerpTradeType!
  leverage: Float!
  collateralAmount: Int!
  openCollateralAmount: Int!
  openPrice: Float!
  closePrice: Float
  sl: Float                      # Stop loss price
  tp: Float                      # Take profit price
  perpBorrowing: PerpBorrowingShortInfo!
  openBlock: Block
  closeBlock: Block
  state: PerpTradeState
}
```

### PerpTradeState

Real-time position state with PnL and fees.

```graphql
type PerpTradeState {
  pnlCollateral: Float!
  pnlPct: Float!
  pnlCollateralAfterFees: Float!
  positionValue: Float!
  liquidationPrice: Float!
  borrowingFeeCollateral: Float!
  borrowingFeePct: Float!
  closingFeeCollateral: Float!
  closingFeePct: Float!
  remainingCollateralAfterFees: Float!
}
```

### PerpBorrowing

Complete market information.

```graphql
type PerpBorrowing {
  marketId: Int!
  baseToken: Token!
  quoteToken: Token!
  collateralToken: Token!
  price: Float!
  oiLong: Int!
  oiShort: Int!
  oiMax: Int!
  feesPerHourLong: Float!
  feesPerHourShort: Float!
  openFeePct: Float!
  closeFeePct: Float!
  triggerOrderFeePct: Float!
  maxLeverage: Float!
  minLeverage: Float!
  minPositionSizeUSD: Int!
  visible: Boolean!
}
```

### Enums

**PerpTradeType:**

* `trade`: Market trade
* `limit`: Limit order
* `stop`: Stop order

**PerpTradeChangeType:**

* `position_opened`
* `position_closed_user`
* `position_closed_sl`
* `position_closed_tp`
* `position_liquidated`
* `sl_updated`
* `tp_updated`
* `limit_order_created`
* `stop_order_created`
* `order_triggered`
* `order_closed_user`

***


# Liquidity Provider (LP) API

Query and subscribe to liquidity pool data including vaults, deposits, withdrawals, and revenue tracking.

## Table of Contents

* [LP API Reference](#lp-api-reference)
  * [Table of Contents](#table-of-contents)
  * [Queries](#queries)
    * [vaults](#vaults)
    * [deposits](#deposits)
    * [depositHistory](#deposithistory)
    * [withdrawRequests](#withdrawrequests)
    * [epochDurationDays](#epochdurationdays)
    * [epochDurationHours](#epochdurationhours)
  * [Subscriptions](#subscriptions)
    * [lpVaults](#lpvaults)
    * [lpDeposits](#lpdeposits)
    * [lpDepositHistory](#lpdeposithistory)
    * [lpWithdrawRequests](#lpwithdrawrequests)
  * [Types Reference](#types-reference)
    * [LpVault](#lpvault)
    * [LpDeposit](#lpdeposit)
    * [LpDepositHistoryItem](#lpdeposithistoryitem)
    * [LpWithdrawRequest](#lpwithdrawrequest)
    * [RevenueInfo](#revenueinfo)
  * [Calculations](#calculations)
    * [Deposit Value](#deposit-value)
    * [Estimated Withdrawal Value](#estimated-withdrawal-value)
    * [Time Until Withdrawal](#time-until-withdrawal)
    * [APY Calculation](#apy-calculation)

## Queries

### vaults

List all liquidity vaults with their metrics.

**Signature:**

```graphql
vaults(
  where: LpVaultsFilter
  limit: Int
  offset: Int
  order_by: LpVaultsOrder
  order_desc: Boolean
): [LpVault!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `address`: Filter by vault address
* `limit`: Max results
* `offset`: Skip results
* `order_by`: Sort by `address`
* `order_desc`: Sort descending

**Returns:** Array of `LpVault` objects

**Example - Get All Vaults:**

```graphql
query GetAllVaults {
  lp {
    vaults {
      address
      collateralDenom
      collateralToken {
        symbol
        name
        logoUrl
        decimals
      }
      tvl
      sharePrice
      apy
      availableAssets
      currentEpoch
      epochStart
      sharesDenom
      sharesERC20
      collateralERC20
      revenueInfo {
        RevenueCumulative
        NetProfit
        TraderLosses
        ClosedPnl
        CurrentEpochPositiveOpenPnl
        Liabilities
        Rewards
      }
    }
  }
}
```

**Example - Get Specific Vault:**

```graphql
query GetVault($address: String!) {
  lp {
    vaults(where: { address: $address }) {
      address
      tvl
      sharePrice
      apy
      availableAssets
    }
  }
}
```

**Key Fields Explained:**

* `tvl`: Total value locked in the vault
* `sharePrice`: Current value of one vault share
* `apy`: Annual percentage yield (annualized return)
* `availableAssets`: Assets not currently in open positions
* `currentEpoch`: Current epoch number
* `epochStart`: Unix timestamp when current epoch started

**Use Cases:**

* Display vault list with metrics
* Compare vault performance
* Show available liquidity
* Calculate potential returns

***

### deposits

Get user deposits in vaults.

**Signature:**

```graphql
deposits(
  where: LpDepositsFilter
  limit: Int
  offset: Int
  order_by: LpDepositsOrder
  order_desc: Boolean
): [LpDeposit!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `depositor`: Filter by depositor address
  * `vault`: Filter by vault address
* `limit`: Max results
* `offset`: Skip results
* `order_by`: Sort by `depositor` or `vault`
* `order_desc`: Sort descending

**Returns:** Array of `LpDeposit` objects

**Example - Get User Deposits:**

```graphql
query GetUserDeposits($user: String!) {
  lp {
    deposits(where: { depositor: $user }) {
      depositor
      shares
      vault {
        address
        collateralToken { symbol }
        sharePrice
        tvl
        apy
      }
    }
  }
}
```

**Example - Calculate Deposit Value:**

```graphql
query CalculateDepositValue($user: String!) {
  lp {
    deposits(where: { depositor: $user }) {
      shares
      vault {
        sharePrice
        collateralToken {
          symbol
          decimals
        }
      }
    }
  }
}
```

To calculate value:

```javascript
const value = (shares * sharePrice) / (10 ** decimals)
```

**Use Cases:**

* Display user's LP positions
* Calculate total deposited value
* Track share ownership
* Portfolio management

***

### depositHistory

Get historical deposit and withdrawal events.

**Signature:**

```graphql
depositHistory(
  where: LpDepositHistoryFilter
  limit: Int
  offset: Int
  order_by: LpDepositHistoryOrder
  order_desc: Boolean
): [LpDepositHistoryItem!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `depositor`: Filter by depositor address
  * `vault`: Filter by vault address
* `limit`: Max results
* `offset`: Skip results
* `order_by`: Sort by `depositor`, `sequence`, or `vault`
* `order_desc`: Sort descending

**Returns:** Array of `LpDepositHistoryItem` objects

**Example - Get Deposit History:**

```graphql
query GetDepositHistory($user: String!) {
  lp {
    depositHistory(
      where: { depositor: $user }
      order_by: sequence
      order_desc: true
      limit: 50
    ) {
      id
      depositor
      amount
      shares
      isWithdraw
      vault {
        address
        collateralToken { symbol }
      }
      block {
        block
        block_ts
      }
    }
  }
}
```

**Example - Filter Vault History:**

```graphql
query GetVaultHistory($vaultAddress: String!) {
  lp {
    depositHistory(
      where: { vault: $vaultAddress }
      order_by: sequence
      order_desc: true
      limit: 100
    ) {
      depositor
      amount
      shares
      isWithdraw
      block { block_ts }
    }
  }
}
```

**Key Fields:**

* `isWithdraw`: `true` for withdrawals, `false` for deposits
* `amount`: Amount of collateral deposited/withdrawn
* `shares`: Shares minted (deposit) or burned (withdrawal)

**Use Cases:**

* Build activity timeline
* Track deposit/withdrawal events
* Audit vault operations
* Calculate historical returns

***

### withdrawRequests

Get pending withdrawal requests.

**Signature:**

```graphql
withdrawRequests(
  where: LpWithdrawRequestsFilter
  limit: Int
  offset: Int
  order_by: LpWithdrawRequestsOrder
  order_desc: Boolean
): [LpWithdrawRequest!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `depositor`: Filter by depositor address
  * `vault`: Filter by vault address
* `limit`: Max results
* `offset`: Skip results
* `order_by`: Sort by `depositor`, `unlock_epoch`, or `vault`
* `order_desc`: Sort descending

**Returns:** Array of `LpWithdrawRequest` objects

**Example - Get User Withdrawals:**

```graphql
query GetWithdrawRequests($user: String!) {
  lp {
    withdrawRequests(where: { depositor: $user }) {
      depositor
      shares
      status
      unlockEpoch
      autoRedeem
      vault {
        address
        collateralToken { symbol }
        currentEpoch
        sharePrice
      }
    }
  }
}
```

**Example - Check Withdrawal Status:**

```graphql
query CheckWithdrawalReady($user: String!) {
  lp {
    withdrawRequests(where: { depositor: $user }) {
      shares
      unlockEpoch
      autoRedeem
      vault {
        currentEpoch
        sharePrice
      }
    }
  }
}
```

To check if ready:

```javascript
const isReady = vault.currentEpoch >= unlockEpoch
const estimatedValue = (shares * vault.sharePrice) / (10 ** decimals)
```

**Key Fields:**

* `status`: Withdrawal request status
* `unlockEpoch`: Epoch when withdrawal becomes available
* `autoRedeem`: If `true`, automatically redeemed when unlocked

**Use Cases:**

* Display pending withdrawals
* Calculate withdrawal timing
* Show estimated withdrawal value
* Notify when ready

***

### epochDurationDays

Get epoch duration in days.

**Signature:**

```graphql
epochDurationDays: Int!
```

**Example:**

```graphql
query GetEpochDuration {
  lp {
    epochDurationDays
    epochDurationHours
  }
}
```

***

### epochDurationHours

Get epoch duration in hours.

**Signature:**

```graphql
epochDurationHours: Int!
```

***

## Subscriptions

Subscribe to real-time LP data updates.

### lpVaults

Subscribe to all vault updates.

**Signature:**

```graphql
subscription {
  lpVaults: [LpVault!]!
}
```

**Example:**

```graphql
subscription WatchVaults {
  lpVaults {
    address
    tvl
    sharePrice
    apy
    availableAssets
    currentEpoch
  }
}
```

**Use Cases:**

* Real-time TVL tracking
* Live APY updates
* Monitor available liquidity
* Track epoch changes

***

### lpDeposits

Subscribe to user deposit updates.

**Signature:**

```graphql
subscription {
  lpDeposits(where: SubLpDepositsFilter!): [LpDeposit!]!
}
```

**Example:**

```graphql
subscription WatchUserDeposits($user: String!) {
  lpDeposits(where: { depositor: $user }) {
    depositor
    shares
    vault {
      address
      sharePrice
    }
  }
}
```

**Use Cases:**

* Update user portfolio in real-time
* Track share balance changes
* Monitor position value

***

### lpDepositHistory

Subscribe to deposit/withdrawal events.

**Signature:**

```graphql
subscription {
  lpDepositHistory(where: SubLpDepositHistoryFilter!): [LpDepositHistoryItem!]!
}
```

**Example:**

```graphql
subscription WatchDepositEvents($user: String!, $vault: String!) {
  lpDepositHistory(where: { depositor: $user, vault: $vault }) {
    id
    amount
    shares
    isWithdraw
    block { block_ts }
  }
}
```

**Use Cases:**

* Real-time activity feed
* Instant transaction notifications
* Live history updates

***

### lpWithdrawRequests

Subscribe to withdrawal request updates.

**Signature:**

```graphql
subscription {
  lpWithdrawRequests(where: SubLpWithdrawRequestsFilter!): [LpWithdrawRequest!]!
}
```

**Example:**

```graphql
subscription WatchWithdrawals($user: String!, $vault: String!) {
  lpWithdrawRequests(where: { depositor: $user, vault: $vault }) {
    shares
    status
    unlockEpoch
    vault {
      currentEpoch
    }
  }
}
```

**Use Cases:**

* Notify when withdrawal ready
* Track withdrawal status
* Update UI when unlocked

***

## Types Reference

### LpVault

```graphql
type LpVault {
  address: String!
  collateralDenom: String!
  collateralToken: Token!
  collateralERC20: String!
  sharesDenom: String!
  sharesERC20: String!
  tvl: Int!
  sharePrice: Float!
  apy: Float
  availableAssets: Int!
  currentEpoch: Int!
  epochStart: Int!
  revenueInfo: RevenueInfo!
}
```

### LpDeposit

```graphql
type LpDeposit {
  depositor: String!
  shares: Int!
  vault: LpVault!
}
```

### LpDepositHistoryItem

```graphql
type LpDepositHistoryItem {
  id: Int!
  depositor: String!
  amount: Int!
  shares: Int!
  isWithdraw: Boolean!
  vault: LpVault!
  block: Block!
}
```

### LpWithdrawRequest

```graphql
type LpWithdrawRequest {
  depositor: String!
  shares: Int!
  status: String!
  unlockEpoch: Int!
  autoRedeem: Boolean!
  vault: LpVault!
}
```

### RevenueInfo

Tracks vault revenue and performance.

```graphql
type RevenueInfo {
  RevenueCumulative: Int!              # Total revenue earned
  NetProfit: Int!                       # Current net profit
  TraderLosses: Int!                    # Profits from trader losses
  ClosedPnl: Int!                       # Realized PnL from closed positions
  CurrentEpochPositiveOpenPnl: Int!     # Current epoch unrealized gains
  Liabilities: Int!                     # Current liabilities (trader profits)
  Rewards: Int!                         # Rewards received
}
```

***

## Calculations

### Deposit Value

```javascript
// Calculate current value of deposit
const depositValue = (shares * sharePrice) / (10 ** decimals)
```

### Estimated Withdrawal Value

```javascript
// For pending withdrawal
const estimatedValue = (shares * currentSharePrice) / (10 ** decimals)
```

### Time Until Withdrawal

```javascript
// Calculate epochs remaining
const epochsRemaining = unlockEpoch - currentEpoch

// Calculate time remaining (assuming epoch duration)
const hoursRemaining = epochsRemaining * epochDurationHours
```

### APY Calculation

APY is calculated from historical revenue:

```javascript
// Simplified APY calculation
const apy = (revenueInfo.NetProfit / tvl) * (365 / epochDurationDays)
```


# Oracle API

Query and subscribe to token price feeds and metadata.

## Table of Contents

* [Queries](#queries)
  * [token](#token)
  * [tokens](#tokens)
  * [tokenPricesUsd](#tokenpricesusd)
* [Subscriptions](#subscriptions)
* [Types Reference](#types-reference)

## Queries

### token

Get details for a specific token by ID.

**Signature:**

```graphql
token(id: Int!): Token!
```

**Parameters:**

* `id`: Token ID (integer)

**Returns:** `Token` object

**Example:**

```graphql
query GetToken {
  oracle {
    token(id: 1) {
      id
      symbol
      name
      logoUrl
      tradingViewSymbol
      permissionGroup
    }
  }
}
```

**Use Cases:**

* Get token metadata
* Display token information
* Resolve token ID to symbol

***

### tokens

List all available tokens with filtering and ordering.

**Signature:**

```graphql
tokens(
  where: TokensFilter
  limit: Int
  order_by: TokensOrder
  order_desc: Boolean
): [Token!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `name`: Filter by token name (string match)
  * `permissionGroup`: Filter by permission group
* `limit`: Max results
* `order_by`: Sort by `id`, `name`, or `permission_group`
* `order_desc`: Sort descending

**Returns:** Array of `Token` objects

**Example - List All Tokens:**

```graphql
query ListAllTokens {
  oracle {
    tokens(order_by: name) {
      id
      symbol
      name
      logoUrl
      tradingViewSymbol
    }
  }
}
```

**Example - Search by Name:**

```graphql
query SearchToken($searchTerm: String!) {
  oracle {
    tokens(where: { name: $searchTerm }) {
      id
      symbol
      name
    }
  }
}
```

**Example - Filter by Permission Group:**

```graphql
query GetPermittedTokens {
  oracle {
    tokens(where: { permissionGroup: 1 }) {
      id
      symbol
      name
    }
  }
}
```

**Use Cases:**

* Build token selector dropdown
* Search tokens by name
* List tradeable assets
* Filter by permissions

***

### tokenPricesUsd query

Get current USD prices for tokens.

**Signature:**

```graphql
tokenPricesUsd(
  where: TokenPriceUsdFilter
  limit: Int
  order_by: TokenPriceUsdOrder
  order_desc: Boolean
): [TokenPricesUsd!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `tokenId`: Filter by specific token ID
* `limit`: Max results
* `order_by`: Sort by `oracle_token_id`
* `order_desc`: Sort descending

**Returns:** Array of `TokenPricesUsd` objects

**Example - Get All Prices:**

```graphql
query GetAllPrices {
  oracle {
    tokenPricesUsd {
      token {
        id
        symbol
        name
      }
      priceUsd
      lastUpdatedBlock {
        block
        block_ts
      }
    }
  }
}
```

**Example - Get Specific Token Price:**

```graphql
query GetBTCPrice {
  oracle {
    tokenPricesUsd(where: { tokenId: 1 }) {
      token {
        symbol
      }
      priceUsd
      lastUpdatedBlock {
        block
        block_ts
      }
    }
  }
}
```

**Example - Get Multiple Prices:**

```graphql
query GetPrices {
  btc: oracle {
    tokenPricesUsd(where: { tokenId: 1 }) {
      priceUsd
      lastUpdatedBlock { block_ts }
    }
  }
  eth: oracle {
    tokenPricesUsd(where: { tokenId: 2 }) {
      priceUsd
      lastUpdatedBlock { block_ts }
    }
  }
}
```

**Key Fields:**

* `priceUsd`: Current price in USD (float)
* `lastUpdatedBlock`: Block info for last price update

**Use Cases:**

* Display token prices
* Calculate position values
* Monitor price changes
* Verify price freshness

***

## Subscriptions

Subscribe to real-time price updates.

### tokenPricesUsd

Subscribe to price updates for specific or all tokens.

**Signature:**

```graphql
subscription {
  tokenPricesUsd(where: SubTokenPriceUsdFilter): [TokenPricesUsd!]!
}
```

**Example - Subscribe to All Prices:**

```graphql
subscription WatchAllPrices {
  tokenPricesUsd {
    token {
      id
      symbol
    }
    priceUsd
    lastUpdatedBlock {
      block
      block_ts
    }
  }
}
```

**Example - Subscribe to Specific Token:**

```graphql
subscription WatchBTCPrice {
  tokenPricesUsd(where: { tokenId: 1 }) {
    token {
      symbol
    }
    priceUsd
    lastUpdatedBlock {
      block_ts
    }
  }
}
```

**Use Cases:**

* Real-time price tickers
* Live trading dashboards
* Price alert systems
* Chart updates

***

### userBalances

Subscribe to user balance updates across all tokens.

**Signature:**

```graphql
subscription {
  userBalances(where: SubUserBalancesFilter!): [Balance!]!
}
```

**Example:**

```graphql
subscription WatchUserBalances($user: String!) {
  userBalances(where: { user: $user }) {
    amount
    token_info {
      symbol
      decimals
      bank_denom
      erc20_contract_address
      logo
      name
      type
    }
  }
}
```

**Use Cases:**

* Track wallet balances
* Monitor account changes
* Real-time portfolio updates
* Display available funds

***

## Types Reference

### Token

```graphql
type Token {
  id: Int!
  symbol: String!
  name: String!
  logoUrl: String
  tradingViewSymbol: String
  permissionGroup: Int!
}
```

**Field Details:**

* `id`: Unique token identifier
* `symbol`: Token ticker (e.g., "BTC", "ETH")
* `name`: Full token name (e.g., "Bitcoin", "Ethereum")
* `logoUrl`: URL to token logo image
* `tradingViewSymbol`: Symbol for TradingView charts
* `permissionGroup`: Access/trading permission level

***

### TokenPricesUsd

```graphql
type TokenPricesUsd {
  token: Token
  priceUsd: Float!
  lastUpdatedBlock: Block!
}
```

**Field Details:**

* `token`: Associated token metadata
* `priceUsd`: Current USD price
* `lastUpdatedBlock`: Last price update info
  * `block`: Block height
  * `block_ts`: Timestamp (RFC3339 format)

***

### Balance

```graphql
type Balance {
  amount: String!
  token_info: TokenInfo!
}
```

***

### TokenInfo

Detailed token information for balances.

```graphql
type TokenInfo {
  bank_denom: String!
  symbol: String!
  name: String!
  decimals: Int!
  logo: String
  type: TokenType!
  erc20_contract_address: String!
}
```

**TokenType Enum:**

* `bank`: Native Cosmos SDK token
* `erc20`: ERC20 token on EVM

***

## Working with Prices

### Price Freshness

Always check when price was last updated:

```javascript
const now = Date.now()
const lastUpdate = new Date(lastUpdatedBlock.block_ts).getTime()
const ageSeconds = (now - lastUpdate) / 1000

if (ageSeconds > 60) {
  console.warn('Price may be stale')
}
```

### Price Formatting

```javascript
// Format price with appropriate decimals
const formatPrice = (priceUsd) => {
  if (priceUsd >= 1000) {
    return priceUsd.toFixed(2)
  } else if (priceUsd >= 1) {
    return priceUsd.toFixed(4)
  } else {
    return priceUsd.toFixed(6)
  }
}
```

### Calculate Position Value

```javascript
// Calculate position value in USD
const positionValueUsd = (collateralAmount, priceUsd, decimals) => {
  const actualAmount = collateralAmount / (10 ** decimals)
  return actualAmount * priceUsd
}
```

### Balance Conversion

```javascript
// Convert balance amount string to number
const getBalanceValue = (balance) => {
  const amount = parseFloat(balance.amount)
  return amount / (10 ** balance.token_info.decimals)
}
```

***

## Best Practices

### Caching Prices

```javascript
// Cache prices for short periods to reduce queries
const priceCache = new Map()
const CACHE_TTL = 5000 // 5 seconds

const getCachedPrice = async (tokenId) => {
  const cached = priceCache.get(tokenId)
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.price
  }
  
  const price = await fetchPrice(tokenId)
  priceCache.set(tokenId, { price, timestamp: Date.now() })
  return price
}
```

### Handling Multiple Tokens

```javascript
// Batch query multiple prices efficiently
const query = `
  query GetMultiplePrices($tokenIds: [Int!]!) {
    oracle {
      prices: tokenPricesUsd(limit: 100) {
        token { id symbol }
        priceUsd
      }
    }
  }
`

// Filter client-side for needed tokens
const relevantPrices = data.oracle.prices.filter(p => 
  tokenIds.includes(p.token.id)
)
```

### Subscription Management

```javascript
// Manage price subscription lifecycle
let priceSubscription

const subscribeToPrices = (tokenIds) => {
  // Unsubscribe from previous
  if (priceSubscription) {
    priceSubscription.unsubscribe()
  }
  
  // Subscribe to new set
  priceSubscription = client.subscribe({
    query: PRICE_SUBSCRIPTION,
    variables: { tokenIds }
  }).subscribe({
    next: (data) => updatePrices(data),
    error: (err) => console.error(err)
  })
}
```


# Fees API

Query fee data including transaction-level fees, daily statistics, and protocol/trader summaries.

## Table of Contents

* [Queries](#queries)
  * [feeTransaction](#feetransaction)
  * [feeTransactions](#feetransactions)
  * [feeDailyStats](#feedailystats)
  * [feeAnalytics](#feeanalytics)
  * [protocolFeeSummary](#protocolfeesummary)
  * [traderFeeSummary](#traderfeesummary)
* [Types Reference](#types-reference)

## Queries

### feeTransaction

Get a specific fee transaction by ID.

**Signature:**

```graphql
feeTransaction(id: ID!): FeeTransaction
```

**Parameters:**

* `id`: Transaction ID (string)

**Returns:** `FeeTransaction` object or null

**Example:**

```graphql
query GetFeeTransaction {
  fee {
    feeTransaction(id: "abc123") {
      id
      traderAddress
      feeType
      totalFeeCharged
      govFee
      vaultFee
      referrerAllocation
      triggerFee
      collateralDenom
      blockHeight
      blockTime
    }
  }
}
```

***

### feeTransactions

List fee transactions with filtering and pagination.

**Signature:**

```graphql
feeTransactions(
  filter: FeeTransactionFilter
  limit: Int = 100
  offset: Int = 0
): [FeeTransaction!]!
```

**Parameters:**

* `filter`: Filter object (optional)
  * `traderAddress`: Filter by trader
  * `collateralDenom`: Filter by collateral token
  * `feeType`: Filter by `OPENING` or `CLOSING`
  * `fromDate`: Start date (RFC3339)
  * `toDate`: End date (RFC3339)
  * `minAmount`: Minimum fee amount
  * `maxAmount`: Maximum fee amount
* `limit`: Max results (default: 100)
* `offset`: Skip results (default: 0)

**Returns:** Array of `FeeTransaction` objects

**Example - Get User Fees:**

```graphql
query GetUserFees($trader: String!) {
  fee {
    feeTransactions(
      filter: { traderAddress: $trader }
      limit: 50
    ) {
      id
      feeType
      totalFeeCharged
      govFee
      vaultFee
      referrerAllocation
      triggerFee
      blockTime
      tradeId
      orderType
    }
  }
}
```

**Example - Filter by Date Range:**

```graphql
query GetFeesInRange {
  fee {
    feeTransactions(
      filter: {
        fromDate: "2024-01-01T00:00:00Z"
        toDate: "2024-01-31T23:59:59Z"
        feeType: OPENING
      }
      limit: 100
    ) {
      id
      traderAddress
      totalFeeCharged
      blockTime
    }
  }
}
```

**Example - Large Fees Only:**

```graphql
query GetLargeFees {
  fee {
    feeTransactions(
      filter: {
        minAmount: 1000000  # Filter fees > 1,000,000
      }
      limit: 20
    ) {
      id
      traderAddress
      totalFeeCharged
      feeType
    }
  }
}
```

**Use Cases:**

* User fee history
* Protocol fee monitoring
* Large transaction tracking
* Fee analysis by period

***

### feeDailyStats

Get aggregated daily fee statistics.

**Signature:**

```graphql
feeDailyStats(
  filter: FeeDailyStatsFilter
  limit: Int = 30
  offset: Int = 0
): [FeeDailyStats!]!
```

**Parameters:**

* `filter`: Filter object (optional)
  * `traderAddress`: Filter by specific trader
  * `collateralDenom`: Filter by collateral token
  * `fromDate`: Start date
  * `toDate`: End date
  * `protocolWide`: If true, get protocol-wide stats
* `limit`: Max results (default: 30)
* `offset`: Skip results (default: 0)

**Returns:** Array of `FeeDailyStats` objects

**Example - Protocol Daily Stats:**

```graphql
query GetProtocolDailyStats {
  fee {
    feeDailyStats(
      filter: { protocolWide: true }
      limit: 30
    ) {
      date
      collateralDenom
      openingFeeCount
      openingFeeTotal
      openingGovFee
      openingReferrerFee
      openingVaultFee
      closingFeeCount
      closingFeeTotal
      closingGovFee
      closingReferrerFee
      closingVaultFee
      closingTriggerFee
      totalBadDebt
      updatedAt
    }
  }
}
```

**Example - Trader Daily Stats:**

```graphql
query GetTraderDailyStats($trader: String!) {
  fee {
    feeDailyStats(
      filter: {
        traderAddress: $trader
        fromDate: "2024-01-01T00:00:00Z"
      }
      limit: 90
    ) {
      date
      openingFeeTotal
      closingFeeTotal
      totalBadDebt
    }
  }
}
```

**Use Cases:**

* Daily fee charts
* Historical fee analysis
* Revenue tracking
* Trader fee breakdown

***

### feeAnalytics

Get enhanced fee analytics with additional metrics.

**Signature:**

```graphql
feeAnalytics(
  filter: FeeDailyStatsFilter
  limit: Int = 30
  offset: Int = 0
): [FeeAnalytics!]!
```

**Parameters:** Same as `feeDailyStats`

**Returns:** Array of `FeeAnalytics` objects

**Example:**

```graphql
query GetFeeAnalytics {
  fee {
    feeAnalytics(
      filter: { protocolWide: true }
      limit: 30
    ) {
      date
      collateralDenom
      openingCount
      openingTotal
      openingGov
      openingReferrer
      openingVault
      openingTrigger
      closingCount
      closingTotal
      closingGov
      closingReferrer
      closingVault
      closingTrigger
      totalFeesAll
      avgFeeMultiplier
      totalBadDebt
      traderAddress
    }
  }
}
```

**Key Differences from feeDailyStats:**

* Includes `avgFeeMultiplier`
* Includes `totalFeesAll` (sum of opening + closing)
* More granular trigger fee breakdown

**Use Cases:**

* Advanced analytics dashboards
* Fee multiplier tracking
* Comprehensive fee reports

***

### protocolFeeSummary

Get protocol-wide fee summary for a time period.

**Signature:**

```graphql
protocolFeeSummary(
  collateralDenom: String
  fromDate: Time
  toDate: Time
): ProtocolFeeSummary!
```

**Parameters:**

* `collateralDenom`: Optional collateral filter
* `fromDate`: Start date (optional)
* `toDate`: End date (optional)

**Returns:** `ProtocolFeeSummary` object

**Example - All-Time Protocol Summary:**

```graphql
query GetProtocolSummary {
  fee {
    protocolFeeSummary {
      period {
        fromDate
        toDate
      }
      totalFees
      totalOpeningFees
      totalClosingFees
      totalGovFees
      totalVaultFees
      totalReferrerFees
      totalTriggerFees
      totalBadDebt
      openingCount
      closingCount
      uniqueTraders
      avgFeeMultiplier
    }
  }
}
```

**Example - Monthly Summary:**

```graphql
query GetMonthlySummary {
  fee {
    protocolFeeSummary(
      fromDate: "2024-01-01T00:00:00Z"
      toDate: "2024-01-31T23:59:59Z"
    ) {
      totalFees
      totalOpeningFees
      totalClosingFees
      uniqueTraders
      openingCount
      closingCount
    }
  }
}
```

**Example - Per-Collateral Summary:**

```graphql
query GetCollateralSummary {
  fee {
    protocolFeeSummary(
      collateralDenom: "uusdc"
      fromDate: "2024-01-01T00:00:00Z"
    ) {
      totalFees
      totalVaultFees
      totalGovFees
      uniqueTraders
    }
  }
}
```

**Use Cases:**

* Protocol revenue reports
* KPI tracking
* Performance metrics
* Time-period comparisons

***

### traderFeeSummary

Get fee summary for a specific trader.

**Signature:**

```graphql
traderFeeSummary(
  traderAddress: String!
  collateralDenom: String
  fromDate: Time
  toDate: Time
): TraderFeeSummary!
```

**Parameters:**

* `traderAddress`: Trader's address (required)
* `collateralDenom`: Optional collateral filter
* `fromDate`: Start date (optional)
* `toDate`: End date (optional)

**Returns:** `TraderFeeSummary` object

**Example - All-Time Trader Summary:**

```graphql
query GetTraderSummary($trader: String!) {
  fee {
    traderFeeSummary(traderAddress: $trader) {
      traderAddress
      period {
        fromDate
        toDate
      }
      totalFees
      totalOpeningFees
      totalClosingFees
      totalReferrerAllocation
      totalBadDebt
      openingCount
      closingCount
      avgFeeMultiplier
    }
  }
}
```

**Example - Monthly Trader Summary:**

```graphql
query GetTraderMonthlySummary($trader: String!) {
  fee {
    traderFeeSummary(
      traderAddress: $trader
      fromDate: "2024-01-01T00:00:00Z"
      toDate: "2024-01-31T23:59:59Z"
    ) {
      totalFees
      openingCount
      closingCount
      avgFeeMultiplier
    }
  }
}
```

**Use Cases:**

* User dashboards
* Trading cost analysis
* Fee optimization insights
* Referral tracking

***

## Types Reference

### FeeTransaction

Complete fee transaction details.

```graphql
type FeeTransaction {
  id: ID!
  traderAddress: String!
  tradeId: Int!
  tradeIndex: Int!
  feeType: FeeType!
  collateralDenom: String!
  totalFeeCharged: Int!
  govFee: Int!
  netGovFee: Int!
  vaultFee: Int!
  referrerAllocation: Int!
  triggerFee: Int!
  openFeeComponent: Int
  triggerFeeComponent: Int
  triggererReward: Int
  collateralLeft: Int
  badDebt: Int!
  feeMultiplier: Float!
  orderType: String
  catalystAddress: String
  blockHeight: Int!
  blockTime: Time!
  createdAt: Time!
}
```

**Key Fields:**

* `totalFeeCharged`: Total fee paid by trader
* `govFee`: Fee to protocol governance
* `vaultFee`: Fee to LP vault
* `referrerAllocation`: Fee to referrer (if any)
* `triggerFee`: Fee paid to order trigger executor
* `badDebt`: Bad debt if position liquidated with insufficient collateral
* `feeMultiplier`: Dynamic fee multiplier applied

***

### FeeDailyStats

Daily aggregated fee statistics.

```graphql
type FeeDailyStats {
  date: Time!
  collateralDenom: String!
  traderAddress: String
  openingFeeCount: Int!
  openingFeeTotal: Int!
  openingGovFee: Int!
  openingReferrerFee: Int!
  openingTriggerFee: Int!
  openingVaultFee: Int!
  closingFeeCount: Int!
  closingFeeTotal: Int!
  closingGovFee: Int!
  closingReferrerFee: Int!
  closingTriggerFee: Int!
  closingVaultFee: Int!
  totalBadDebt: Int!
  updatedAt: Time!
}
```

***

### FeeAnalytics

Enhanced analytics with additional metrics.

```graphql
type FeeAnalytics {
  date: Time!
  collateralDenom: String!
  traderAddress: String
  openingCount: Int!
  openingTotal: Int!
  openingGov: Int!
  openingReferrer: Int!
  openingTrigger: Int!
  openingVault: Int!
  closingCount: Int!
  closingTotal: Int!
  closingGov: Int!
  closingReferrer: Int!
  closingTrigger: Int!
  closingVault: Int!
  totalFeesAll: Int!
  avgFeeMultiplier: Float!
  totalBadDebt: Int!
}
```

***

### ProtocolFeeSummary

Protocol-wide fee summary.

```graphql
type ProtocolFeeSummary {
  period: TimePeriod!
  totalFees: Int!
  totalOpeningFees: Int!
  totalClosingFees: Int!
  totalGovFees: Int!
  totalVaultFees: Int!
  totalReferrerFees: Int!
  totalTriggerFees: Int!
  totalBadDebt: Int!
  openingCount: Int!
  closingCount: Int!
  uniqueTraders: Int!
  avgFeeMultiplier: Float!
}
```

***

### TraderFeeSummary

Per-trader fee summary.

```graphql
type TraderFeeSummary {
  traderAddress: String!
  period: TimePeriod!
  totalFees: Int!
  totalOpeningFees: Int!
  totalClosingFees: Int!
  totalReferrerAllocation: Int!
  totalBadDebt: Int!
  openingCount: Int!
  closingCount: Int!
  avgFeeMultiplier: Float!
}
```

***

### Enums

**FeeType:**

* `OPENING`: Fees charged when opening positions
* `CLOSING`: Fees charged when closing positions

***

## Fee Calculations

### Total Protocol Revenue

```javascript
const totalRevenue = protocolFeeSummary.totalGovFees + 
                     protocolFeeSummary.totalVaultFees
```

### Average Fee Per Trade

```javascript
const avgOpeningFee = protocolFeeSummary.totalOpeningFees / 
                      protocolFeeSummary.openingCount

const avgClosingFee = protocolFeeSummary.totalClosingFees / 
                      protocolFeeSummary.closingCount
```

### Fee Breakdown Percentages

```javascript
const calculateBreakdown = (summary) => {
  const total = summary.totalFees
  return {
    govPct: (summary.totalGovFees / total) * 100,
    vaultPct: (summary.totalVaultFees / total) * 100,
    referrerPct: (summary.totalReferrerFees / total) * 100,
    triggerPct: (summary.totalTriggerFees / total) * 100
  }
}
```

### Convert Fee Amounts

```javascript
// Fees are in smallest unit, convert to decimal
const convertFee = (feeAmount, decimals) => {
  return feeAmount / (10 ** decimals)
}

// Example: USDC fees (6 decimals)
const feeUSDC = convertFee(1000000, 6) // = 1.0 USDC
```

***

## Best Practices

### Date Range Queries

```javascript
// Get current month stats
const now = new Date()
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1)
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0)

const query = `
  query GetMonthlyStats {
    fee {
      protocolFeeSummary(
        fromDate: "${firstDay.toISOString()}"
        toDate: "${lastDay.toISOString()}"
      ) {
        totalFees
        uniqueTraders
      }
    }
  }
`
```

### Pagination for Large Datasets

```javascript
// Fetch all fee transactions in batches
const fetchAllFees = async (trader, limit = 100) => {
  let allFees = []
  let offset = 0
  let hasMore = true
  
  while (hasMore) {
    const { data } = await client.query({
      query: FEE_TRANSACTIONS_QUERY,
      variables: { trader, limit, offset }
    })
    
    const fees = data.fee.feeTransactions
    allFees = allFees.concat(fees)
    
    hasMore = fees.length === limit
    offset += limit
  }
  
  return allFees
}
```

### Caching Daily Stats

```javascript
// Cache daily stats (they don't change)
const cacheDailyStats = new Map()

const getDailyStats = async (date) => {
  const dateKey = date.toISOString().split('T')[0]
  
  if (cacheDailyStats.has(dateKey)) {
    return cacheDailyStats.get(dateKey)
  }
  
  const stats = await fetchDailyStats(date)
  cacheDailyStats.set(dateKey, stats)
  return stats
}
```


# Examples: Queries

Practical examples for common query patterns using the Sai Keeper API.

## Table of Contents

* [Setup](#setup)
* [Perp Queries](#perp-queries)
* [LP Queries](#lp-queries)
* [Oracle Queries](#oracle-queries)
* [Fee Queries](#fee-queries)
* [Advanced Patterns](#advanced-patterns)

## Setup

These examples use Apollo Client, but the queries work with any GraphQL client.

```javascript
import { ApolloClient, InMemoryCache, gql } from '@apollo/client'

const client = new ApolloClient({
  uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql',
  cache: new InMemoryCache()
})
```

***

## Perp Queries

### Example 1: Get All Open Positions for a Trader

```javascript
const GET_OPEN_POSITIONS = gql`
  query GetOpenPositions($trader: String!) {
    perp {
      trades(
        where: {
          trader: $trader
          isOpen: true
        }
        order_desc: true
      ) {
        id
        isLong
        leverage
        collateralAmount
        openPrice
        sl
        tp
        perpBorrowing {
          baseToken {
            symbol
            name
            logoUrl
          }
          collateralToken {
            symbol
            decimals
          }
          marketId
        }
        openBlock {
          block_ts
        }
        state {
          pnlCollateral
          pnlPct
          pnlCollateralAfterFees
          liquidationPrice
          positionValue
          borrowingFeeCollateral
        }
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_OPEN_POSITIONS,
  variables: { trader: 'nibi1abc...' }
})

// Process results
data.perp.trades.forEach(trade => {
  const token = trade.perpBorrowing.baseToken
  const decimals = trade.perpBorrowing.collateralToken.decimals
  
  console.log(`${token.symbol} ${trade.isLong ? 'LONG' : 'SHORT'}`)
  console.log(`Leverage: ${trade.leverage}x`)
  console.log(`PnL: ${(trade.state.pnlPct * 100).toFixed(2)}%`)
  console.log(`Liquidation: $${trade.state.liquidationPrice.toFixed(2)}`)
})
```

### Example 2: Get Trade History with Realized PnL

```javascript
const GET_TRADE_HISTORY = gql`
  query GetTradeHistory($trader: String!, $limit: Int!) {
    perp {
      tradeHistory(
        where: { trader: $trader }
        limit: $limit
        order_by: sequence
        order_desc: true
      ) {
        id
        tradeChangeType
        realizedPnlCollateral
        realizedPnlPct
        block {
          block
          block_ts
        }
        trade {
          id
          isLong
          leverage
          openPrice
          closePrice
          perpBorrowing {
            baseToken {
              symbol
            }
            collateralToken {
              symbol
              decimals
            }
          }
        }
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_TRADE_HISTORY,
  variables: { trader: 'nibi1abc...', limit: 50 }
})

// Calculate total realized PnL
const totalPnL = data.perp.tradeHistory
  .filter(h => h.realizedPnlCollateral !== null)
  .reduce((sum, h) => sum + h.realizedPnlCollateral, 0)

console.log(`Total Realized PnL: ${totalPnL}`)
```

### Example 3: Get Market Information and Funding Rates

```javascript
const GET_MARKET_INFO = gql`
  query GetMarketInfo($collateralId: Int!, $marketId: Int!) {
    perp {
      borrowing(collateralId: $collateralId, marketId: $marketId) {
        marketId
        baseToken {
          symbol
          name
          logoUrl
        }
        quoteToken {
          symbol
        }
        collateralToken {
          symbol
          decimals
        }
        price
        oiLong
        oiShort
        oiMax
        feesPerHourLong
        feesPerHourShort
        openFeePct
        closeFeePct
        maxLeverage
        minLeverage
        minPositionSizeUSD
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_MARKET_INFO,
  variables: { collateralId: 1, marketId: 1 }
})

const market = data.perp.borrowing

// Calculate funding rate APR
const hourlyRateLong = market.feesPerHourLong
const aprLong = hourlyRateLong * 24 * 365 * 100

console.log(`${market.baseToken.symbol} Market`)
console.log(`Price: $${market.price}`)
console.log(`OI Long: ${market.oiLong} | Short: ${market.oiShort}`)
console.log(`Max Leverage: ${market.maxLeverage}x`)
console.log(`Funding APR Long: ${aprLong.toFixed(2)}%`)
```

### Example 4: List All Available Markets

```javascript
const GET_ALL_MARKETS = gql`
  query GetAllMarkets {
    perp {
      borrowings(order_by: base_token_name) {
        marketId
        baseToken {
          symbol
          name
          logoUrl
          tradingViewSymbol
        }
        collateralToken {
          symbol
        }
        visible
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_ALL_MARKETS
})

// Filter only visible markets
const visibleMarkets = data.perp.borrowings.filter(m => m.visible)

console.log(`Available markets: ${visibleMarkets.length}`)
visibleMarkets.forEach(m => {
  console.log(`${m.baseToken.symbol}-${m.collateralToken.symbol} (ID: ${m.marketId})`)
})
```

***

## LP Queries

### Example 5: Get All Vaults with Metrics

```javascript
const GET_ALL_VAULTS = gql`
  query GetAllVaults {
    lp {
      vaults {
        address
        collateralToken {
          symbol
          name
          logoUrl
          decimals
        }
        tvl
        sharePrice
        apy
        availableAssets
        currentEpoch
        epochStart
        sharesDenom
        collateralERC20
        sharesERC20
        revenueInfo {
          RevenueCumulative
          NetProfit
          TraderLosses
          CurrentEpochPositiveOpenPnl
          Liabilities
        }
      }
      epochDurationDays
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_ALL_VAULTS
})

data.lp.vaults.forEach(vault => {
  const decimals = vault.collateralToken.decimals
  const tvlFormatted = vault.tvl / (10 ** decimals)
  
  console.log(`${vault.collateralToken.symbol} Vault`)
  console.log(`TVL: ${tvlFormatted.toLocaleString()} ${vault.collateralToken.symbol}`)
  console.log(`APY: ${vault.apy?.toFixed(2) || 'N/A'}%`)
  console.log(`Share Price: ${vault.sharePrice}`)
  console.log(`Epoch: ${vault.currentEpoch}`)
})
```

### Example 6: Get User LP Positions

```javascript
const GET_USER_LP_POSITIONS = gql`
  query GetUserLPPositions($user: String!) {
    lp {
      deposits(where: { depositor: $user }) {
        depositor
        shares
        vault {
          address
          collateralToken {
            symbol
            decimals
          }
          sharePrice
          apy
          currentEpoch
        }
      }
      withdrawRequests(where: { depositor: $user }) {
        depositor
        shares
        status
        unlockEpoch
        autoRedeem
        vault {
          address
          collateralToken {
            symbol
            decimals
          }
          sharePrice
          currentEpoch
        }
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_USER_LP_POSITIONS,
  variables: { user: 'nibi1abc...' }
})

// Calculate total portfolio value
let totalValue = 0

data.lp.deposits.forEach(deposit => {
  const decimals = deposit.vault.collateralToken.decimals
  const value = (deposit.shares * deposit.vault.sharePrice) / (10 ** decimals)
  totalValue += value
  
  console.log(`${deposit.vault.collateralToken.symbol} Vault`)
  console.log(`Shares: ${deposit.shares}`)
  console.log(`Value: ${value.toFixed(2)}`)
  console.log(`APY: ${deposit.vault.apy?.toFixed(2) || 'N/A'}%`)
})

// Check pending withdrawals
data.lp.withdrawRequests.forEach(request => {
  const isReady = request.vault.currentEpoch >= request.unlockEpoch
  const epochsRemaining = Math.max(0, request.unlockEpoch - request.vault.currentEpoch)
  
  console.log(`\nPending Withdrawal: ${request.vault.collateralToken.symbol}`)
  console.log(`Status: ${isReady ? 'Ready' : `${epochsRemaining} epochs remaining`}`)
  console.log(`Auto-redeem: ${request.autoRedeem}`)
})

console.log(`\nTotal LP Value: ${totalValue.toFixed(2)}`)
```

### Example 7: Get Vault Deposit/Withdrawal History

```javascript
const GET_VAULT_HISTORY = gql`
  query GetVaultHistory($vaultAddress: String!, $limit: Int!) {
    lp {
      depositHistory(
        where: { vault: $vaultAddress }
        limit: $limit
        order_by: sequence
        order_desc: true
      ) {
        id
        depositor
        amount
        shares
        isWithdraw
        block {
          block
          block_ts
        }
        vault {
          collateralToken {
            symbol
            decimals
          }
        }
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_VAULT_HISTORY,
  variables: { vaultAddress: 'nibi1vault...', limit: 100 }
})

data.lp.depositHistory.forEach(event => {
  const decimals = event.vault.collateralToken.decimals
  const amount = event.amount / (10 ** decimals)
  const action = event.isWithdraw ? 'Withdrew' : 'Deposited'
  const date = new Date(event.block.block_ts).toLocaleDateString()
  
  console.log(`${date}: ${event.depositor} ${action} ${amount.toFixed(2)}`)
})
```

***

## Oracle Queries

### Example 8: Get Current Token Prices

```javascript
const GET_TOKEN_PRICES = gql`
  query GetTokenPrices {
    oracle {
      tokenPricesUsd(limit: 100) {
        token {
          id
          symbol
          name
          logoUrl
        }
        priceUsd
        lastUpdatedBlock {
          block
          block_ts
        }
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_TOKEN_PRICES
})

// Create price map
const priceMap = {}
data.oracle.tokenPricesUsd.forEach(item => {
  priceMap[item.token.symbol] = {
    price: item.priceUsd,
    lastUpdate: item.lastUpdatedBlock.block_ts
  }
})

console.log(`BTC: $${priceMap['BTC'].price.toFixed(2)}`)
console.log(`ETH: $${priceMap['ETH'].price.toFixed(2)}`)
```

### Example 9: Get Specific Token Price with Freshness Check

```javascript
const GET_TOKEN_PRICE = gql`
  query GetTokenPrice($tokenId: Int!) {
    oracle {
      tokenPricesUsd(where: { tokenId: $tokenId }) {
        token {
          symbol
          name
        }
        priceUsd
        lastUpdatedBlock {
          block
          block_ts
        }
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_TOKEN_PRICE,
  variables: { tokenId: 1 }
})

const priceData = data.oracle.tokenPricesUsd[0]
const lastUpdate = new Date(priceData.lastUpdatedBlock.block_ts)
const ageSeconds = (Date.now() - lastUpdate.getTime()) / 1000

console.log(`${priceData.token.symbol}: $${priceData.priceUsd}`)
console.log(`Last updated: ${ageSeconds.toFixed(0)}s ago`)

if (ageSeconds > 60) {
  console.warn('⚠️  Price may be stale')
}
```

***

## Fee Queries

### Example 10: Get User Fee History

```javascript
const GET_USER_FEES = gql`
  query GetUserFees($trader: String!, $limit: Int!) {
    fee {
      feeTransactions(
        filter: { traderAddress: $trader }
        limit: $limit
      ) {
        id
        feeType
        totalFeeCharged
        govFee
        vaultFee
        referrerAllocation
        triggerFee
        collateralDenom
        feeMultiplier
        blockTime
        tradeId
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_USER_FEES,
  variables: { trader: 'nibi1abc...', limit: 50 }
})

// Calculate total fees paid
const totalFees = data.fee.feeTransactions.reduce((sum, fee) => {
  return sum + fee.totalFeeCharged
}, 0)

console.log(`Total fees paid: ${totalFees}`)
console.log(`Number of transactions: ${data.fee.feeTransactions.length}`)
```

### Example 11: Get Protocol Fee Summary

```javascript
const GET_PROTOCOL_SUMMARY = gql`
  query GetProtocolSummary($fromDate: Time, $toDate: Time) {
    fee {
      protocolFeeSummary(fromDate: $fromDate, toDate: $toDate) {
        period {
          fromDate
          toDate
        }
        totalFees
        totalOpeningFees
        totalClosingFees
        totalGovFees
        totalVaultFees
        totalReferrerFees
        totalTriggerFees
        totalBadDebt
        openingCount
        closingCount
        uniqueTraders
        avgFeeMultiplier
      }
    }
  }
`

// Usage - Get last 30 days
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)

const { data } = await client.query({
  query: GET_PROTOCOL_SUMMARY,
  variables: {
    fromDate: thirtyDaysAgo.toISOString(),
    toDate: new Date().toISOString()
  }
})

const summary = data.fee.protocolFeeSummary

console.log('Protocol Summary (Last 30 Days)')
console.log(`Total Fees: ${summary.totalFees}`)
console.log(`Gov Fees: ${summary.totalGovFees}`)
console.log(`Vault Fees: ${summary.totalVaultFees}`)
console.log(`Unique Traders: ${summary.uniqueTraders}`)
console.log(`Avg Fee Multiplier: ${summary.avgFeeMultiplier.toFixed(2)}x`)
```

### Example 12: Get Daily Fee Statistics

```javascript
const GET_DAILY_STATS = gql`
  query GetDailyStats($limit: Int!) {
    fee {
      feeDailyStats(
        filter: { protocolWide: true }
        limit: $limit
      ) {
        date
        collateralDenom
        openingFeeTotal
        closingFeeTotal
        openingFeeCount
        closingFeeCount
        totalBadDebt
      }
    }
  }
`

// Usage
const { data } = await client.query({
  query: GET_DAILY_STATS,
  variables: { limit: 30 }
})

// Create time series for charting
const chartData = data.fee.feeDailyStats.map(stat => ({
  date: new Date(stat.date).toLocaleDateString(),
  totalFees: stat.openingFeeTotal + stat.closingFeeTotal,
  transactions: stat.openingFeeCount + stat.closingFeeCount
}))

console.log('Daily Fee Data:', chartData)
```

***

## Advanced Patterns

### Example 13: Combine Multiple Queries

```javascript
const GET_DASHBOARD_DATA = gql`
  query GetDashboardData($trader: String!) {
    perp {
      trades(where: { trader: $trader, isOpen: true }) {
        id
        isLong
        leverage
        state {
          pnlCollateral
          pnlPct
        }
        perpBorrowing {
          baseToken { symbol }
        }
      }
    }
    lp {
      deposits(where: { depositor: $trader }) {
        shares
        vault {
          sharePrice
          collateralToken {
            symbol
            decimals
          }
        }
      }
    }
    fee {
      traderFeeSummary(traderAddress: $trader) {
        totalFees
        openingCount
        closingCount
      }
    }
  }
`

// Usage - Get all user data in one query
const { data } = await client.query({
  query: GET_DASHBOARD_DATA,
  variables: { trader: 'nibi1abc...' }
})

console.log('Open Positions:', data.perp.trades.length)
console.log('LP Deposits:', data.lp.deposits.length)
console.log('Total Trades:', data.fee.traderFeeSummary.openingCount + data.fee.traderFeeSummary.closingCount)
```

### Example 14: Pagination Pattern

```javascript
// Fetch all trades with pagination
async function fetchAllTrades(trader) {
  const QUERY = gql`
    query GetTrades($trader: String!, $limit: Int!, $offset: Int!) {
      perp {
        trades(
          where: { trader: $trader }
          limit: $limit
          offset: $offset
        ) {
          id
          isOpen
          isLong
        }
      }
    }
  `
  
  let allTrades = []
  let offset = 0
  const limit = 100
  let hasMore = true
  
  while (hasMore) {
    const { data } = await client.query({
      query: QUERY,
      variables: { trader, limit, offset }
    })
    
    const trades = data.perp.trades
    allTrades = allTrades.concat(trades)
    
    hasMore = trades.length === limit
    offset += limit
    
    console.log(`Fetched ${allTrades.length} trades...`)
  }
  
  return allTrades
}

// Usage
const allTrades = await fetchAllTrades('nibi1abc...')
console.log(`Total trades: ${allTrades.length}`)
```

### Example 15: Error Handling Pattern

```javascript
async function queryWithRetry(query, variables, maxRetries = 3) {
  let lastError
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const { data, errors } = await client.query({
        query,
        variables
      })
      
      if (errors && errors.length > 0) {
        throw new Error(errors[0].message)
      }
      
      return data
    } catch (error) {
      lastError = error
      console.warn(`Query attempt ${i + 1} failed:`, error.message)
      
      if (i < maxRetries - 1) {
        // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)))
      }
    }
  }
  
  throw new Error(`Query failed after ${maxRetries} attempts: ${lastError.message}`)
}

// Usage
try {
  const data = await queryWithRetry(GET_OPEN_POSITIONS, { trader: 'nibi1abc...' })
  console.log('Success:', data)
} catch (error) {
  console.error('Failed:', error.message)
}
```


# Examples: Subscriptions

Real-time data updates using GraphQL subscriptions over WebSockets.

## Table of Contents

* [Setup](#setup)
* [Perp Subscriptions](#perp-subscriptions)
* [LP Subscriptions](#lp-subscriptions)
* [Oracle Subscriptions](#oracle-subscriptions)
* [Advanced Patterns](#advanced-patterns)
* [Best Practices](#best-practices)

## Setup

### Apollo Client with Subscriptions

```javascript
import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { getMainDefinition } from '@apollo/client/utilities'
import { createClient } from 'graphql-ws'

const httpLink = new HttpLink({
  uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql'
})

const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://sai-keeper.testnet-2.nibiru.fi/graphql',
    connectionParams: {
      // Add auth headers if needed
    },
    retryAttempts: 5,
    shouldRetry: () => true
  })
)

// Split link based on operation type
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query)
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    )
  },
  wsLink,
  httpLink
)

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache()
})
```

### urql with Subscriptions

```javascript
import { createClient, subscriptionExchange, fetchExchange } from 'urql'
import { createClient as createWSClient } from 'graphql-ws'

const wsClient = createWSClient({
  url: 'wss://sai-keeper.testnet-2.nibiru.fi/graphql'
})

const client = createClient({
  url: 'https://sai-keeper.testnet-2.nibiru.fi/graphql',
  exchanges: [
    fetchExchange,
    subscriptionExchange({
      forwardSubscription: (operation) => ({
        subscribe: (sink) => ({
          unsubscribe: wsClient.subscribe(operation, sink)
        })
      })
    })
  ]
})
```

***

## Perp Subscriptions

### Example 1: Watch User Trades

Subscribe to all trade updates for a specific user.

```javascript
import { gql } from '@apollo/client'

const WATCH_TRADES = gql`
  subscription WatchTrades($trader: String!) {
    perpTrades(where: { trader: $trader }) {
      id
      isOpen
      isLong
      leverage
      collateralAmount
      openPrice
      closePrice
      sl
      tp
      perpBorrowing {
        baseToken {
          symbol
          logoUrl
        }
        marketId
      }
      state {
        pnlCollateral
        pnlPct
        liquidationPrice
        positionValue
        borrowingFeeCollateral
      }
      openBlock {
        block_ts
      }
    }
  }
`

// Apollo Client usage
const subscription = client.subscribe({
  query: WATCH_TRADES,
  variables: { trader: 'nibi1abc...' }
}).subscribe({
  next: ({ data }) => {
    console.log('Trades updated:', data.perpTrades)
    
    data.perpTrades.forEach(trade => {
      const pnlPct = (trade.state.pnlPct * 100).toFixed(2)
      const status = trade.isOpen ? 'OPEN' : 'CLOSED'
      
      console.log(`[${status}] ${trade.perpBorrowing.baseToken.symbol} ${trade.isLong ? 'LONG' : 'SHORT'} ${trade.leverage}x`)
      console.log(`  PnL: ${pnlPct}%`)
      console.log(`  Liquidation: $${trade.state.liquidationPrice.toFixed(2)}`)
    })
  },
  error: (error) => {
    console.error('Subscription error:', error)
  }
})

// Cleanup
// subscription.unsubscribe()
```

### Example 2: Watch Trade Events

Subscribe to trade history events (opens, closes, liquidations, etc).

```javascript
const WATCH_TRADE_EVENTS = gql`
  subscription WatchTradeEvents($trader: String!) {
    perpTradeHistory(where: { trader: $trader }) {
      id
      tradeChangeType
      realizedPnlCollateral
      realizedPnlPct
      block {
        block
        block_ts
      }
      trade {
        id
        perpBorrowing {
          baseToken {
            symbol
          }
          collateralToken {
            symbol
            decimals
          }
        }
        isLong
        leverage
        openPrice
        closePrice
      }
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_TRADE_EVENTS,
  variables: { trader: 'nibi1abc...' }
}).subscribe({
  next: ({ data }) => {
    const events = data.perpTradeHistory
    
    events.forEach(event => {
      const timestamp = new Date(event.block.block_ts).toLocaleTimeString()
      const token = event.trade?.perpBorrowing.baseToken.symbol
      
      console.log(`[${timestamp}] ${event.tradeChangeType}`)
      
      if (event.realizedPnlPct !== null) {
        const pnlPct = (event.realizedPnlPct * 100).toFixed(2)
        const decimals = event.trade.perpBorrowing.collateralToken.decimals
        const pnl = event.realizedPnlCollateral / (10 ** decimals)
        
        console.log(`  ${token} - Realized PnL: ${pnl.toFixed(2)} (${pnlPct}%)`)
      }
    })
    
    // Show notification for important events
    events.forEach(event => {
      if (event.tradeChangeType === 'position_liquidated') {
        showNotification('⚠️ Position Liquidated!', {
          body: `Your ${event.trade?.perpBorrowing.baseToken.symbol} position was liquidated`
        })
      }
      
      if (event.tradeChangeType === 'position_closed_tp') {
        showNotification('✅ Take Profit Hit!', {
          body: `TP triggered on ${event.trade?.perpBorrowing.baseToken.symbol}`
        })
      }
    })
  }
})
```

### Example 3: Watch Market Borrowing Rates

Subscribe to real-time borrowing rate updates for a specific market.

```javascript
const WATCH_MARKET = gql`
  subscription WatchMarket($collateralId: Int!, $marketId: Int!) {
    perpBorrowing(collateralId: $collateralId, marketId: $marketId) {
      marketId
      baseToken {
        symbol
      }
      price
      oiLong
      oiShort
      oiMax
      feesPerHourLong
      feesPerHourShort
      openFeePct
      closeFeePct
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_MARKET,
  variables: { collateralId: 1, marketId: 1 }
}).subscribe({
  next: ({ data }) => {
    const market = data.perpBorrowing
    
    // Calculate funding rate APR
    const fundingAprLong = market.feesPerHourLong * 24 * 365 * 100
    const fundingAprShort = market.feesPerHourShort * 24 * 365 * 100
    
    // Calculate OI utilization
    const oiTotal = market.oiLong + market.oiShort
    const utilization = (oiTotal / market.oiMax) * 100
    
    console.log(`${market.baseToken.symbol} Market Update`)
    console.log(`Price: $${market.price.toFixed(2)}`)
    console.log(`OI: ${market.oiLong} L / ${market.oiShort} S`)
    console.log(`Utilization: ${utilization.toFixed(1)}%`)
    console.log(`Funding APR - Long: ${fundingAprLong.toFixed(2)}% | Short: ${fundingAprShort.toFixed(2)}%`)
    
    // Alert on high utilization
    if (utilization > 90) {
      console.warn('⚠️  High OI utilization!')
    }
  }
})
```

### Example 4: Watch All Markets

Subscribe to updates for all available markets.

```javascript
const WATCH_ALL_MARKETS = gql`
  subscription WatchAllMarkets {
    perpBorrowings {
      marketId
      baseToken {
        symbol
        name
      }
      collateralToken {
        symbol
      }
      visible
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_ALL_MARKETS
}).subscribe({
  next: ({ data }) => {
    const markets = data.perpBorrowings.filter(m => m.visible)
    console.log(`Active markets: ${markets.length}`)
    
    // Update market selector UI
    updateMarketList(markets)
  }
})
```

***

## LP Subscriptions

### Example 5: Watch Vault Metrics

Subscribe to real-time vault updates (TVL, APY, share price).

```javascript
const WATCH_VAULTS = gql`
  subscription WatchVaults {
    lpVaults {
      address
      collateralToken {
        symbol
        name
        decimals
      }
      tvl
      sharePrice
      apy
      availableAssets
      currentEpoch
      revenueInfo {
        NetProfit
        TraderLosses
        Liabilities
      }
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_VAULTS
}).subscribe({
  next: ({ data }) => {
    data.lpVaults.forEach(vault => {
      const decimals = vault.collateralToken.decimals
      const tvl = vault.tvl / (10 ** decimals)
      const available = vault.availableAssets / (10 ** decimals)
      const utilization = ((tvl - available) / tvl) * 100
      
      console.log(`${vault.collateralToken.symbol} Vault`)
      console.log(`  TVL: ${tvl.toLocaleString()}`)
      console.log(`  APY: ${vault.apy?.toFixed(2) || 'N/A'}%`)
      console.log(`  Share Price: ${vault.sharePrice.toFixed(6)}`)
      console.log(`  Utilization: ${utilization.toFixed(1)}%`)
      console.log(`  Net Profit: ${vault.revenueInfo.NetProfit}`)
    })
    
    // Update dashboard
    updateVaultDashboard(data.lpVaults)
  }
})
```

### Example 6: Watch User LP Positions

Subscribe to user's LP deposit updates.

```javascript
const WATCH_USER_LP = gql`
  subscription WatchUserLP($user: String!) {
    lpDeposits(where: { depositor: $user }) {
      depositor
      shares
      vault {
        address
        collateralToken {
          symbol
          decimals
        }
        sharePrice
        apy
      }
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_USER_LP,
  variables: { user: 'nibi1abc...' }
}).subscribe({
  next: ({ data }) => {
    let totalValue = 0
    
    data.lpDeposits.forEach(deposit => {
      const decimals = deposit.vault.collateralToken.decimals
      const value = (deposit.shares * deposit.vault.sharePrice) / (10 ** decimals)
      totalValue += value
      
      console.log(`${deposit.vault.collateralToken.symbol}: ${value.toFixed(2)}`)
    })
    
    console.log(`Total LP Value: ${totalValue.toFixed(2)}`)
    
    // Update portfolio UI
    updatePortfolioValue(totalValue)
  }
})
```

### Example 7: Watch Deposit Events

Subscribe to deposit/withdrawal events.

```javascript
const WATCH_DEPOSIT_EVENTS = gql`
  subscription WatchDepositEvents($user: String!, $vault: String!) {
    lpDepositHistory(where: { depositor: $user, vault: $vault }) {
      id
      amount
      shares
      isWithdraw
      block {
        block_ts
      }
      vault {
        collateralToken {
          symbol
          decimals
        }
      }
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_DEPOSIT_EVENTS,
  variables: { 
    user: 'nibi1abc...',
    vault: 'nibi1vault...'
  }
}).subscribe({
  next: ({ data }) => {
    data.lpDepositHistory.forEach(event => {
      const decimals = event.vault.collateralToken.decimals
      const amount = event.amount / (10 ** decimals)
      const action = event.isWithdraw ? 'Withdrew' : 'Deposited'
      const timestamp = new Date(event.block.block_ts).toLocaleString()
      
      console.log(`[${timestamp}] ${action} ${amount.toFixed(2)} ${event.vault.collateralToken.symbol}`)
      
      // Show notification
      showNotification(`LP ${action}`, {
        body: `${amount.toFixed(2)} ${event.vault.collateralToken.symbol}`,
        timestamp: event.block.block_ts
      })
    })
  }
})
```

### Example 8: Watch Withdrawal Requests

Subscribe to withdrawal request updates.

```javascript
const WATCH_WITHDRAWALS = gql`
  subscription WatchWithdrawals($user: String!, $vault: String!) {
    lpWithdrawRequests(where: { depositor: $user, vault: $vault }) {
      shares
      status
      unlockEpoch
      autoRedeem
      vault {
        currentEpoch
        sharePrice
        collateralToken {
          symbol
          decimals
        }
      }
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_WITHDRAWALS,
  variables: {
    user: 'nibi1abc...',
    vault: 'nibi1vault...'
  }
}).subscribe({
  next: ({ data }) => {
    data.lpWithdrawRequests.forEach(request => {
      const isReady = request.vault.currentEpoch >= request.unlockEpoch
      const epochsRemaining = Math.max(0, request.unlockEpoch - request.vault.currentEpoch)
      
      const decimals = request.vault.collateralToken.decimals
      const estimatedValue = (request.shares * request.vault.sharePrice) / (10 ** decimals)
      
      console.log(`Withdrawal Request`)
      console.log(`  Status: ${isReady ? 'Ready ✅' : `${epochsRemaining} epochs remaining`}`)
      console.log(`  Estimated Value: ${estimatedValue.toFixed(2)} ${request.vault.collateralToken.symbol}`)
      console.log(`  Auto-redeem: ${request.autoRedeem}`)
      
      // Notify when ready
      if (isReady && !notifiedAlready) {
        showNotification('✅ Withdrawal Ready!', {
          body: `Your withdrawal of ${estimatedValue.toFixed(2)} ${request.vault.collateralToken.symbol} is ready`
        })
        notifiedAlready = true
      }
    })
  }
})
```

***

## Oracle Subscriptions

### Example 9: Watch Token Prices

Subscribe to real-time price updates.

```javascript
const WATCH_PRICES = gql`
  subscription WatchPrices {
    tokenPricesUsd {
      token {
        id
        symbol
        name
      }
      priceUsd
      lastUpdatedBlock {
        block
        block_ts
      }
    }
  }
`

// Usage
const priceCache = new Map()

const subscription = client.subscribe({
  query: WATCH_PRICES
}).subscribe({
  next: ({ data }) => {
    data.tokenPricesUsd.forEach(item => {
      const oldPrice = priceCache.get(item.token.symbol)
      const newPrice = item.priceUsd
      
      // Calculate price change
      if (oldPrice) {
        const change = ((newPrice - oldPrice) / oldPrice) * 100
        const arrow = change > 0 ? '↑' : '↓'
        console.log(`${item.token.symbol}: $${newPrice.toFixed(2)} ${arrow} ${Math.abs(change).toFixed(2)}%`)
      } else {
        console.log(`${item.token.symbol}: $${newPrice.toFixed(2)}`)
      }
      
      priceCache.set(item.token.symbol, newPrice)
    })
    
    // Update price ticker UI
    updatePriceTicker(data.tokenPricesUsd)
  }
})
```

### Example 10: Watch Specific Token Price

```javascript
const WATCH_TOKEN_PRICE = gql`
  subscription WatchTokenPrice($tokenId: Int!) {
    tokenPricesUsd(where: { tokenId: $tokenId }) {
      token {
        symbol
      }
      priceUsd
      lastUpdatedBlock {
        block_ts
      }
    }
  }
`

// Usage with price alerts
const subscription = client.subscribe({
  query: WATCH_TOKEN_PRICE,
  variables: { tokenId: 1 } // BTC
}).subscribe({
  next: ({ data }) => {
    const priceData = data.tokenPricesUsd[0]
    const price = priceData.priceUsd
    
    console.log(`BTC: $${price.toFixed(2)}`)
    
    // Price alerts
    if (price > 50000) {
      showNotification('🚀 BTC Above $50k!', {
        body: `Current price: $${price.toFixed(2)}`
      })
    }
    
    if (price < 45000) {
      showNotification('📉 BTC Below $45k', {
        body: `Current price: $${price.toFixed(2)}`
      })
    }
  }
})
```

### Example 11: Watch User Balances

```javascript
const WATCH_BALANCES = gql`
  subscription WatchBalances($user: String!) {
    userBalances(where: { user: $user }) {
      amount
      token_info {
        symbol
        name
        decimals
        type
        logo
      }
    }
  }
`

// Usage
const subscription = client.subscribe({
  query: WATCH_BALANCES,
  variables: { user: 'nibi1abc...' }
}).subscribe({
  next: ({ data }) => {
    console.log('Balance Update:')
    
    data.userBalances.forEach(balance => {
      const amount = parseFloat(balance.amount) / (10 ** balance.token_info.decimals)
      console.log(`  ${balance.token_info.symbol}: ${amount.toFixed(4)}`)
    })
    
    // Update wallet UI
    updateWalletBalances(data.userBalances)
  }
})
```

***

## Advanced Patterns

### Example 12: Multiple Subscriptions Manager

```javascript
class SubscriptionManager {
  constructor(client) {
    this.client = client
    this.subscriptions = new Map()
  }
  
  subscribe(name, query, variables, callback) {
    // Unsubscribe existing if any
    this.unsubscribe(name)
    
    const subscription = this.client.subscribe({
      query,
      variables
    }).subscribe({
      next: callback,
      error: (error) => {
        console.error(`Subscription ${name} error:`, error)
        // Attempt reconnection
        setTimeout(() => {
          this.subscribe(name, query, variables, callback)
        }, 5000)
      }
    })
    
    this.subscriptions.set(name, subscription)
  }
  
  unsubscribe(name) {
    const sub = this.subscriptions.get(name)
    if (sub) {
      sub.unsubscribe()
      this.subscriptions.delete(name)
    }
  }
  
  unsubscribeAll() {
    this.subscriptions.forEach(sub => sub.unsubscribe())
    this.subscriptions.clear()
  }
}

// Usage
const manager = new SubscriptionManager(client)

manager.subscribe('trades', WATCH_TRADES, { trader: 'nibi1abc...' }, (data) => {
  updateTradesUI(data.perpTrades)
})

manager.subscribe('prices', WATCH_PRICES, {}, (data) => {
  updatePricesUI(data.tokenPricesUsd)
})

// Cleanup on component unmount
// manager.unsubscribeAll()
```

### Example 13: Subscription with Reconnection Logic

```javascript
function createResilientSubscription(query, variables, onData) {
  let subscription = null
  let reconnectAttempts = 0
  const maxReconnectAttempts = 10
  
  function connect() {
    subscription = client.subscribe({
      query,
      variables
    }).subscribe({
      next: (data) => {
        reconnectAttempts = 0 // Reset on successful data
        onData(data)
      },
      error: (error) => {
        console.error('Subscription error:', error)
        
        if (reconnectAttempts < maxReconnectAttempts) {
          reconnectAttempts++
          const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000)
          console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...`)
          
          setTimeout(connect, delay)
        } else {
          console.error('Max reconnection attempts reached')
        }
      },
      complete: () => {
        console.log('Subscription completed')
      }
    })
  }
  
  connect()
  
  return {
    unsubscribe: () => {
      if (subscription) {
        subscription.unsubscribe()
      }
    }
  }
}

// Usage
const sub = createResilientSubscription(
  WATCH_TRADES,
  { trader: 'nibi1abc...' },
  (data) => {
    console.log('Received trade update:', data.perpTrades)
  }
)

// Cleanup
// sub.unsubscribe()
```

***

## Best Practices

### 1. Always Clean Up Subscriptions

```javascript
useEffect(() => {
  const subscription = client.subscribe({
    query: WATCH_TRADES,
    variables: { trader }
  }).subscribe({
    next: (data) => setTrades(data.perpTrades)
  })
  
  // Cleanup function
  return () => {
    subscription.unsubscribe()
  }
}, [trader])
```

### 2. Handle Connection States

```javascript
const [connectionState, setConnectionState] = useState('connecting')

const subscription = client.subscribe({
  query: WATCH_PRICES
}).subscribe({
  next: (data) => {
    setConnectionState('connected')
    updatePrices(data)
  },
  error: (error) => {
    setConnectionState('error')
    console.error(error)
  }
})

// Show connection status in UI
if (connectionState === 'connecting') {
  return <div>Connecting to real-time feed...</div>
}
```

### 3. Debounce Rapid Updates

```javascript
import { debounce } from 'lodash'

const debouncedUpdate = debounce((data) => {
  updateUI(data)
}, 100)

const subscription = client.subscribe({
  query: WATCH_PRICES
}).subscribe({
  next: (data) => {
    debouncedUpdate(data.tokenPricesUsd)
  }
})
```

### 4. Combine with Queries for Initial Data

```javascript
// Fetch initial data with query
const { data: initialData } = await client.query({
  query: GET_TRADES,
  variables: { trader }
})

setTrades(initialData.perp.trades)

// Then subscribe to updates
const subscription = client.subscribe({
  query: WATCH_TRADES,
  variables: { trader }
}).subscribe({
  next: (data) => {
    setTrades(data.perpTrades)
  }
})
```


# Client Setup

Set up GraphQL clients in various languages to interact with Sai Keeper API.

## Table of Contents

* [JavaScript/TypeScript](#javascripttypescript)
  * [Apollo Client](#apollo-client)
  * [urql](#urql)
  * [GraphQL Request](#graphql-request)
* [Python](#python)
* [Rust](#rust)
* [Go](#go)

***

## JavaScript/TypeScript

### Apollo Client

Apollo Client is the most popular GraphQL client for JavaScript applications.

#### Installation

```bash
npm install @apollo/client graphql
# For subscriptions
npm install graphql-ws
```

#### Basic Setup (Queries Only)

```typescript
import { ApolloClient, InMemoryCache, HttpLink, gql } from '@apollo/client'

const client = new ApolloClient({
  link: new HttpLink({
    uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql'
  }),
  cache: new InMemoryCache()
})

// Example query
const GET_PRICES = gql`
  query GetPrices {
    oracle {
      tokenPricesUsd(limit: 10) {
        token { symbol }
        priceUsd
      }
    }
  }
`

async function fetchPrices() {
  const { data, error } = await client.query({
    query: GET_PRICES
  })
  
  if (error) {
    console.error('Error:', error)
    return
  }
  
  console.log('Prices:', data.oracle.tokenPricesUsd)
}

fetchPrices()
```

#### Full Setup (With Subscriptions)

```typescript
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
  split,
  gql
} from '@apollo/client'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { getMainDefinition } from '@apollo/client/utilities'
import { createClient } from 'graphql-ws'

// HTTP link for queries and mutations
const httpLink = new HttpLink({
  uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql'
})

// WebSocket link for subscriptions
const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://sai-keeper.testnet-2.nibiru.fi/graphql',
    connectionParams: {
      // Add authentication if needed
      // authToken: 'your-auth-token'
    },
    retryAttempts: 5,
    shouldRetry: () => true,
    on: {
      connected: () => console.log('WebSocket connected'),
      closed: () => console.log('WebSocket closed'),
      error: (error) => console.error('WebSocket error:', error)
    }
  })
)

// Split traffic between HTTP and WebSocket
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query)
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    )
  },
  wsLink,
  httpLink
)

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network'
    }
  }
})

// Example subscription
const WATCH_PRICES = gql`
  subscription WatchPrices {
    tokenPricesUsd {
      token { symbol }
      priceUsd
    }
  }
`

const subscription = client.subscribe({
  query: WATCH_PRICES
}).subscribe({
  next: ({ data }) => {
    console.log('Price update:', data.tokenPricesUsd)
  },
  error: (error) => {
    console.error('Subscription error:', error)
  }
})

// Clean up
// subscription.unsubscribe()

export default client
```

#### React Integration

```typescript
import { ApolloProvider, useQuery, useSubscription, gql } from '@apollo/client'
import client from './apollo-client'

// Wrap your app
function App() {
  return (
    <ApolloProvider client={client}>
      <Dashboard />
    </ApolloProvider>
  )
}

// Use queries in components
function Dashboard() {
  const GET_TRADES = gql`
    query GetTrades($trader: String!) {
      perp {
        trades(where: { trader: $trader, isOpen: true }) {
          id
          isLong
          leverage
          state { pnlPct }
        }
      }
    }
  `
  
  const { loading, error, data } = useQuery(GET_TRADES, {
    variables: { trader: 'nibi1abc...' }
  })
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  
  return (
    <div>
      {data.perp.trades.map(trade => (
        <div key={trade.id}>
          Trade #{trade.id} - PnL: {(trade.state.pnlPct * 100).toFixed(2)}%
        </div>
      ))}
    </div>
  )
}

// Use subscriptions in components
function PriceTicker() {
  const WATCH_PRICES = gql`
    subscription {
      tokenPricesUsd {
        token { symbol }
        priceUsd
      }
    }
  `
  
  const { data, loading } = useSubscription(WATCH_PRICES)
  
  if (loading) return <div>Connecting...</div>
  
  return (
    <div>
      {data.tokenPricesUsd.map(item => (
        <div key={item.token.symbol}>
          {item.token.symbol}: ${item.priceUsd.toFixed(2)}
        </div>
      ))}
    </div>
  )
}
```

***

### urql

urql is a lightweight alternative to Apollo Client.

#### Installation

```bash
npm install urql graphql
# For subscriptions
npm install graphql-ws
```

#### Setup

```typescript
import { createClient, fetchExchange, subscriptionExchange } from 'urql'
import { createClient as createWSClient } from 'graphql-ws'

const wsClient = createWSClient({
  url: 'wss://sai-keeper.testnet-2.nibiru.fi/graphql'
})

const client = createClient({
  url: 'https://sai-keeper.testnet-2.nibiru.fi/graphql',
  exchanges: [
    fetchExchange,
    subscriptionExchange({
      forwardSubscription: (operation) => ({
        subscribe: (sink) => ({
          unsubscribe: wsClient.subscribe(operation, sink)
        })
      })
    })
  ]
})

export default client
```

#### React Integration

```typescript
import { Provider, useQuery, useSubscription } from 'urql'
import client from './urql-client'

function App() {
  return (
    <Provider value={client}>
      <Dashboard />
    </Provider>
  )
}

function Dashboard() {
  const QUERY = `
    query GetTrades($trader: String!) {
      perp {
        trades(where: { trader: $trader, isOpen: true }) {
          id
          isLong
          leverage
        }
      }
    }
  `
  
  const [result] = useQuery({
    query: QUERY,
    variables: { trader: 'nibi1abc...' }
  })
  
  const { data, fetching, error } = result
  
  if (fetching) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  
  return (
    <div>
      {data.perp.trades.map(trade => (
        <div key={trade.id}>Trade #{trade.id}</div>
      ))}
    </div>
  )
}
```

***

### GraphQL Request

Lightweight library for simple queries (no caching or subscriptions).

#### Installation

```bash
npm install graphql-request graphql
```

#### Usage

```typescript
import { GraphQLClient, gql } from 'graphql-request'

const client = new GraphQLClient(
  'https://sai-keeper.testnet-2.nibiru.fi/graphql'
)

const GET_TRADES = gql`
  query GetTrades($trader: String!) {
    perp {
      trades(where: { trader: $trader, isOpen: true }) {
        id
        isLong
        leverage
      }
    }
  }
`

async function fetchTrades(trader: string) {
  try {
    const data = await client.request(GET_TRADES, { trader })
    console.log('Trades:', data.perp.trades)
    return data.perp.trades
  } catch (error) {
    console.error('Error fetching trades:', error)
    throw error
  }
}

fetchTrades('nibi1abc...')
```

***

## Python

### gql Library

Full-featured GraphQL client for Python.

#### Installation

```bash
pip install gql[all]
```

#### Setup

```python
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.websockets import WebsocketsTransport
import asyncio

# HTTP Transport for queries
http_transport = AIOHTTPTransport(
    url="https://sai-keeper.testnet-2.nibiru.fi/graphql"
)

# Create client
client = Client(
    transport=http_transport,
    fetch_schema_from_transport=True
)

# Example query
async def get_trades(trader_address):
    query = gql("""
        query GetTrades($trader: String!) {
            perp {
                trades(where: { trader: $trader, isOpen: true }) {
                    id
                    isLong
                    leverage
                    state {
                        pnlPct
                        liquidationPrice
                    }
                    perpBorrowing {
                        baseToken {
                            symbol
                        }
                    }
                }
            }
        }
    """)
    
    params = {"trader": trader_address}
    
    async with Client(transport=http_transport) as session:
        result = await session.execute(query, variable_values=params)
        return result['perp']['trades']

# Run the query
trades = asyncio.run(get_trades("nibi1abc..."))
for trade in trades:
    pnl_pct = trade['state']['pnlPct'] * 100
    print(f"Trade #{trade['id']}: {trade['perpBorrowing']['baseToken']['symbol']} {trade['leverage']}x - PnL: {pnl_pct:.2f}%")
```

#### With Subscriptions

```python
from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport
import asyncio

# WebSocket transport for subscriptions
ws_transport = WebsocketsTransport(
    url="wss://sai-keeper.testnet-2.nibiru.fi/graphql"
)

async def watch_prices():
    subscription = gql("""
        subscription WatchPrices {
            tokenPricesUsd {
                token {
                    symbol
                }
                priceUsd
            }
        }
    """)
    
    async with Client(transport=ws_transport) as session:
        async for result in session.subscribe(subscription):
            prices = result['tokenPricesUsd']
            for item in prices:
                print(f"{item['token']['symbol']}: ${item['priceUsd']:.2f}")

# Run subscription
asyncio.run(watch_prices())
```

#### Synchronous Version

```python
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport

# Synchronous transport
transport = RequestsHTTPTransport(
    url="https://sai-keeper.testnet-2.nibiru.fi/graphql",
    verify=True,
    retries=3
)

client = Client(transport=transport, fetch_schema_from_transport=True)

# Synchronous query
def get_token_prices():
    query = gql("""
        query GetPrices {
            oracle {
                tokenPricesUsd(limit: 10) {
                    token {
                        symbol
                    }
                    priceUsd
                }
            }
        }
    """)
    
    result = client.execute(query)
    return result['oracle']['tokenPricesUsd']

prices = get_token_prices()
for item in prices:
    print(f"{item['token']['symbol']}: ${item['priceUsd']:.2f}")
```

***

## Rust

### graphql\_client

Type-safe GraphQL client for Rust.

#### Installation

Add to `Cargo.toml`:

```toml
[dependencies]
graphql_client = "0.13"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
```

#### Setup

```rust
use graphql_client::{GraphQLQuery, Response};
use reqwest;

// Define the query
#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "schema.graphql",
    query_path = "queries/get_trades.graphql",
    response_derives = "Debug"
)]
pub struct GetTrades;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    
    let variables = get_trades::Variables {
        trader: "nibi1abc...".to_string(),
    };
    
    let request_body = GetTrades::build_query(variables);
    
    let res = client
        .post("https://sai-keeper.testnet-2.nibiru.fi/graphql")
        .json(&request_body)
        .send()
        .await?;
    
    let response_body: Response<get_trades::ResponseData> = res.json().await?;
    
    if let Some(data) = response_body.data {
        for trade in data.perp.trades {
            println!("Trade #{}: {}x leverage", trade.id, trade.leverage);
        }
    }
    
    if let Some(errors) = response_body.errors {
        for error in errors {
            eprintln!("Error: {}", error.message);
        }
    }
    
    Ok(())
}
```

#### Without Code Generation

```rust
use reqwest;
use serde::{Deserialize, Serialize};
use serde_json::json;

#[derive(Debug, Serialize, Deserialize)]
struct GraphQLRequest {
    query: String,
    variables: serde_json::Value,
}

#[derive(Debug, Deserialize)]
struct GraphQLResponse<T> {
    data: Option<T>,
    errors: Option<Vec<GraphQLError>>,
}

#[derive(Debug, Deserialize)]
struct GraphQLError {
    message: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    
    let query = r#"
        query GetTrades($trader: String!) {
            perp {
                trades(where: { trader: $trader, isOpen: true }) {
                    id
                    isLong
                    leverage
                }
            }
        }
    "#;
    
    let variables = json!({
        "trader": "nibi1abc..."
    });
    
    let request = GraphQLRequest {
        query: query.to_string(),
        variables,
    };
    
    let response = client
        .post("https://sai-keeper.testnet-2.nibiru.fi/graphql")
        .json(&request)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;
    
    println!("Response: {:#?}", response);
    
    Ok(())
}
```

***

## Go

### graphql Package

#### Installation

```bash
go get github.com/machinebox/graphql
```

#### Setup

```go
package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/machinebox/graphql"
)

type Trade struct {
    ID       int     `json:"id"`
    IsLong   bool    `json:"isLong"`
    Leverage float64 `json:"leverage"`
}

type PerpResponse struct {
    Trades []Trade `json:"trades"`
}

type Response struct {
    Perp PerpResponse `json:"perp"`
}

func main() {
    // Create client
    client := graphql.NewClient("https://sai-keeper.testnet-2.nibiru.fi/graphql")
    
    // Define query
    req := graphql.NewRequest(`
        query GetTrades($trader: String!) {
            perp {
                trades(where: { trader: $trader, isOpen: true }) {
                    id
                    isLong
                    leverage
                }
            }
        }
    `)
    
    // Set variables
    req.Var("trader", "nibi1abc...")
    
    // Execute query
    ctx := context.Background()
    var response Response
    
    if err := client.Run(ctx, req, &response); err != nil {
        log.Fatal(err)
    }
    
    // Process response
    for _, trade := range response.Perp.Trades {
        fmt.Printf("Trade #%d: %fx leverage\n", trade.ID, trade.Leverage)
    }
}
```

#### With Error Handling

```go
package main

import (
    "context"
    "fmt"
    "log"
    "time"
    
    "github.com/machinebox/graphql"
)

func fetchTrades(trader string) error {
    client := graphql.NewClient("https://sai-keeper.testnet-2.nibiru.fi/graphql")
    
    req := graphql.NewRequest(`
        query GetTrades($trader: String!) {
            perp {
                trades(where: { trader: $trader, isOpen: true }) {
                    id
                    isLong
                    leverage
                }
            }
        }
    `)
    
    req.Var("trader", trader)
    
    // Add timeout
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    var response map[string]interface{}
    
    if err := client.Run(ctx, req, &response); err != nil {
        return fmt.Errorf("query failed: %w", err)
    }
    
    // Check for data
    perp, ok := response["perp"].(map[string]interface{})
    if !ok {
        return fmt.Errorf("invalid response structure")
    }
    
    trades, ok := perp["trades"].([]interface{})
    if !ok {
        return fmt.Errorf("invalid trades structure")
    }
    
    fmt.Printf("Found %d open trades\n", len(trades))
    
    return nil
}

func main() {
    if err := fetchTrades("nibi1abc..."); err != nil {
        log.Fatal(err)
    }
}
```

***

## Environment Variables

For all languages, consider using environment variables for configuration:

```bash
# .env file
SAI_KEEPER_HTTP_URL=https://sai-keeper.testnet-2.nibiru.fi/graphql
SAI_KEEPER_WS_URL=wss://sai-keeper.testnet-2.nibiru.fi/graphql
```

**JavaScript:**

```javascript
const httpUrl = process.env.SAI_KEEPER_HTTP_URL
const wsUrl = process.env.SAI_KEEPER_WS_URL
```

**Python:**

```python
import os
http_url = os.getenv('SAI_KEEPER_HTTP_URL')
ws_url = os.getenv('SAI_KEEPER_WS_URL')
```

**Rust:**

```rust
use std::env;
let http_url = env::var("SAI_KEEPER_HTTP_URL").unwrap();
```

**Go:**

```go
import "os"
httpUrl := os.Getenv("SAI_KEEPER_HTTP_URL")
```


# Filters & Pagination

Master advanced querying patterns including filtering, pagination, and ordering.

## Table of Contents

* [Filter Types](#filter-types)
* [Pagination](#pagination)
* [Ordering](#ordering)
* [Advanced Patterns](#advanced-patterns)
* [Best Practices](#best-practices)

***

## Filter Types

Sai Keeper supports various filter types for different data types.

### String Filters

**StringFilter** supports equality and pattern matching.

```graphql
input StringFilter {
  eq: String      # Exact match
  like: String    # Pattern match
}
```

**Example - Exact Match:**

```graphql
query SearchToken {
  oracle {
    tokens(where: { name: { eq: "Bitcoin" } }) {
      id
      symbol
      name
    }
  }
}
```

**Example - Pattern Match:**

```graphql
query SearchByPattern {
  oracle {
    tokens(where: { name: { like: "%coin%" } }) {
      symbol
      name
    }
  }
}
```

**Usage in JavaScript:**

```javascript
// Exact match
const { data } = await client.query({
  query: gql`
    query SearchToken($name: String!) {
      oracle {
        tokens(where: { name: $name }) {
          symbol
          name
        }
      }
    }
  `,
  variables: { name: "Bitcoin" }
})

// Pattern search
const searchResults = await client.query({
  query: gql`
    query SearchTokens($pattern: String!) {
      oracle {
        tokens(where: { name: $pattern }) {
          symbol
          name
        }
      }
    }
  `,
  variables: { pattern: "%BTC%" }
})
```

***

### Integer Filters

**IntFilter** supports comparison operations.

```graphql
input IntFilter {
  eq: Int   # Equal to
  gt: Int   # Greater than
  gte: Int  # Greater than or equal
  lt: Int   # Less than
  lte: Int  # Less than or equal
}
```

**Example - Fee Amount Range:**

```graphql
query GetLargeFees {
  fee {
    feeTransactions(
      filter: {
        minAmount: 1000000    # >= 1,000,000
        maxAmount: 10000000   # <= 10,000,000
      }
    ) {
      id
      totalFeeCharged
      traderAddress
    }
  }
}
```

**Usage in JavaScript:**

```javascript
const { data } = await client.query({
  query: gql`
    query GetFeesInRange($min: Int!, $max: Int!) {
      fee {
        feeTransactions(
          filter: {
            minAmount: $min
            maxAmount: $max
          }
        ) {
          id
          totalFeeCharged
        }
      }
    }
  `,
  variables: {
    min: 1000000,
    max: 10000000
  }
})
```

***

### Float Filters

**FloatFilter** for floating-point comparisons.

```graphql
input FloatFilter {
  eq: Float   # Equal to
  gt: Float   # Greater than
  gte: Float  # Greater than or equal
  lt: Float   # Less than
  lte: Float  # Less than or equal
}
```

**Example - Filter by Leverage:**

```javascript
// Note: Direct float filters not used in current schema,
// but filter objects use similar patterns
const highLeverageTrades = await client.query({
  query: gql`
    query GetHighLeverageTrades($trader: String!) {
      perp {
        trades(where: { trader: $trader }) {
          id
          leverage
          isOpen
        }
      }
    }
  `,
  variables: { trader: "nibi1abc..." }
})

// Filter client-side
const filtered = highLeverageTrades.data.perp.trades.filter(
  trade => trade.leverage > 10
)
```

***

### Time Filters

**TimeFilter** for date/time range queries.

```graphql
input TimeFilter {
  eq: Time   # Exact time
  gt: Time   # After
  gte: Time  # After or at
  lt: Time   # Before
  lte: Time  # Before or at
}
```

**Example - Filter by Date Range:**

```graphql
query GetFeesLastMonth {
  fee {
    feeTransactions(
      filter: {
        fromDate: "2024-10-01T00:00:00Z"
        toDate: "2024-10-31T23:59:59Z"
      }
      limit: 100
    ) {
      id
      totalFeeCharged
      blockTime
    }
  }
}
```

**Usage in JavaScript:**

```javascript
// Get fees for last 30 days
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)

const { data } = await client.query({
  query: gql`
    query GetRecentFees($fromDate: Time!, $toDate: Time!) {
      fee {
        feeTransactions(
          filter: {
            fromDate: $fromDate
            toDate: $toDate
          }
          limit: 100
        ) {
          id
          totalFeeCharged
          blockTime
        }
      }
    }
  `,
  variables: {
    fromDate: thirtyDaysAgo.toISOString(),
    toDate: new Date().toISOString()
  }
})
```

**Helper Function:**

```javascript
function getDateRange(days) {
  const toDate = new Date()
  const fromDate = new Date()
  fromDate.setDate(fromDate.getDate() - days)
  
  return {
    fromDate: fromDate.toISOString(),
    toDate: toDate.toISOString()
  }
}

// Usage
const last7Days = getDateRange(7)
const last30Days = getDateRange(30)
const last90Days = getDateRange(90)
```

***

### Enum Filters

Filter by enum values.

**Example - Fee Type Filter:**

```graphql
query GetOpeningFees($trader: String!) {
  fee {
    feeTransactions(
      filter: {
        traderAddress: $trader
        feeType: OPENING
      }
      limit: 50
    ) {
      id
      feeType
      totalFeeCharged
    }
  }
}
```

**Example - Trade Type Filter:**

```javascript
// Get only limit orders
const { data } = await client.query({
  query: gql`
    query GetLimitOrders($trader: String!) {
      perp {
        tradeHistory(where: { trader: $trader }, limit: 100) {
          id
          tradeChangeType
          trade {
            id
            tradeType
          }
        }
      }
    }
  `,
  variables: { trader: "nibi1abc..." }
})

// Filter for limit_order_created events
const limitOrders = data.perp.tradeHistory.filter(
  h => h.tradeChangeType === 'limit_order_created'
)
```

***

## Pagination

### Basic Pagination

Use `limit` and `offset` for pagination.

```graphql
query GetTradesPaginated($trader: String!, $limit: Int!, $offset: Int!) {
  perp {
    trades(
      where: { trader: $trader }
      limit: $limit
      offset: $offset
    ) {
      id
      isOpen
      leverage
    }
  }
}
```

**JavaScript Implementation:**

```javascript
async function fetchPage(trader, page = 0, pageSize = 20) {
  const { data } = await client.query({
    query: GET_TRADES_PAGINATED,
    variables: {
      trader,
      limit: pageSize,
      offset: page * pageSize
    }
  })
  
  return data.perp.trades
}

// Get first page
const firstPage = await fetchPage("nibi1abc...", 0, 20)

// Get second page
const secondPage = await fetchPage("nibi1abc...", 1, 20)

// Get third page
const thirdPage = await fetchPage("nibi1abc...", 2, 20)
```

***

### Fetch All Pattern

Fetch all results across multiple pages.

```javascript
async function fetchAllTrades(trader, pageSize = 100) {
  let allTrades = []
  let page = 0
  let hasMore = true
  
  while (hasMore) {
    const { data } = await client.query({
      query: gql`
        query GetTrades($trader: String!, $limit: Int!, $offset: Int!) {
          perp {
            trades(
              where: { trader: $trader }
              limit: $limit
              offset: $offset
            ) {
              id
              isOpen
              leverage
            }
          }
        }
      `,
      variables: {
        trader,
        limit: pageSize,
        offset: page * pageSize
      }
    })
    
    const trades = data.perp.trades
    allTrades = allTrades.concat(trades)
    
    hasMore = trades.length === pageSize
    page++
    
    console.log(`Fetched page ${page}, total: ${allTrades.length}`)
  }
  
  return allTrades
}

// Usage
const allTrades = await fetchAllTrades("nibi1abc...")
console.log(`Total trades: ${allTrades.length}`)
```

***

### Cursor-Based Pagination Alternative

While Sai Keeper uses offset pagination, you can implement cursor-based logic:

```javascript
async function fetchTradesAfterBlock(trader, afterBlock = 0, limit = 100) {
  const { data } = await client.query({
    query: gql`
      query GetTrades($trader: String!, $limit: Int!) {
        perp {
          trades(
            where: { trader: $trader }
            limit: $limit
            order_desc: false
          ) {
            id
            openBlock {
              block
            }
          }
        }
      }
    `,
    variables: { trader, limit }
  })
  
  // Filter trades after cursor block
  const filtered = data.perp.trades.filter(
    trade => trade.openBlock.block > afterBlock
  )
  
  return filtered
}

// Get first batch
const firstBatch = await fetchTradesAfterBlock("nibi1abc...", 0)
const lastBlock = firstBatch[firstBatch.length - 1].openBlock.block

// Get next batch
const nextBatch = await fetchTradesAfterBlock("nibi1abc...", lastBlock)
```

***

### Infinite Scroll Pattern

Implement infinite scroll for UI:

```javascript
class TradesPaginator {
  constructor(client, trader, pageSize = 20) {
    this.client = client
    this.trader = trader
    this.pageSize = pageSize
    this.currentPage = 0
    this.allTrades = []
    this.hasMore = true
  }
  
  async loadMore() {
    if (!this.hasMore) {
      return []
    }
    
    const { data } = await this.client.query({
      query: gql`
        query GetTrades($trader: String!, $limit: Int!, $offset: Int!) {
          perp {
            trades(
              where: { trader: $trader }
              limit: $limit
              offset: $offset
              order_desc: true
            ) {
              id
              isOpen
              leverage
            }
          }
        }
      `,
      variables: {
        trader: this.trader,
        limit: this.pageSize,
        offset: this.currentPage * this.pageSize
      }
    })
    
    const trades = data.perp.trades
    this.allTrades = this.allTrades.concat(trades)
    this.hasMore = trades.length === this.pageSize
    this.currentPage++
    
    return trades
  }
  
  reset() {
    this.currentPage = 0
    this.allTrades = []
    this.hasMore = true
  }
  
  getAll() {
    return this.allTrades
  }
}

// Usage
const paginator = new TradesPaginator(client, "nibi1abc...", 20)

// Load first page
const firstPage = await paginator.loadMore()

// Load more on scroll
window.addEventListener('scroll', async () => {
  if (isNearBottom() && paginator.hasMore) {
    const nextPage = await paginator.loadMore()
    appendToUI(nextPage)
  }
})
```

***

## Ordering

### Order Direction

Most queries support `order_by` and `order_desc` parameters.

```graphql
query GetTradesOrdered($trader: String!) {
  perp {
    trades(
      where: { trader: $trader }
      order_by: sequence
      order_desc: true  # Descending (newest first)
    ) {
      id
      openBlock { block_ts }
    }
  }
}
```

**Order Options by Domain:**

**Perp Trades:**

* `sequence` - Order by sequence number

**Trade History:**

* `sequence` - Order by sequence
* `trade_id` - Order by trade ID

**LP Deposits:**

* `depositor` - Order by depositor address
* `vault` - Order by vault address

**LP Deposit History:**

* `depositor` - Order by depositor
* `sequence` - Order by sequence
* `vault` - Order by vault

**LP Withdraw Requests:**

* `depositor` - Order by depositor
* `unlock_epoch` - Order by unlock epoch
* `vault` - Order by vault

**Oracle Tokens:**

* `id` - Order by token ID
* `name` - Order by token name
* `permission_group` - Order by permission group

**Fee Transactions:**

* No explicit ordering parameter, results ordered by ID

***

### Sorting Examples

**Newest First:**

```javascript
const { data } = await client.query({
  query: gql`
    query GetRecentTrades($trader: String!) {
      perp {
        trades(
          where: { trader: $trader }
          order_desc: true
          limit: 20
        ) {
          id
          openBlock { block_ts }
        }
      }
    }
  `,
  variables: { trader: "nibi1abc..." }
})
```

**Oldest First:**

```javascript
const { data } = await client.query({
  query: gql`
    query GetOldestTrades($trader: String!) {
      perp {
        trades(
          where: { trader: $trader }
          order_desc: false
          limit: 20
        ) {
          id
          openBlock { block_ts }
        }
      }
    }
  `,
  variables: { trader: "nibi1abc..." }
})
```

**By Token Name:**

```javascript
const { data } = await client.query({
  query: gql`
    query GetTokensAlphabetically {
      oracle {
        tokens(
          order_by: name
          order_desc: false
        ) {
          symbol
          name
        }
      }
    }
  `
})
```

***

## Advanced Patterns

### Combined Filters

Combine multiple filters for precise queries.

```javascript
const { data } = await client.query({
  query: gql`
    query GetFilteredFees(
      $trader: String!
      $feeType: FeeType!
      $fromDate: Time!
      $toDate: Time!
      $minAmount: Int!
    ) {
      fee {
        feeTransactions(
          filter: {
            traderAddress: $trader
            feeType: $feeType
            fromDate: $fromDate
            toDate: $toDate
            minAmount: $minAmount
          }
          limit: 100
        ) {
          id
          totalFeeCharged
          blockTime
        }
      }
    }
  `,
  variables: {
    trader: "nibi1abc...",
    feeType: "OPENING",
    fromDate: "2024-01-01T00:00:00Z",
    toDate: "2024-12-31T23:59:59Z",
    minAmount: 1000000
  }
})
```

***

### Client-Side Post-Filtering

When server-side filters aren't available, filter client-side:

```javascript
// Fetch all trades
const { data } = await client.query({
  query: gql`
    query GetAllTrades($trader: String!) {
      perp {
        trades(where: { trader: $trader }, limit: 1000) {
          id
          leverage
          isLong
          isOpen
          collateralAmount
          state {
            pnlPct
          }
          perpBorrowing {
            baseToken { symbol }
          }
        }
      }
    }
  `,
  variables: { trader: "nibi1abc..." }
})

// Filter for high-leverage profitable longs
const filtered = data.perp.trades.filter(trade => 
  trade.isLong &&
  trade.leverage > 10 &&
  trade.state.pnlPct > 0.1 && // 10% profit
  trade.isOpen
)

// Group by token
const byToken = filtered.reduce((acc, trade) => {
  const symbol = trade.perpBorrowing.baseToken.symbol
  if (!acc[symbol]) acc[symbol] = []
  acc[symbol].push(trade)
  return acc
}, {})

console.log('Profitable high-leverage longs by token:', byToken)
```

***

### Search Implementation

Build a search feature:

```javascript
async function searchTrades(trader, searchTerm) {
  // Fetch all trades
  const { data } = await client.query({
    query: gql`
      query GetTrades($trader: String!) {
        perp {
          trades(where: { trader: $trader }) {
            id
            isLong
            leverage
            perpBorrowing {
              baseToken {
                symbol
                name
              }
              marketId
            }
          }
        }
      }
    `,
    variables: { trader }
  })
  
  // Search by token symbol or name
  const searchLower = searchTerm.toLowerCase()
  return data.perp.trades.filter(trade => {
    const symbol = trade.perpBorrowing.baseToken.symbol.toLowerCase()
    const name = trade.perpBorrowing.baseToken.name.toLowerCase()
    return symbol.includes(searchLower) || name.includes(searchLower)
  })
}

// Usage
const btcTrades = await searchTrades("nibi1abc...", "bitcoin")
const ethTrades = await searchTrades("nibi1abc...", "eth")
```

***

## Best Practices

### 1. Use Appropriate Page Sizes

```javascript
// Good: Reasonable page size
const PAGE_SIZE = 50

// Bad: Too large, slow queries
const PAGE_SIZE = 10000

// Bad: Too small, too many requests
const PAGE_SIZE = 5
```

**Recommended page sizes:**

* Trades: 20-100
* Fee transactions: 50-100
* Trade history: 50-200
* Tokens: 100-500

***

### 2. Cache Paginated Results

```javascript
class CachedPaginator {
  constructor(client, query, variables) {
    this.client = client
    this.query = query
    this.variables = variables
    this.cache = new Map()
  }
  
  async getPage(page, pageSize = 20) {
    const cacheKey = `${page}-${pageSize}`
    
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey)
    }
    
    const { data } = await this.client.query({
      query: this.query,
      variables: {
        ...this.variables,
        limit: pageSize,
        offset: page * pageSize
      }
    })
    
    this.cache.set(cacheKey, data)
    return data
  }
  
  clearCache() {
    this.cache.clear()
  }
}
```

***

### 3. Handle Empty Results

```javascript
async function fetchTradesWithEmptyCheck(trader, page = 0) {
  const { data } = await client.query({
    query: GET_TRADES,
    variables: {
      trader,
      limit: 20,
      offset: page * 20
    }
  })
  
  const trades = data.perp.trades
  
  if (trades.length === 0) {
    if (page === 0) {
      console.log('No trades found for this trader')
    } else {
      console.log('No more trades to load')
    }
  }
  
  return trades
}
```

***

### 4. Debounce Filter Changes

```javascript
import { debounce } from 'lodash'

const debouncedSearch = debounce(async (searchTerm) => {
  const results = await searchTrades(trader, searchTerm)
  updateUI(results)
}, 300) // Wait 300ms after user stops typing

// Usage in React
function SearchInput() {
  const handleChange = (e) => {
    debouncedSearch(e.target.value)
  }
  
  return <input onChange={handleChange} />
}
```

***

### 5. Show Loading States

```javascript
async function loadTradesWithUI(trader, page) {
  try {
    showLoadingSpinner()
    
    const trades = await fetchPage(trader, page)
    
    if (trades.length === 0) {
      showEmptyState()
    } else {
      renderTrades(trades)
    }
  } catch (error) {
    showErrorState(error)
  } finally {
    hideLoadingSpinner()
  }
}
```

***

### 6. Validate Filter Inputs

```javascript
function validateDateRange(fromDate, toDate) {
  const from = new Date(fromDate)
  const to = new Date(toDate)
  
  if (isNaN(from.getTime()) || isNaN(to.getTime())) {
    throw new Error('Invalid date format')
  }
  
  if (from > to) {
    throw new Error('From date must be before to date')
  }
  
  const maxRange = 90 * 24 * 60 * 60 * 1000 // 90 days
  if (to - from > maxRange) {
    throw new Error('Date range too large (max 90 days)')
  }
  
  return { fromDate, toDate }
}

// Usage
try {
  const { fromDate, toDate } = validateDateRange(
    userInputFrom,
    userInputTo
  )
  
  const { data } = await client.query({
    query: GET_FEES,
    variables: { fromDate, toDate }
  })
} catch (error) {
  showError(error.message)
}
```


# Use Cases

Discover real-world examples of building with Sai. Learn how developers integrate Sai into their applications and services.

Explore real-world examples of how to build with Sai. These guides showcase practical implementations using the Sai developer tools, smart contracts, and APIs.

## Featured Use Cases

### Telegram Bot

Build a trading bot that brings Sai's power directly to your users through Telegram.

{% content-ref url="/pages/bzk5b2zHGvi61jze31O7" %}
[Telegram Bot Integration](/for-devs/use-cases/tg-guide)
{% endcontent-ref %}

***

## Get Started

New to Sai? Check out our main developer documentation to learn about the core tools and APIs:

{% content-ref url="/pages/3IWKplL3iXb4MF6ssbI2" %}
[Getting Started](/for-devs/dev)
{% endcontent-ref %}

## Have a Use Case to Share?

Building something interesting with Sai? We'd love to hear about it!

* **Share your project** on [X/Twitter](https://x.com/saidotfun)
* **Join our community** to showcase your work


# Telegram Bot Integration

A step-by-step walkthrough for creating a Telegram bot that queries real data from the Sai protocol. **No prior bot experience needed!**

***

## What You're About to Build

By the end of this guide, you'll have a working Telegram bot that:

* Shows perpetual trades for any wallet address
* Displays oracle prices for all tokens
* Lets users search for specific token prices
* Filters trades by asset (BTC only, ETH only, etc.)
* Displays results with pagination (Next buttons)

**Best part:** No database needed! Everything pulls live data from Sai's GraphQL API.

***

## Prerequisites Checklist

Before starting, make sure you have:

### Software You'll Need

* **Python 3.10+** - [Download here](https://www.python.org/downloads/)
  * Check your version: Open terminal and type `python3 --version`
* **A code editor** - Any of these work:
  * [VS Code](https://code.visualstudio.com/) (recommended for beginners)
  * PyCharm
  * Sublime Text
  * Even Notepad will work!
* **Terminal/Command Line** - You'll need to type a few commands

### Accounts You'll Need

* **Telegram account** - To test your bot
* **A Telegram bot token** - We'll get this from BotFather (the official Telegram bot creator tool)

### Knowledge Requirements

* Basic Python understanding (if-statements, functions, dictionaries)
* How to use terminal/command line (navigating folders, running commands)
* **No GraphQL knowledge needed** - We'll explain everything!

> **Don't have Python installed?** This is normal! Follow the download link above and choose your operating system. Python installation is straightforward.

***

## Part 1: Get Your Telegram Bot Token (5 minutes)

Your bot needs a unique token to identify itself to Telegram. Here's how to get one:

### Step 1: Open BotFather on Telegram

1. Open the Telegram app (phone, desktop, or [web](https://web.telegram.org/))
2. Search for `@BotFather` in the search bar
3. Click on it and start a conversation

You should see BotFather respond with a welcome message and commands.

### Step 2: Create a New Bot

1. Send this command: `/newbot`
2. BotFather will ask: **"Alright! New bot. How are we going to call it? Please choose a name for your bot."**
   * Type something like: `My Sai Trading Bot` (this is what appears in Telegram)
3. BotFather will ask: **"Good. Now let's choose a username for your bot. It must end in bot."**
   * Type something like: `my_sai_trading_bot` (must end with `bot`)
   * Example: `sai_price_bot`, `trading_bot_sai`, etc.

### Step 3: Copy Your Token

BotFather will send you a message like:

```txt
Done! Congratulations on your new bot. You'll find it at t.me/my_sai_trading_bot. 
You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your bot and it's ready to go public, ping our Bot Support if you'd like a chance to be featured on the @BotStoreBot and the Bot Store within Telegram.

Here's your token:

123456789:ABCdefGHIjklMNOpqrsTUVwxyz
```

**Important:** This token is like your bot's password. **Never share it publicly or commit it to GitHub!**

***

## Part 2: Prepare Your Computer (10 minutes)

### Step 1: Create a Project Folder

Open your terminal/command prompt and create a folder for your project:

```bash
mkdir sai-telegram-bot
cd sai-telegram-bot
```

**What this does:**

* Creates a new folder called `sai-telegram-bot`
* Moves you into that folder (you'll work from here)

### Step 2: Create a Virtual Environment

A virtual environment is like a sandbox for your project. It keeps your bot's dependencies separate from other Python projects.

**On Mac/Linux:**

```bash
python3 -m venv venv
source venv/bin/activate
```

**On Windows:**

```bash
python -m venv venv
venv\Scripts\activate
```

**What should happen:**

* Your terminal prompt should now show `(venv)` at the beginning
* Example: `(venv) user@computer sai-telegram-bot %`

If you see `(venv)` ✅ - Perfect! You're in the virtual environment.

> **What's a virtual environment?** Think of it like a separate Python installation just for this project. If you install packages here, they won't affect your other projects.

### Step 3: Create Your Project Structure

Let's create the folders and files you'll need:

```bash
mkdir bot
touch bot/__init__.py
touch bot/main.py
touch bot/graphql.py
touch requirements.txt
touch .env
touch env.example
```

**What this creates:**

```txt
sai-telegram-bot/
├── venv/                  # Virtual environment (created automatically)
├── bot/
│   ├── __init__.py       # Makes 'bot' a Python package
│   ├── main.py           # Your bot's main code
│   └── graphql.py        # Code to talk to Sai's API
├── requirements.txt      # List of packages to install
├── .env                  # Your secret config (token goes here)
└── env.example          # Template for .env
```

***

## Part 3: Install Required Packages (2 minutes)

Packages are like plugins that give Python extra abilities. We need several for this bot.

### Step 1: Create `requirements.txt`

Open your code editor and create a file called `requirements.txt` with this content:

```txt
python-telegram-bot==21.4
requests==2.32.3
python-dotenv==1.0.1
pytz==2024.1
```

**What each package does:**

| Package               | Purpose                            |
| --------------------- | ---------------------------------- |
| `python-telegram-bot` | Library for creating Telegram bots |
| `requests`            | Makes HTTP requests to APIs        |
| `python-dotenv`       | Loads secret config from .env file |
| `pytz`                | Handles timezones                  |

### Step 2: Install the Packages

In your terminal (make sure `(venv)` is showing), type:

```bash
pip install -r requirements.txt
```

This will download and install all the packages. You should see lots of text scrolling by. Wait for it to finish.

**Success looks like:**

```cmd
Successfully installed python-telegram-bot-21.4 requests-2.32.3 python-dotenv-1.0.1 pytz-2024.1
```

***

## Part 4: Set Up Your Secret Token (5 minutes)

We'll store your bot token in a special file so it's not exposed in your code.

### Step 1: Create `env.example`

This file shows what secrets are needed (without actual values):

```ini
# Telegram Bot Configuration
# Get your bot token from @BotFather on Telegram
TELEGRAM_BOT_TOKEN=your_bot_token_here

# Sai GraphQL Endpoint (the server we get data from)
SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query
```

### Step 2: Create `.env`

Copy the example file:

```bash
cp env.example .env
```

Now open `.env` in your editor and replace `your_bot_token_here` with your actual token from BotFather:

```ini
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query
```

**Security note:** The `.env` file should NEVER be shared or committed to GitHub. If you use Git, add `.env` to your `.gitignore` file.

***

## Part 5: Build the GraphQL Client (Intermediate)

The "GraphQL client" is code that talks to Sai's data API. Don't worry - we'll explain everything!

### What is GraphQL?

GraphQL is a way to ask a server for specific data. Think of it like a restaurant menu:

* **REST API (old way):** "Give me all the data about trades" (you get everything)
* **GraphQL (new way):** "Give me only the trade ID, amount, and price" (you get exactly what you want)

### Create `bot/graphql.py`

This file handles all communication with Sai's API. Here's the code:

```python
import os
from typing import Any, Dict, List, Optional
import requests

# Get the GraphQL endpoint from .env file, or use default
SAI_GRAPHQL_ENDPOINT = os.environ.get(
    "SAI_GRAPHQL_ENDPOINT", 
    "https://sai-keeper.nibiru.fi/query"
)


class SaiGQLClient:
    """
    This class talks to the Sai GraphQL API.
    It's like a translator between your bot and Sai's data server.
    """
    
    def __init__(self, endpoint: Optional[str] = None) -> None:
        """
        Initialize the client.
        
        Args:
            endpoint: URL of the GraphQL server (or None to use default)
        """
        self.endpoint = endpoint or SAI_GRAPHQL_ENDPOINT

    def query(self, query: str, variables: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """
        Send a GraphQL query to the server.
        
        Args:
            query: The GraphQL query string (ask what data you want)
            variables: Dynamic values to plug into the query
            
        Returns:
            Dictionary with the response data
            
        Raises:
            RuntimeError if the query fails
        """
        # Send POST request (like sending a letter) to GraphQL server
        resp = requests.post(
            self.endpoint,
            json={"query": query, "variables": variables or {}},
            timeout=20,  # Wait max 20 seconds for response
        )
        
        # Check if the HTTP request itself failed
        resp.raise_for_status()
        
        # Convert response from JSON format to Python dictionary
        try:
            payload = resp.json()
        except ValueError as e:
            # If response wasn't JSON, something went wrong
            raise RuntimeError(
                f"Invalid JSON response from {self.endpoint}: {resp.text[:200]}"
            )
        
        # Check if GraphQL returned an error
        if "errors" in payload:
            raise RuntimeError(str(payload["errors"]))
        
        # Return just the "data" part of the response
        return payload.get("data", {})

    def fetch_trades(
        self, 
        trader: str, 
        is_open: Optional[bool] = None, 
        limit: int = 100,
        base_symbol: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        Get trades for a specific wallet address.
        
        Args:
            trader: Wallet address (e.g., "nibiru1abc123...")
            is_open: 
                - True: only open trades
                - False: only closed trades  
                - None: all trades
            limit: Max number of trades to return
            base_symbol: Filter by token (e.g., "BTC" for Bitcoin only)
            
        Returns:
            List of trade dictionaries
        """
        # This is a GraphQL query (in plain English: "give me trades")
        query = """
        query Trades($trader: String!, $isOpen: Boolean, $limit: Int!) {
          perp {
            trades(
              where: { trader: $trader, isOpen: $isOpen }
              limit: $limit
              order_by: sequence
              order_desc: true
            ) {
              id
              trader
              isOpen
              isLong
              leverage
              openPrice
              closePrice
              openCollateralAmount
              collateralAmount
              openBlock { block block_ts }
              closeBlock { block block_ts }
              state {
                positionValue
                liquidationPrice
                pnlCollateral
                pnlPct
              }
              perpBorrowing {
                marketId
                baseToken { id name symbol }
                quoteToken { id name symbol }
              }
            }
          }
        }
        """
        
        # Send the query with the variables
        data = self.query(query, {
            "trader": trader, 
            "isOpen": is_open, 
            "limit": limit
        })
        
        # Extract trades from the response
        trades = data.get("perp", {}).get("trades", [])
        
        # Filter by symbol if user asked for it (e.g., BTC only)
        if base_symbol:
            base_symbol_upper = base_symbol.upper()
            trades = [
                t for t in trades
                if (t.get("perpBorrowing", {}).get("baseToken", {}).get("symbol", "") or "").upper() == base_symbol_upper
            ]
        
        return trades

    def fetch_prices(self, limit: int = 200) -> List[Dict[str, Any]]:
        """
        Get current prices for all tokens from the oracle.
        
        Args:
            limit: Max number of prices to return
            
        Returns:
            List of price dictionaries
        """
        query = """
        query Prices($limit: Int!) {
          oracle {
            tokenPricesUsd(limit: $limit, order_by: oracle_token_id) {
              priceUsd
              token { id name symbol }
              lastUpdatedBlock { block block_ts }
            }
          }
        }
        """
        data = self.query(query, {"limit": limit})
        return data.get("oracle", {}).get("tokenPricesUsd", [])

    def fetch_price_by_symbol(self, symbol: str) -> Optional[Dict[str, Any]]:
        """
        Get price for one specific token.
        
        Args:
            symbol: Token symbol (e.g., "BTC", "ETH")
            
        Returns:
            Price dictionary if found, None if not
        """
        prices = self.fetch_prices(limit=200)
        symbol_upper = symbol.upper()
        for price in prices:
            token = price.get("token") or {}
            if (token.get("symbol") or "").upper() == symbol_upper:
                return price
        return None
```

**Key concepts explained:**

* **`class SaiGQLClient:`** - A class is like a blueprint. It groups related functions together.
* **`def query():`** - The main function that sends queries to the API
* **`fetch_trades():`** - Gets trade data for a wallet
* **`fetch_prices():`** - Gets token prices
* **String variables like `$trader`** - These are placeholders that get filled in with real values

***

## Part 6: Create the Bot's Brain (`bot/main.py`)

This is where your bot comes alive! We'll build this in sections.

### Section 1: Imports and Setup

Create `bot/__init__.py` (can be empty):

```python
# This file makes 'bot' a Python package
```

Create `bot/main.py` - Start with imports and setup:

```python
import os
import logging
from typing import Optional

from dotenv import load_dotenv
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    Application,
    CommandHandler,
    CallbackQueryHandler,
    ContextTypes,
)

from .graphql import SaiGQLClient

# Set up logging (so you can see what your bot is doing)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
```

**What this does:**

* Imports libraries we installed earlier
* Sets up logging so we can debug issues
* Imports our GraphQL client

***

### Section 2: Trade Formatting Functions

Add this to `bot/main.py`:

```python
def format_trade_open(trade: dict) -> str:
    """
    Format an open trade for display in Telegram.
    Shows all the important info: position value, profit/loss, etc.
    """
    trade_id = trade.get('id', '?')
    is_long = trade.get('isLong', False)
    side = "🟢 LONG" if is_long else "🔴 SHORT"
    
    # Extract market info (what's being traded)
    market = trade.get("perpBorrowing", {})
    base_token = market.get("baseToken") or {}
    quote_token = market.get("quoteToken") or {}
    base = base_token.get("symbol") or base_token.get("name") or "?"
    quote = quote_token.get("symbol") or quote_token.get("name") or "?"
    
    # Extract trade details
    leverage = trade.get('leverage')
    entry_price = trade.get('openPrice')
    open_collateral = trade.get('openCollateralAmount')
    
    # Extract state info (for open trades)
    state = trade.get('state') or {}
    position_value = state.get('positionValue')  # How much the position is worth
    liquidation_price = state.get('liquidationPrice')  # When you get liquidated
    pnl = state.get('pnlCollateral')  # Profit/Loss in dollars
    pnl_pct = state.get('pnlPct')  # Profit/Loss in percentage
    
    open_ts = trade.get("openBlock", {}).get("block_ts")
    
    # Build the message
    lines = [
        f"━━━━━━━━━━━━━━━━",
        f"Trade #{trade_id} ✅ OPEN",
        f"{base}/{quote} | {side} | {leverage}x leverage",
        f"Entry Price: {entry_price}",
    ]
    
    if liquidation_price:
        lines.append(f"Liquidation Price: {liquidation_price}")
    
    # Convert from micro units to dollars
    # (API stores values * 1,000,000, we need to divide back)
    if position_value is not None:
        position_value_usd = position_value / (10 ** 6)
        lines.append(f"Position Value: ${position_value_usd:,.2f}")
    
    if pnl is not None:
        pnl_usd = pnl / (10 ** 6)
        pnl_sign = "+" if pnl_usd >= 0 else ""
        lines.append(f"PnL: {pnl_sign}${pnl_usd:,.2f}")
    
    if pnl_pct is not None:
        pnl_sign = "+" if pnl_pct >= 0 else ""
        lines.append(f"PnL %: {pnl_sign}{pnl_pct:.2f}%")
    
    if open_collateral:
        collateral_usd = open_collateral / (10 ** 6)
        lines.append(f"Collateral: ${collateral_usd:,.2f}")
    
    if open_ts:
        lines.append(f"Opened: {open_ts}")
    
    return "\n".join(lines)


def format_trade_closed(trade: dict) -> str:
    """
    Format a closed trade for display.
    Shows entry/exit prices and when it was opened/closed.
    """
    trade_id = trade.get('id', '?')
    is_long = trade.get('isLong', False)
    side = "🟢 LONG" if is_long else "🔴 SHORT"
    
    market = trade.get("perpBorrowing", {})
    base_token = market.get("baseToken") or {}
    quote_token = market.get("quoteToken") or {}
    base = base_token.get("symbol") or base_token.get("name") or "?"
    quote = quote_token.get("symbol") or quote_token.get("name") or "?"
    
    leverage = trade.get('leverage')
    entry_price = trade.get('openPrice')
    exit_price = trade.get('closePrice')
    
    open_ts = trade.get("openBlock", {}).get("block_ts")
    close_ts = trade.get("closeBlock", {}).get("block_ts")
    
    lines = [
        f"━━━━━━━━━━━━━━━━",
        f"Trade #{trade_id} ❌ CLOSED",
        f"{base}/{quote} | {side} | {leverage}x leverage",
        f"Entry Price: {entry_price}",
    ]
    
    if exit_price:
        lines.append(f"Exit Price: {exit_price}")
    
    if open_ts:
        lines.append(f"Opened: {open_ts}")
    if close_ts:
        lines.append(f"Closed: {close_ts}")
    
    return "\n".join(lines)
```

**What this does:**

* `format_trade_open()` - Displays an open trade with profit/loss info
* `format_trade_closed()` - Displays a closed trade with entry/exit prices
* The division by `10^6` converts from micro-units (how the API stores numbers) to regular dollars

***

### Section 3: Price Formatting Functions

Add this to `bot/main.py`:

```python
# Popular tokens to show first
POPULAR_TOKENS = ["BTC", "ETH", "USDT", "USDC", "NIBI", "ATOM", "SOL"]

def format_prices(prices: list, start_idx: int = 0, page_size: int = 10) -> tuple:
    """
    Format prices for display with pagination (Next buttons).
    
    Args:
        prices: List of all prices
        start_idx: Which index to start at
        page_size: How many prices per page
        
    Returns:
        (formatted_text, has_more_pages)
    """
    if not prices:
        return "No prices found.", False
    
    # Sort prices: popular tokens first, then by ID
    def sort_key(p):
        token = p.get("token") or {}
        symbol = (token.get("symbol") or "").upper()
        if symbol in POPULAR_TOKENS:
            return (0, POPULAR_TOKENS.index(symbol))
        return (1, token.get("id", 9999))
    
    sorted_prices = sorted(prices, key=sort_key)
    
    # Get just this page of prices
    end_idx = min(start_idx + page_size, len(sorted_prices))
    page_prices = sorted_prices[start_idx:end_idx]
    has_more = end_idx < len(sorted_prices)
    
    # Format as text
    msg_lines = [f"💰 Oracle Prices ({end_idx} of {len(sorted_prices)})\n"]
    for p in page_prices:
        token = p.get("token") or {}
        symbol = token.get("symbol") or token.get("name") or "Unknown"
        price = p.get("priceUsd")
        if price is not None:
            price_str = f"${price:,.2f}" if price >= 1 else f"${price:.8f}"
            msg_lines.append(f"• {symbol}: {price_str}")
    
    return "\n".join(msg_lines), has_more


def format_price_single(price_data: dict) -> str:
    """Format a single price for display."""
    token = price_data.get("token") or {}
    symbol = token.get("symbol") or token.get("name") or "?"
    price = price_data.get("priceUsd")
    
    if price is None:
        return f"Price not available for {symbol}"
    
    price_str = f"${price:,.2f}" if price >= 1 else f"${price:.8f}"
    return f"💰 {symbol}: {price_str}"
```

**What this does:**

* `format_prices()` - Formats multiple prices with pagination
* `format_price_single()` - Formats a single price nicely
* Popular tokens (BTC, ETH, etc.) appear first

***

### Section 4: Command Handlers

Add this to `bot/main.py`:

```python
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Handle /start command - shows welcome message."""
    text = (
        "👋 Welcome to Sai Bot!\n\n"
        "Available commands:\n"
        "/trades <address> - View trades for a wallet\n"
        "/prices - Show all token prices\n"
        "/price <symbol> - Get price for one token\n"
        "/help - Show this message\n\n"
        "Examples:\n"
        "/trades nibiru1abc123def456\n"
        "/price BTC\n"
        "/prices"
    )
    await update.effective_message.reply_text(text)


async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Handle /help command."""
    await start(update, context)
```

**What this does:**

* `/start` command shows welcome message
* `/help` shows the same message

***

### Section 5: The Trades Command

Add this to `bot/main.py`:

```python
async def trades_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """
    Handle /trades command.
    
    Usage:
        /trades <address>
        /trades <address> open
        /trades <address> closed
        /trades <address> btc
    """
    if not context.args:
        await update.effective_message.reply_text(
            "Usage: /trades <wallet_address> [open|closed] [symbol]\n\n"
            "Examples:\n"
            "/trades nibiru1abc123...\n"
            "/trades nibiru1abc123... open\n"
            "/trades nibiru1abc123... btc"
        )
        return
    
    address = context.args[0].strip()
    is_open = None  # None = all trades
    symbol = None
    
    # Parse additional arguments
    for arg in context.args[1:]:
        arg_lower = arg.strip().lower()
        if arg_lower == "open":
            is_open = True
        elif arg_lower == "closed":
            is_open = False
        else:
            symbol = arg.strip().upper()
    
    try:
        # Get trades from Sai
        gql = SaiGQLClient()
        trades = gql.fetch_trades(
            trader=address, 
            is_open=is_open, 
            limit=100, 
            base_symbol=symbol
        )
        
        if not trades:
            await update.effective_message.reply_text(
                f"❌ No trades found for {address}"
            )
            return
        
        # Separate open and closed trades
        open_trades = [t for t in trades if t.get('isOpen')]
        closed_trades = [t for t in trades if not t.get('isOpen')]
        
        # Show open trades first
        if open_trades:
            for i, trade in enumerate(open_trades[:5]):  # Show first 5
                await update.effective_message.reply_text(
                    format_trade_open(trade)
                )
        
        # Show closed trades
        if closed_trades:
            for i, trade in enumerate(closed_trades[:5]):  # Show first 5
                await update.effective_message.reply_text(
                    format_trade_closed(trade)
                )
                
    except Exception as e:
        logger.error(f"Error in trades_cmd: {e}")
        await update.effective_message.reply_text(
            f"❌ Error: {str(e)}"
        )
```

***

### Section 6: The Prices Commands

Add this to `bot/main.py`:

```python
async def prices_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Handle /prices command - show all prices."""
    try:
        gql = SaiGQLClient()
        prices = gql.fetch_prices(limit=200)
        
        if not prices:
            await update.effective_message.reply_text("No prices found.")
            return
        
        # Get first page
        text, has_more = format_prices(prices, start_idx=0, page_size=10)
        
        # Add keyboard with "Next" button if there are more prices
        reply_markup = None
        if has_more:
            keyboard = [[InlineKeyboardButton("Next →", callback_data="prices_next")]]
            reply_markup = InlineKeyboardMarkup(keyboard)
        
        await update.effective_message.reply_text(text, reply_markup=reply_markup)
        
    except Exception as e:
        logger.error(f"Error in prices_cmd: {e}")
        await update.effective_message.reply_text(f"❌ Error: {str(e)}")


async def price_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Handle /price <symbol> command - show single price."""
    if not context.args:
        await update.effective_message.reply_text(
            "Usage: /price <symbol>\n\n"
            "Examples: /price BTC, /price ETH"
        )
        return
    
    symbol = context.args[0].strip()
    
    try:
        gql = SaiGQLClient()
        price_data = gql.fetch_price_by_symbol(symbol)
        
        if not price_data:
            await update.effective_message.reply_text(
                f"❌ Token '{symbol}' not found"
            )
            return
        
        text = format_price_single(price_data)
        await update.effective_message.reply_text(text)
        
    except Exception as e:
        logger.error(f"Error in price_cmd: {e}")
        await update.effective_message.reply_text(f"❌ Error: {str(e)}")
```

***

### Section 7: Callback Handler (for buttons)

Add this to `bot/main.py`:

```python
async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Handle button clicks (pagination buttons)."""
    query = update.callback_query
    if not query:
        return
    
    # Acknowledge the button click
    await query.answer()
    
    # Handle "Next" button for prices
    if query.data == "prices_next":
        page = context.user_data.get("prices_page", 0) + 1
        context.user_data["prices_page"] = page
        
        try:
            gql = SaiGQLClient()
            prices = gql.fetch_prices(limit=200)
            text, has_more = format_prices(prices, start_idx=page * 10, page_size=10)
            
            reply_markup = None
            if has_more:
                keyboard = [[InlineKeyboardButton("Next →", callback_data="prices_next")]]
                reply_markup = InlineKeyboardMarkup(keyboard)
            
            await query.edit_message_text(text, reply_markup=reply_markup)
        except Exception as e:
            await query.edit_message_text(f"❌ Error: {str(e)}")
```

***

### Section 8: Start the Bot

Add this to the end of `bot/main.py`:

```python
def main() -> None:
    """Main function - creates and starts the bot."""
    # Load environment variables from .env file
    load_dotenv()
    
    # Get bot token
    token = os.environ.get("TELEGRAM_BOT_TOKEN")
    if not token:
        raise RuntimeError("❌ TELEGRAM_BOT_TOKEN not found in .env!")
    
    # Create the bot application
    app = Application.builder().token(token).build()
    
    # Register command handlers
    app.add_handler(CommandHandler("start", start))
    app.add_handler(CommandHandler("help", help_cmd))
    app.add_handler(CommandHandler("trades", trades_cmd))
    app.add_handler(CommandHandler("prices", prices_cmd))
    app.add_handler(CommandHandler("price", price_cmd))
    
    # Register button click handler
    app.add_handler(CallbackQueryHandler(callback_handler))
    
    # Start the bot
    logger.info("🚀 Bot is starting...")
    app.run_polling()


if __name__ == "__main__":
    main()
```

***

## Part 7: Test Your Bot! (5 minutes)

### Step 1: Activate Virtual Environment

Make sure you're in the project folder and the virtual environment is active:

```bash
# You should see (venv) at the start of your terminal prompt
# If not, activate it:

# Mac/Linux:
source venv/bin/activate

# Windows:
venv\Scripts\activate
```

### Step 2: Start the Bot

```bash
python -m bot.main
```

You should see:

```txt
INFO:__main__:🚀 Bot is starting...
```

**This means your bot is running!** Leave this terminal open.

### Step 3: Test on Telegram

1. Open Telegram
2. Search for your bot (the username you created)
3. Send `/start`
4. You should see the welcome message!

Try these commands:

| Command                    | What it does                            |
| -------------------------- | --------------------------------------- |
| `/start`                   | Shows welcome message                   |
| `/help`                    | Shows help message                      |
| `/prices`                  | Shows all token prices                  |
| `/price BTC`               | Shows BTC price                         |
| `/trades nibiru1abc123...` | Shows trades (need real wallet address) |

### Step 4: Stop the Bot

When you're done testing, press `Ctrl+C` in the terminal to stop the bot.

***

## Troubleshooting

### Problem: "TELEGRAM\_BOT\_TOKEN not found"

**Solution:**

1. Check that `.env` file exists in your project root
2. Open it and verify the token is there
3. Make sure there are no spaces: `TELEGRAM_BOT_TOKEN=123456789:ABC...`

### Problem: Bot doesn't respond to commands

**Solution:**

1. Check that the bot is still running (you should see `🚀 Bot is starting...` in terminal)
2. Try the `/start` command first
3. Check for errors in the terminal

### Problem: "ModuleNotFoundError: No module named 'bot'"

**Solution:**

```bash
# Make sure you're in the project root directory
cd /path/to/sai-telegram-bot

# Make sure you activated the virtual environment
source venv/bin/activate  # Mac/Linux
# or
venv\Scripts\activate     # Windows

# Run with -m flag
python -m bot.main
```

### Problem: GraphQL errors or "No prices found"

**Solution:**

1. Check your internet connection
2. Verify the endpoint is correct in `.env`:

   ```txt
   SAI_GRAPHQL_ENDPOINT=https://sai-keeper.nibiru.fi/query
   ```
3. Test the endpoint manually in your browser (visit the URL)

***

## How It All Works Together

Here's the flow when someone uses your bot:

```txt
1. User sends: /prices
   ↓
2. Telegram receives it and forwards to your bot
   ↓
3. prices_cmd() function is called
   ↓
4. SaiGQLClient queries the Sai GraphQL API
   ↓
5. API returns price data
   ↓
6. format_prices() formats the data beautifully
   ↓
7. Bot sends the message back to the user
   ↓
8. User sees prices with a "Next" button
   ↓
9. User clicks "Next"
   ↓
10. callback_handler() fetches the next page
    ↓
11. User sees next page of prices
```

***

## Next Steps

Your bot is working! Now you can:

### Level Up Your Bot

* Add price alerts ("Notify me when BTC hits $100,000")
* Add wallet monitoring ("Show me when this address opens a trade")
* Add more markets or data sources

### Deploy to Production

* Run your bot 24/7 on a server (AWS, DigitalOcean, etc.)
* Use `systemd` or Docker to keep it running

### Learn More

* [python-telegram-bot docs](https://python-telegram-bot.org/)
* [GraphQL basics](https://graphql.org/learn/)
* [Sai Docs](https://docs.sai.fi)

***

## Congratulations! 🎉

You've built your first Telegram bot that:

* Connects to the Sai protocol
* Queries real live data
* Displays prices and trades
* Handles pagination
* Filters by asset

**You're now a bot developer!**

***

## Common Questions

**Q: Can I share my bot with others?** A: Yes! They can find it by searching your bot's username on Telegram and click "Start".

**Q: Will my bot keep running if I close my computer?** A: No. You'll need to deploy it to a server to run 24/7. See the "Deploy to Production" section.

**Q: How do I update my bot with new features?** A: Edit the Python files, then restart the bot (press Ctrl+C and run `python -m bot.main` again).

**Q: Is it safe to share my bot token?** A: No! Treat it like a password. Keep it in `.env` and never commit to GitHub.

***

Happy coding! 🚀


# Community

Join the Sai community across our official channels.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><img src="https://cdn.simpleicons.org/discord/5865F2" alt=""> <strong>Discord</strong></td><td>Chat, support, and announcements</td><td><a href="https://discord.com/invite/saidotfun">https://discord.com/invite/saidotfun</a></td><td></td></tr><tr><td><img src="https://cdn.simpleicons.org/x/000000" alt=""> <strong>X / Twitter</strong></td><td>@SaiDotFun — news and updates</td><td><a href="https://x.com/SaiDotFun">https://x.com/SaiDotFun</a></td><td></td></tr><tr><td><img src="https://cdn.simpleicons.org/telegram/26A5E4" alt=""> <strong>Telegram</strong></td><td>News channel and alerts</td><td><a href="https://t.me/saidotfun">https://t.me/saidotfun</a></td><td></td></tr><tr><td><img src="https://cdn.simpleicons.org/instagram/E4405F" alt=""> <strong>Instagram</strong></td><td>@saidotfun</td><td><a href="https://instagram.com/saidotfun">https://instagram.com/saidotfun</a></td><td></td></tr><tr><td><img src="https://cdn.simpleicons.org/tiktok/000000" alt=""> <strong>TikTok</strong></td><td>@saidotfun</td><td><a href="https://www.tiktok.com/@saidotfun">https://www.tiktok.com/@saidotfun</a></td><td></td></tr><tr><td><img src="https://cdn.simpleicons.org/youtube/FF0000" alt=""> <strong>YouTube</strong></td><td>@saidotfun</td><td><a href="https://www.youtube.com/@saidotfun">https://www.youtube.com/@saidotfun</a></td><td></td></tr></tbody></table>


# Blog

Blogs for Sai and Sai.fun.

Stay updated with the latest news, feature releases, and insights from the Sai perpetuals platform. Our blog covers trading tips, platform updates, ecosystem news, and community highlights.

***

> 📰 [**Introducing Sai: Nibiru's Perpetuals DEX**](/resources/blogs/intro-to-sai)\
> Learn about oracle-settled pricing, flexible collateral, and our 2026 roadmap.

> 🏆 [**Let's Go Saicho: $25,000 Prize Pool**](/resources/blogs/lets-go-saicho)\
> Two-phase competition (Feb 18 - Mar 19, 2026) rewarding both skill and participation.

> 🤝 [**The SaiClone Ambassador Program**](/resources/blogs/saiclone-ambassador)\
> Earn rewards for participation and content creation across three tiers: Saicho, Saiborg, and Sage.

> 🎁 [**Let's Go Saicho: Reward Distribution**](/resources/blogs/lets-go-saicho-rewards)\
> Competition has concluded. See eligibility, prize breakdowns, and how rewards are distributed.

> 📈 [**Sai Launches Stock Trading on Perps**](/resources/blogs/stock-perps-launch)\
> Trade popular stocks long or short through perpetual markets — fully onchain, no gas fees, no custody.

> 🏆 [**Sai Cookout: $5,100 Prize Pool**](/resources/blogs/sai-cookout)\
> Month-long competition (Apr 22 - May 20, 2026) with three ways to win across all Sai markets.

***

## Subscribe to Updates

Don't miss new posts. Follow Sai for real-time announcements:

* **X / Twitter:** [@SaiDotFun](https://github.com/NibiruChain/sai-docs/blob/main/blogs/__https:/x.com/SaiDotFun__/README.md)
* **YouTube:** [@saidotfun](https://github.com/NibiruChain/sai-docs/blob/main/blogs/__https:/www.youtube.com/@saidotfun__/README.md)
* **Telegram:** [Join our community](https://github.com/NibiruChain/sai-docs/blob/main/blogs/__https:/t.me/saidotfun__/README.md)
* **Discord:** [Join our server](https://github.com/NibiruChain/sai-docs/blob/main/blogs/__https:/discord.com/invite/saidotfun__/README.md)
* **TikTok:** [@SaiDotFun](https://github.com/NibiruChain/sai-docs/blob/main/blogs/__https:/www.tiktok.com/@saidotfun__/README.md)
* **Instagram:** [@SaiDotFun](https://github.com/NibiruChain/sai-docs/blob/main/blogs/__https:/instagram.com/saidotfun__/README.md)


# 01 - Intro to Sai

Introducing Sai: Nibiru’s Perpetuals DEX

Over the last few years, crypto trading has moved from spot markets to derivatives. Perpetual futures, or perps, now drive most activity, and a new wave of perpetual DEX apps has proven that crypto perpetual futures are the product many traders care about most.

Crypto trading has evolved, but the user experience hasn't kept up. Traders are still forced to choose: the performance of a CEX, or the security of a DEX.

Sai is a decentralized perpetuals platform built to deliver CEX-level performance with an intuitive UX and reliable settlement. The system provides easy access to yield, reinforces collateral and features protection within the trading experience. Traders get a familiar, streamlined workflow, while liquidity providers gain a transparent path to earning from real activity.

<figure><picture><source srcset="/files/1p1kedCxJCWa2aNcN0go" media="(prefers-color-scheme: dark)"><img src="/files/1p1kedCxJCWa2aNcN0go" alt="Sai"></picture><figcaption><p>Trading made simple. Perps without limits.</p></figcaption></figure>

Sai combines:

* Oracle settled pricing that tracks the global market
* Flexible collateral so capital can be reused across positions
* Single asset vaults that let anyone earn fees from real trading

<figure><picture><source srcset="/files/ijXMy2USMWRPyDptNTxa" media="(prefers-color-scheme: dark)"><img src="/files/ijXMy2USMWRPyDptNTxa" alt="Sai"></picture><figcaption><p>Sai’s Trading Interface</p></figcaption></figure>

## What Traders Get with Sai

### Fair Pricing

Trades settle at oracle based prices that reflect global markets. Your fills and liquidations follow a reliable price feed. This reduces scam wicks, increases confidence when sizing larger positions, and makes Sai feel like a low slippage DEX even when liquidity appears light.

### Funding that is easy to understand

Many perps platforms use funding payments in fixed windows. This can turn funding into a timing game. Sai uses a continuous borrow fee that updates every block based on open interest. You pay a clear borrow cost instead of needing to remember when the next funding window hits:

* Others have payments in fixed windows
* Sai has continuous block by block payments
* Clear borrow costs instead of timing the windows

### Collateral that works cross-chain

Sai will soon support multiple collateral types, such as USDC and yield-bearing assets like stNIBI. You can deposit USDC from chains like Ethereum or Base, and the protocol treats it the same once it arrives.

<figure><picture><source srcset="/files/gYSYPBX537nhJqPoBpQt" media="(prefers-color-scheme: dark)"><img src="/files/gYSYPBX537nhJqPoBpQt" alt="- Deposit USDC from major chains such as Ethereum or Base
- Use yield-bearing assets as collateral so capital can keep earning while you trade
- Open and manage all positions from a single margin balance instead of splitting funds across pairs or platforms
- Deposit funds into Sai using a checking, debit, or credit card through integrated on-ramp partners, so you can start trading without using a separate exchange"></picture><figcaption></figcaption></figure>

### How Sai compares to other platforms

Under the hood, Sai is built around a simple question: what would the best perpetual platform look like if it was designed from scratch?

#### Oracle-native Perps Model

| Aspect                | Details                                                                                 |
| --------------------- | --------------------------------------------------------------------------------------- |
| **Why it works**      | Oracle settled pricing, continuous borrow fee, single asset vaults, flexible collateral |
| **Impact on Traders** | Fairer fills, more, predictable costs, clear roles for traders and LPs                  |

#### Sai vs Legacy Models

| Platform Type              | Common Issues                                                                           | Impact on Traders                                                     |
| -------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Centralized Perp Exchanges | Custody risk, opaque liquidation logic, possible withdrawal limits                      | Funds are not fully under user control                                |
| AMM Style Perp DEXs        | Funding games, skewed vaults, limited markets                                           | Unclear costs, LPs take uneven risk                                   |
| Hybrid Off-Chain Engines   | Extra trust in sequencers or keepers, downtime risk                                     | Users rely on a middle layer instead of the chain itself              |
| Sai                        | Oracle settled pricing, continuous borrow fee, single asset vaults, flexible collateral | Fairer fills, more predictable costs, clear roles for traders and LPs |

Sai tries to answer a simple question: what would a best perpetual DEX look like if it stayed fully onchain and focused on a small set of strong ideas.

[See how trading works on Sai](https://docs.sai.fun/).

## Sai’s Key Design Choices

Under the hood, Sai is meant to be simple.

| Sai Features              | What it Means                                                                                              |
| ------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Oracle Settled Execution  | Custody risk, opaque liquidation logic, possible withdrawal limits                                         |
| Continuous Borrow Fee     | Funding adjusts per block with OI skew                                                                     |
| Passive LP via SLP Vaults | Sai V2 allows collateral deposits of USDC or stNIBI. Users can earn a share of fees without managing pairs |
| Real Fee Perks            | Volume-tier discounts and perks for ecosystem users                                                        |
| Aligned Economics         | Revenue flows back to buybacks and ecosystem growth                                                        |
| Multi VM Performance      | Built on Nibiru's EVM + Wasm stack for smooth integrations                                                 |
| Fast Market Onboarding    | New markets can list quickly once an oracle feed and liquidity are ready                                   |

## How Does Sai Handle Liquidations?

Liquidations are where most perp platforms break during liquidity crunches. [The Oct 10 flash crash](https://www.coindesk.com/opinion/2025/12/02/why-the-market-crashed-on-october-10-and-why-it-s-struggling-to-bounce) showed how a sharp move on one venue can trigger a chain of forced closes, even when the broader market never truly traded there.

Sai liquidates using onchain rules and a global oracle price, not a thin local order book. Validator feeders pull prices from multiple venues using a commit then reveal flow. The chain only accepts updates when participation clears a threshold, then publishes a weighted median price. If data is stale or participation is low, Sai uses the last good price and caps large moves so a single bad update does not snowball into unfair liquidations.

Liquidation is meant to be a last resort. Sai uses a deep buffer, with liquidations only triggering once losses and fees have consumed roughly 90 percent of posted collateral. Your liquidation price is deterministic and includes fees, so it is predictable. If you set a stop loss that would exit earlier than liquidation, Sai prioritizes the stop loss instead of forcing a liquidation. Any remaining collateral after closing costs is returned to the trader.

### Built for stress

* Oracle guardrails (multi source median, participation checks, last good price fallback, capped large moves)
* Exposure limits so single markets do not get dangerously crowded
* Stop loss checks so orders cannot be set in a way that only triggers after liquidation
* Onchain enforcement with permissionless triggering, so execution stays fast and transparent when markets move quickly

## Sai as an All-Market Platform

With oracle based pricing and pooled SLP liquidity, Sai can list any asset that has a reliable price feed and strong market demand.

<figure><picture><source srcset="/files/mEaikKfxARJc974lTAka" media="(prefers-color-scheme: dark)"><img src="/files/mEaikKfxARJc974lTAka" alt="How new markets get created
- **Fee routing:** A portion of trading fees from liquid pairs like BTC and ETH can be directed to seed vaults for less liquid markets
- **Revenue recycling:** As platform revenue grows, funds can be allocated into SLPs,  deepening liquidity
- **Auto-seeding:** When demand crosses a threshold (open interest, votes, or community signals), Sai can seed initial liquidity and spin up that market"></picture><figcaption></figcaption></figure>

### What this unlocks for traders

* Tokenized stocks and IPOs that trade around the clock
* Commodities such as gold or oil through onchain futures
* Synthetic indexes, such as sector or theme based baskets

For many users, this is what a modern decentralized derivatives exchange should look like: one adaptable venue always ready for the next phase of decentralized finance.

### What this unlocks for LPs

* Core vaults gain more fee sources as new markets appear
* Different vaults can target different levels of risk and return
* In future versions, SLP holders will be able to vote on listings and earn extra rewards for backing new markets early

Sai contributors and the community will surface high-potential assets to list. Users can propose and vote on new markets, keeping listings aligned with real demand. When interest is strong, the protocol can seed initial liquidity so trading can start immediately without relying on external market makers.

## Earning with Sai Liquidity Positions (SLP)

SLPs earn trading fees by providing liquidity through single asset vaults. Vaults can be segmented by volatility profile, giving LPs more control over exposure. At launch, deposits in USDC and stNIBI are supported.

<figure><picture><source srcset="/files/3bTZBX478lDZvdnBOhGO" media="(prefers-color-scheme: dark)"><img src="/files/3bTZBX478lDZvdnBOhGO" alt="Sai"></picture><figcaption><p>Risk note: SLPs are the counterparty to traders. When traders profit, vaults can take losses. When traders lose, LPs earn fees.</p></figcaption></figure>

Each vault will support a curated basket of markets, ranging from bluechips to volatile assets to RWAs.

**For example:**

* Vault 1: Solana, Bitcoin, Ethereum, and other bluechips
* Vault 2: WIF, Pepe, TRUMP, Aster, Hype
* Vault 3: S\&P 500, Gold Index, Japanese Yen

[Provide liquidity for Sai - app.sai.fun.](https://app.sai.fun/buy-SLP/)

## Sai Roadmap (2026)

Sai will continue shipping in three main verticals: more markets, better tools, and deeper yield.

<figure><picture><source srcset="/files/Swe2GrPAG6SBR1W61RSB" media="(prefers-color-scheme: dark)"><img src="/files/Swe2GrPAG6SBR1W61RSB" alt="Sai"></picture><figcaption></figcaption></figure>

### Near term focus

* **Real world asset markets:** Add new pairs, through crypto, stocks and commodities to grow liquidity on current markets
* **Refined trading interface:** Continue improvements for a simple interface for new users and fast execution for active traders
* **DeFi integrations:** Connect Sai to other DeFi apps for swaps, routing, and using SLPs as collateral through the earn tab

### New products and yield

* **Sai Savings**: A way for traders to earn yield on their idle stables parked on Sai, 5% on all idle funds while maintaining control and flexibility
* **Automated strategies:** Launch vaults that run user managed strategies on Sai so users can get exposure with one click
* **Accounts:** A true CEX UX with gasless trades, multi-chain and fiat deposits

### Ecosystem and access

* **Cross chain and fiat funding:** Support more networks for USDC deposits and improve on ramps so users can fund with a card or bank account
* **Mobile app:** Release a mobile app that covers core trading and portfolio management
* **Comprehensive tooling:** Robust tooling for developers to build apps on the Sai platform
* Data platform: Backtesting, historical data, custom order types, and automated strategies

Sai’s vision is simple: a platform that matches CEX scale while staying transparent and onchain, and expands into whatever markets traders demand.

## Stay Updated with Sai

Don't miss what's coming next. Follow us for real-time updates on:

* **Beta Launch**: Early access opportunities and testing phases
* **Feature Releases**: New trading pairs, vault strategies, and platform upgrades
* **Developer News**: API changes, smart contract updates, and integration guides
* **Community Events**: Trading competitions, AMAs, and governance discussions

### Follow Sai

{% tabs %}
{% tab title="X / Twitter" %}
Stay updated on announcements, market insights, and community highlights.

[Follow @SaiDotFun](https://x.com/SaiDotFun)
{% endtab %}

{% tab title="Telegram" %}
Quick updates and community coordination.

[Join Telegram](https://t.me/saidotfun)
{% endtab %}
{% endtabs %}

*Published: December 16, 2025*


# 02 - Sai 2026 Roadmap

For 2026, the focus continues to be around trader-first trading, frictionless funding, and a more powerful user experience.

<figure><img src="/files/cFV3v3wZySYwk4DqY4gt" alt=""><figcaption></figcaption></figure>

The priorities cluster around three themes: (1) bringing Sai to mobile with full feature parity, (2) integrating a fiat on-ramp so users can fund accounts directly with a credit or debit card, and (3) enhancing Sai’s trading toolset so power users have full position control.

### **Strategic Priorities**

### **1 - Mobile Experience**

The first priority is making Sai fully accessible on mobile. Starting with wallet browser support, traders will be able to access Sai through popular wallet browsers like Base Wallet, MetaMask, and Trust Wallet. This means you can trade directly from your wallet without needing to open a separate browser.

Following that, a native iOS and Android app is in development. The native app will include a shared design system across iOS and Android, the full trading flow including markets, position management, deposits and withdrawals, and push notifications for fills, liquidations, funding, and price alerts.

### 2 - Multichain Trades

Deposit and trade directly from Base, Ethereum, and other EVM-compatible chains using a single account, with no bridging required. You sign a transaction from the chain where your capital already lives, and Sai handles the rest behind the scenes.

### **3 - Platform Feature Expansion**

<figure><img src="/files/G96V9IutulTgS2MZ6lPY" alt=""><figcaption></figcaption></figure>

Sai gains several high-impact trading features and a developer-facing surface. Together these close the gap with top-tier perp DEXs and unlock new flows for both manual traders and agentic systems.

* Add margin after a position is open: lower liquidation price without closing the trade
* Partial take-profit on any position: lock in gains in pieces instead of all-or-nothing
* Reverse position: flip from long to short (or vice versa) in a single action
* Limit sell orders: schedule exits at a target price
* Sai MCP: A Model Context Protocol server so Claude and other agents can read markets, manage positions, and place orders on Sai programmatically
* Sai Trading Bot: Easy integration with third-party applications like Telegram, Discord, and X to allow users to access to Sai’s engine with an application’s dedicated interface.

### **4 - Fiat On-ramp & Card Funding**

<figure><img src="/files/AJBh7OMwNaEwZKRSTi9G" alt=""><figcaption></figcaption></figure>

Getting capital into Sai requires existing crypto, that typically lives onchain or off a centralized exchange. That's a hard stop for most new users. Integrating a fiat on-ramp provider with credit and debit card support enhances the funding flow into a few taps and unlocks a much larger top-of-funnel.

* Integrate a tier-1 on-ramp provider (e.g., Stripe Crypto, MoonPay) with multi-region coverage
* Credit and debit card funding directly into a user's Sai account. To also explore services that will enable Apple and Google Pay
* Embedded in both web and mobile app for a single funding experience

### **5 - Growth & Marketing**

<figure><img src="/files/5Q4NRlHrXuB7CMC21dQD" alt=""><figcaption></figcaption></figure>

Distribution and retention initiatives that compound the product launches above. The mobile app and on-ramp give us reasons to bring users in; the programs below give them reasons to stay, trade more, and bring others with them.

* Nibiru NFT release: limited collection that doubles as an identity / status layer for early supporters and unlocks perks like fee discounts, points multipliers, whitelist access, and more.
* Points program: rewards traders for volume, holding, and referrals; designed to feed into future rewards.
* Ongoing trading competitions: always-on leaderboard with rotating prize pools and themed events (mobile launch, new market listings, fiat on-ramp activation) to drive volume and social proof.

NFT holders may earn boosted points, reduced fees, and more benefits. Top points earners and competition winners get whitelist access to future drops.

### 6 - Forward-Facing Initiatives

The items above are the current priority. The initiatives below may be incorporated into scope depending on bandwidth and capacity. The team continues to evaluate market conditions, and AI-based solutions are a growing focus that will keep moving up this list.

#### **New Products Inside the DEX**

* Spot trading and swaps integrated directly into Sai via a third-party provider like Oku Trade
* Onchain lending: borrow against your collateral or lend out what's idle
* Social and copy trading: follow top traders, mirror their positions with one tap

#### **Interesting Financial Products**

* Pre-launch token markets: take a position before a project's token goes live
* Structured notes: covered calls, principal-protected, yield-enhanced strategies as one-click products

#### **More Exotic Markets**

* Equities, commodities, FX, rates onchain, 24/7
* Prediction and event markets: elections, sports, macro data, anything with a clear outcome
* Long-tail listings voted in by the community

#### **AI Agents and Strategy Managers**

* Sai Co-pilot: an in-app assistant that suggests setups and explains risk
* AI Strategy Manager: give it a budget, a risk limit, and a thesis. It trades inside your parameters
* Natural-language trading: tell Sai what to do in plain English
* Agent-managed vaults: deposit into AI-run strategies with onchain performance
* Always-on risk monitor: watches positions, warns early, can hedge if you let it

Sai's near-term focus is removing friction for traders: a mobile-first experience, card funding, and the trade controls power users expect. This roadmap will be dynamic. Priorities shift as the markets shift, and feedback from traders and partners directly influences what gets built and in what order.


# 03 - Let's Go Saicho Trading Competition

Sai Trading Competition: $25,500 Prize Pool with Two Exciting Phases

We're excited to announce **Sai's Trading Competition**, a one-month onchain trading competition, running from February 18 to March 19, 2026 designed to reward active traders on Sai. Whether you're a seasoned perpetuals trader or new to the platform, this competition offers something for everyone.

**Competition Dates:** February 18 - March 19, 2026\
**Total Prize Pool:** $25,500 **Entry:** Automatic, just start trading on Sai!

A prize pool of $25,500 will be split across two phases over the duration of the campaign. The competition is divided into two two-week segments: one phase rewards traders with the highest PNL, and the other rewards traders for building volume on the platform. This dual structure recognizes both skilled profitable trading and sustained participation. Both new and experienced traders are encouraged to compete, since every trade can contribute toward the volume phase while net profitable trading is recognized in the PNL phase.

## The Competition Structure

Sai's competition features a dual-phase design that rewards two different trader profiles: those with exceptional trading skill, and those committed to building volume on the platform.

### Phase 2: Volume-Based (March 5 - March 19)

**Reward Pool:** $5,500 | **Focus:** Volume-based rewards across three tracks

| Prize  | How to Win                                                                                                                                                                 |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| $4,000 | Shared among all traders who cross $50k in Phase 2 volume. The more volume you trade, the larger your share of this pool. All activity from Phase 1 counts toward Phase 2. |
| $1,000 | Split evenly among the first 50 traders to reach $10k in Phase 2 volume.                                                                                                   |
| $500   | Awarded to the trader with the highest total volume in Phase 2.                                                                                                            |

Phase 2 is designed to reward participation and sustained trading activity. Unlike Phase 1, this phase is based on trading volume rather than profitability. Most of the rewards go to traders who build meaningful volume throughout the period, while a smaller portion rewards early momentum through the first-to-$10k pool.

#### Phase 2 Eligibility Requirements

* **Timeline:** March 5 - March 19, 2026
* **$10k milestone:** Reaching $10k in Phase 2 volume makes you eligible for the first-50 pool.
* **$50k milestone:** Reaching $50k in Phase 2 volume makes you eligible for the $4,000 shared pool.
* **Top-volume bonus:** The single highest-volume trader in Phase 2 earns the additional $500 prize.
* **Profitability:** You do not need to be profitable to qualify for Phase 2 rewards.

**Why this phase?** We want to encourage all trader types, from steady volume builders to high-frequency traders, to engage with Sai. The structure rewards meaningful participation over the full two weeks while still giving earlier active traders an extra way to earn.

### Phase 1: PNL Competition (Feb 18 - March 4)

**Reward Pool:** $20,000 | **Winners:** Top 25 Traders by Profit

Phase 1 rewards traders who demonstrate strong trading skill and strategy. Participants are ranked by **percentage profit-and-loss (ROI)**, which measures performance relative to starting capital, not absolute size. This means a 50% gain on a $500 account can outrank a 5% gain on a $50,000 account. Only closed positions count toward PNL (unrealized gains don't count until the trade is closed).

#### Prize Distribution (Phase 1)

| Rank       | Prize     |
| ---------- | --------- |
| Rank 1     | $6,250    |
| Rank 2     | $3,125    |
| Rank 3     | $1,250    |
| Rank 4-10  | $625 each |
| Rank 11-25 | $250 each |

#### Phase 1 Eligibility Requirements

To qualify for Phase 1 rewards:

| Rank       | Volume | Profit (PNL) |
| ---------- | ------ | ------------ |
| Rank 1-3   | $1M+   | $250         |
| Rank 4-10  | $50K+  | $50          |
| Rank 11-25 | $50K+  | None         |

* Values in the table represent **minimum thresholds**.
* Volume requirement = **cumulative trading volume** throughout the competition.

**Why evaluate percentage profit (a.k.a. ROI) for the competition?** This metric rewards trading skill and strategy over capital size. It encourages thoughtful risk management and disciplined decision-making, which is exactly what sustainable trading is built on. The use of percentage PNL means that the focus is on trading skill and strategy rather than sheer size of capital - a 50% gain on a small account (provided the small account achieves a minimum ROI of at least $100) ranks higher than a 5% gain on a large account. This phase encourages strategic trading, risk management, and profitable decision-making.

## What You Can Trade

**Eligible Markets & Collateral:** All markets on Sai are eligible for both phases. Trade any direction you believe in:

* **Go long or short** on any listed trading pair
* **Use any supported collateral:** USDC, stNIBI, or other platform-supported assets
* **All markets count:** Whether you're trading major pairs like BTC/USD and ETH/USD, or emerging assets, all volume and PNL contribute to competition standings

## How Leaderboards Work

Both PNL and volume leaderboards update regularly (daily or in real-time on the platform dashboard) so you can track your standing throughout the competition. Check the **Leaderboard** tab in the Sai app to monitor your progress. The competition leaderboards (for both PNL and Volume) will be updated regularly (e.g., daily or in real-time on the platform's dashboard) so participants can track their standings.

## Rules & Integrity

Sai's (Let's Go Saicho) competition will enforce rules against abusive practices and set minimum requirements for trades to qualify. To keep the competition fair and protect all participants, Sai enforces strict rules against abusive practices:

### What's Prohibited

* **Sybil attacks:** One account per person. Multiple accounts by the same individual are strictly forbidden. Any form of sybil attack (using multiple accounts by the same person) is strictly prohibited. Only genuine, unique user accounts will be eligible for prizes.
* **Wash trading:** Self-dealing and coordinated trades between accounts to inflate volume or PNL will result in immediate disqualification. No wash trading or fake volume generation is forbidden. Trades must reflect authentic market activity. Self-trades or coordinated trades between colluding accounts to inflate volume or PNL will result in immediate disqualification.
* **Fake volume:** Trades must reflect authentic market activity. Coordinated volume inflation is prohibited.
* **Malicious bots:** While algorithmic trading is permitted, bots that engage in prohibited tactics will have their trades voided. While algorithmic trading isn't outright banned, any malicious bot-driven manipulation of the competition is prohibited. Fair-use trading bots are allowed, but if they are detected to engage in the above forbidden tactics, those trades will be void.
* **Rapid round-trip trades:** Extremely fast open-close cycles solely to game volume metrics are not allowed.

### What's Allowed

* **Fair-use trading bots:** Algorithmic strategies are welcome, as long as they follow the spirit and letter of the rules.
* **Genuine volume:** All authentic, non-collusive trading counts toward your standings.

### Minimum Position Holding Time

For the volume phase (Phase 2), a minimum position holding time (e.g. at least 10-20 minutes) may be required for a trade to qualify. This discourages instantaneous round-trip trades purely to inflate volume. (No holding minimum is required in the PnL phase beyond what's needed to close a trade with profit/loss, but extremely rapid open-close cycles solely to game volume are not allowed.)

## Reward Distribution & Payouts

Reward distribution is complete and final.

* **Distribution Date:** March 27, 2026
* **Distribution Method:** Automatic direct transfer to eligible traders' connected wallets (no manual claim required)
* **Integrity Filtering:** Some accounts were removed for obvious wash trading or intentionally gamed low-activity behavior (for example, ultra short, repetitive trades with unrealistically small P\&L changes)
* **Onchain Transactions:**
  * [Phase 1 Rewards](https://safe.nibiru.fi/transactions/tx?safe=cataclysm-1:0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2\&id=multisig_0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2_0xdfb9bc786cb97bd4f358a07183f5c8ee711e2b358e18271b51438e1e855eb47d)
  * [Phase 2 Rewards](https://safe.nibiru.fi/transactions/tx?safe=cataclysm-1:0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2\&id=multisig_0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2_0xcc0108446ae59c6e96dbb51970ab6fda054c028db600288a4554ebc2830cfc3c)

Important Note: If there are less qualified participants than prizes available, then all qualifying participants shall earn a prize, and Sai's team reserves the right to distribute the prizes to the next most qualified participants, or not at all, as it sees fit. Thus, if an insufficient number of qualified people participate, it is possible that the entirety of the planned prize pools are not distributed. For example, if only a small number of participants qualify, the full prize pool will not be distributed. Prizes are intended to reward competitive participation, not guaranteed regardless of turnout.

## Eligibility & Legal Requirements

### Who Can Participate?

**How to Enter**: Any user of the Sai platform is automatically entered into the competition.

**Participant Eligibility:** The competition is open to all traders who are at least **18 years of age** and not residents of restricted jurisdictions. **US residents and those in sanctioned regions are NOT eligible** to participate. By entering, participants confirm they are abiding by all local laws and the platform's terms of use.

**Competition Duration**: The competition will start on 00:00 UTC and ends at 23:59 UTC on the dates prescribed.

* Phase 1: February 18th to March 4th 2026
* Phase 2: March 5th to March 19th 2026

By entering the competition, you confirm that:

* You are of legal age to participate
* No jurisdiction with authority over you prohibits your participation
* You understand and accept Sai's Terms of Use and Privacy Policy

### Risk Acknowledgment

**Participation is at User's Own Risk**: Trading, especially perps trading, is inherently risky. Your participation in this competition is at your own risk. You disclaim and release any claims from any legal action taken by or upon you through your use of the Sai platform. Sai is not responsible in any way for loss of assets or value. If you are not experienced with similar trading platforms, you should not participate. Even experienced users may lose all of their cryptocurrency assets or value.

You acknowledge that:

* Sai is not responsible for loss of assets or value
* You may lose your entire cryptocurrency holdings
* If you're inexperienced with perpetuals trading, you should not participate
* Even experienced traders can lose all assets

### Tax & Legal Responsibility

**You Are Responsible for Your Own Legal and Tax Situation:** You understand, acknowledge, and agree that you are responsible for any tax or legal issues that result in your participation in the competition. In participating, you represent that no jurisdiction with authority prohibits your participation.

You are solely responsible for:

* Any tax implications resulting from competition participation and prizes
* Compliance with local regulations in your jurisdiction
* Understanding the legal implications of receiving competition prizes

### Terms of Use and Privacy Policy

**Sai's Terms of Use and Privacy Policy Apply**: If you do not agree to Sai's Terms of Use and Privacy Policy, then you must not participate in the competition. Sections 21 to 25 of Sai's Terms of Use (INDEMNIFICATION, GOVERNING LAW & JURISDICTION, ARBITRATION; CLASS ARBITRATION WAIVER, LIMITATION ON TIME TO FILE CLAIMS, AND WAIVER & SEVERABILITY) specifically are incorporated by reference herein and apply to this competition.

Sai's Terms of Use apply fully to this competition, including sections 21-25 (Indemnification, Governing Law & Jurisdiction, Arbitration, Class Arbitration Waiver, Limitation on Time to File Claims, and Waiver & Severability).

### Agreement Supersession

**These Terms Supersede Promotional Statements:** These terms, along with the Sai Terms of Use and Privacy Policy, are the only agreement between us and you. Promotional statements by any party do not constitute an agreement.

## Sai's Authority

**Sai is Sole Decision Maker**: Sai reserves the right to disqualify any participant for not following the spirit or the letter of the rules of this competition. Sai reserves the sole and absolute right to disqualify any participant that it deems ineligible for participation for any reason at its sole discretion. This includes, but is not limited to, violating these rules, the terms and conditions of Sai and any other conduct that Sai considers inappropriate or unacceptable. All decisions of Sai shall be final.

**Integrity and Auditing**: Sai's team will closely monitor the competition. Any participant found engaging in prohibited behavior (sybil attacks, wash trading, etc.) will be immediately disqualified and forfeit any rewards. The organizers reserve the right to audit trading activity and adjust or remove suspicious entries to ensure fairness. This policy is essential to maintain a level playing field and uphold the integrity of the competition.

Sai reserves the absolute right to:

* Disqualify any participant for violating these rules or the spirit of fair competition
* Audit trading activity and remove suspicious entries
* Disqualify participants for any reason deemed appropriate at Sai's sole discretion
* Adjust or void suspicious trades to maintain integrity

All decisions made by Sai are final.

## Competition Timeline

| Date                | Event                                 |
| ------------------- | ------------------------------------- |
| February 18th, 2026 | Phase 1 Begins (PNL Competition)      |
| March 4th, 2026     | Phase 1 Ends                          |
| March 5th, 2026     | Phase 2 Begins (Volume-Based Rewards) |
| March 19th, 2026    | Phase 2 Ends                          |

## How to Get Started

1. **Visit Sai:** Head to [app.sai.fun](https://app.sai.fun)
2. **Connect Your Wallet:** Link your Web3 wallet (MetaMask, Phantom, or similar)
3. **Deposit Collateral:** Fund your account with USDC, stNIBI, or supported assets
4. **Start Trading:** Open positions on any market and begin accumulating volume or PNL
5. **Monitor Progress:** Check the leaderboard dashboard to track your standing in real-time

You're automatically entered once you make your first trade, so no separate registration needed.

## FAQ

{% tabs %}
{% tab title="Eligibility & Geography" %}
**Can I participate from the United States?**

No. US residents and those in sanctioned regions are not eligible to participate. Check our [eligibility section](#eligibility-and-legal-requirements) above for a full list of restrictions.

**Can I participate with multiple accounts?**

Absolutely not. One account per person. Multiple accounts by the same individual result in immediate disqualification and removal from all prize considerations.
{% endtab %}

{% tab title="Trading & Participation" %}
**Do I need to be profitable to earn rewards in Phase 2?**

No. Phase 2 rewards are based on trading volume, not profitability. You can lose money on trades and still qualify for rewards if you meet the volume thresholds. Phase 2 has three reward tracks: a $4,000 shared pool for traders who cross $50k in volume, a $1,000 pool split among the first 50 traders to reach $10k in volume, and a $500 bonus for the single highest-volume trader. Only the $10k pool is first come, first serve; most of Phase 2 rewards are based on total volume. Additionally, the volume must be from genuine trading activity, such as wash trading, self-dealing, or fake volume generation is strictly prohibited and will result in immediate disqualification.

**Can I use a trading bot?**

Yes, fair-use algorithmic trading is permitted. Bots are welcome as long as they don't engage in wash trading, sybil attacks, or other prohibited tactics. If your bot follows the rules, go ahead and automate your strategy.

**Do all markets count toward my volume and PNL?**

Yes. All markets on Sai are eligible for both phases. Trade BTC, ETH, emerging assets, and it all counts toward your standing.
{% endtab %}

{% tab title="Prizes & Payouts" %}
**When were prizes distributed?**

Payouts were distributed on **March 27, 2026** in two final transactions, one for each phase:

* [Phase 1 Rewards](https://safe.nibiru.fi/transactions/tx?safe=cataclysm-1:0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2\&id=multisig_0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2_0xdfb9bc786cb97bd4f358a07183f5c8ee711e2b358e18271b51438e1e855eb47d)
* [Phase 2 Rewards](https://safe.nibiru.fi/transactions/tx?safe=cataclysm-1:0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2\&id=multisig_0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2_0xcc0108446ae59c6e96dbb51970ab6fda054c028db600288a4554ebc2830cfc3c)

Rewards were sent directly to eligible traders' connected wallets, and no manual claim was required.

**What if not all 50 early-volume slots in Phase 2 are filled?**

Sai reserves the right to distribute remaining prizes to the next most qualified participants, or may choose not to distribute unused portions of the prize pool.

**Can I earn from multiple Phase 2 tiers at once?**

Yes. If you qualify for the early-volume pool and also cross the $50,000 volume threshold for the $4,000 shared pool, you earn from both. The top volume bonus also stacks on top of any other Phase 2 rewards.
{% endtab %}

{% tab title="Getting Started" %}
**How do I enter the competition?**

Just start trading! Visit [app.sai.fun](https://app.sai.fun), connect your wallet, deposit collateral, and make your first trade. You're automatically entered, so no separate registration needed.

**What collateral can I use?**

You can deposit USDC, stNIBI, or other platform-supported assets. Check the Sai app for the full list of supported collateral.

**How do I track my progress?**

Both PNL and volume leaderboards update regularly on the platform. Check the **Leaderboard** tab in the Sai app to monitor your standing in real-time throughout the competition.
{% endtab %}
{% endtabs %}

## Questions?

For more details on Sai's trading mechanics, collateral options, and market listings, explore the [Sai documentation](/).

Follow us for competition updates and real-time announcements:

* **X / Twitter:** [@SaiDotFun](https://x.com/SaiDotFun)
* **Telegram:** [Join our community](https://t.me/saidotfun)
* **Discord:** [Join our server](https://discord.com/invite/saidotfun)
* **TikTok:** [@SaiDotFun](https://www.tiktok.com/@saidotfun)
* **Instagram:** [@SaiDotFun](https://instagram.com/saidotfun)

***

**Ready to trade?** Head to [app.sai.fun](https://app.sai.fun)


# 04 - SaiClone Ambassador Program

The SaiClone Ambassador Program is a structured progression system that rewards active participation, content creation, and community engagement.

The SaiClone Ambassador Program is a structured progression system that rewards active participation, content creation, and community engagement. Your contributions are tracked and rewarded through three Tiers, each with unique benefits and recognition.

<figure><img src="/files/Ak7RcUzwcHMld6MJOLFz" alt="Program Structure: The Tiers of SaiClone"><figcaption><p>Program Structure: The Tiers of SaiClone</p></figcaption></figure>

## Program Structure: The Tiers of SaiClone

### 1. Saicho

Saicho is your entry point. You're automatically granted access upon engaging with the community. Starting at Level 0–5 and 0 – 1,624 XP.

### 2. Saiborg

Saiborg members are trusted, highly engaged members who actively protect and nurture Sai's ecosystem. Saiborgs receive special recognition in community announcements, shoutouts, and increased influence on community decisions. Starting at Level 6–15 and \~1,625 – 13,799 XP.

### 3. Sage

Sage is reserved for the best of the Sai community. Unlike other tiers, becoming a Sage requires achieving Level 16+ and submitting a formal application for review by the Sai team. Requires Level 16+ (\~13,800 XP) and a formal application.

Your application should demonstrate:

* Your vision for contributing to Sai's growth
* Examples of your best community contributions
* Your understanding of Sai's technology and mission
* Plans for ongoing community leadership

Sage benefits include exclusive bi-monthly raffles, direct team access, exclusive merchandise, collaboration opportunities (AMAs, content features), early access to platform updates, and invitations to Sage-only events and calls.

## How to Earn XP

The SaiClone Ambassador Program uses the Mee6 bot to automatically track and reward your community contributions. XP can be earned through various activities, both automatic and manual.

<figure><img src="/files/3yh5BMGaI2H0hqhgcgo8" alt="How to Earn XP on SaiClone"><figcaption><p>How to Earn XP on SaiClone</p></figcaption></figure>

## Exclusive Bi-monthly Sage Raffle

Sage members gain exclusive access to the bi-monthly Sage Raffle, a reward system for the most dedicated community leaders.

### Achievement Boosts

Sage members can increase their raffle chances through community participation. Your achievement tier is calculated bi-monthly based on engagement across multiple categories. You can achieve a tier by meeting the requirement in any single category. For example, sending 200+ messages alone qualifies you for the Bronze level.

<figure><img src="/files/zyw1NG9VPztll7BNEuTu" alt="Unlock Exclusive Bi-monthly Sage Rewards"><figcaption><p>Unlock Exclusive Bi-monthly Sage Rewards</p></figcaption></figure>

### How it works

* Meet the requirement in any single category to achieve that tier and its raffle multiplier. Example: 30 voice minutes alone qualifies you for Bronze (1.5x).
* Your tier is determined by your highest qualifying category. Example: 800 messages (Diamond) but only 15 content posts (Bronze) = Diamond tier.
* All metrics are tracked per calendar month and reset after each bi-monthly raffle.
* Content posts include educational posts/blogs, video content/shorts, and technical analysis on any social platform except X. Due to X's updated policy, we can no longer reward posts made there.
* Only Sage members are eligible for Achievement Boosts.

### Raffle Prizes

* Exclusive Sai merchandise bundle
* Premium subscription packages
* Featured spotlight in official Sai channels
* Digital collectables and badges
* Special community recognition
* Featured in bimonthly highlights
* ...and more

### Raffle Rules

* Must be an active Sage (Level 16+)
* 7 days to claim prize; unclaimed prizes roll over to the next raffle

## Maintaining Your SaiClone Status

### Inactivity Policy

* **Saicho & Saiborg:** Roles are never removed for inactivity. XP and Levels remain permanently. Returning members automatically regain their role based on their current Level.
* **Sage:** The Sage role is removed after 60 days of inactivity. XP and Levels are preserved, but you must reapply via a ticket to regain the role. Approval is not automatic.

**Activity definition:** Any XP-earning action (messaging, voice participation, content creation, events, bug reporting, or trading) at least once within 60 days. Any XP event resets the inactivity timer.

## General Guidelines

**Dos:**

* Engage authentically and thoughtfully
* Help newcomers learn about Sai
* Create original, valuable content
* Report suspicious activity
* Collaborate with other SaiClones
* Provide constructive feedback
* Represent Sai positively across platforms

**Don't:**

* Spam messages to farm XP
* Share misleading information
* Engage in price manipulation discussions
* Share unauthorised alpha or inside information
* Harass or discriminate against others
* Post NSFW or offensive content
* Promote competing projects maliciously

Any behaviour that violates the code of conduct will result in immediate action.

## Getting Started

1. **Join the Community:** [discord.com/invite/saidotfun](https://discord.com/invite/saidotfun)
2. **Start Engaging:** Participate in discussions, ask questions, and share your thoughts.
3. **Track Your Progress:** Use `!rank` in Discord to check your current level and XP.
4. **Create Value:** Share memes, create content, help others, and contribute meaningfully.
5. **Progress Through Tiers:** Consistent engagement propels you through the ranks.
6. **Apply for Sage:** Once you reach Level 16+, submit your application by opening a ticket.

## FAQ

{% tabs %}
{% tab title="Tiers & Progression" %}
**How long does it take to reach the Sage tier?**\
With consistent, high-quality engagement, dedicated members can reach Sage status in approximately 1 month. This requires daily participation, content creation, and active community involvement.

**Can I lose my Sage status?**\
Yes, through inactivity (60+ days) or code of conduct violations.

**Can I transfer Tiers/XP between accounts?**\
No. Tiers and XP are non-transferable and tied to your Discord account.
{% endtab %}

{% tab title="Content & XP" %}
**How is content quality assessed?**\
Moderators evaluate content based on originality, effort, accuracy, community value, and alignment with Sai's brand.

**What if I disagree with the XP awarded for my content?**\
You can respectfully discuss with moderators in #support. All decisions are final but made fairly.
{% endtab %}

{% tab title="Sage Raffle" %}
**When does the Sage raffle happen?**\
Every 2 months during the final week. Exclusively available to Sage members.
{% endtab %}

{% tab title="General" %}
**Are there any costs to participate?**\
No. The SaiClone Program is completely free. Never pay anyone claiming to sell Tiers or XP.
{% endtab %}
{% endtabs %}

***

Follow us for updates and real-time announcements:

* **X / Twitter:** [@SaiDotFun](https://x.com/SaiDotFun)
* **Telegram:** [Join our community](https://t.me/saidotfun)
* **TikTok:** [@SaiDotFun](https://www.tiktok.com/@saidotfun)
* **Instagram:** [@SaiDotFun](https://instagram.com/saidotfun)


# 05 - Let's Go Saicho: Rewards

Let's Go Saicho has officially concluded. Here's how rewards were finalized, who was eligible, and where to verify distribution.

**Let's Go Saicho has officially concluded.** Thank you to every trader who competed, built volume, and pushed the limits of what Sai's trading engine can handle. This document outlines how rewards were calculated, who was eligible, and how prizes were distributed.

**Competition Dates:** February 18 to March 19, 2026\
**Total Prize Pool:** $25,000\
**Distribution Method:** Automatic, no manual claim required

The final public record of reward distribution is captured in the payout transactions below.

## Final Reward Payouts

Reward distribution was completed on **March 27, 2026** in two final transactions, one for each competition phase.

* [Phase 1 Rewards](https://safe.nibiru.fi/transactions/tx?safe=cataclysm-1:0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2\&id=multisig_0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2_0xdfb9bc786cb97bd4f358a07183f5c8ee711e2b358e18271b51438e1e855eb47d), final Safe payout transaction for qualified Phase 1 recipients
* [Phase 2 Rewards](https://safe.nibiru.fi/transactions/tx?safe=cataclysm-1:0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2\&id=multisig_0x22CBd7CbF3b33681abB3Ced4D64d71acB9a9dCd2_0xcc0108446ae59c6e96dbb51970ab6fda054c028db600288a4554ebc2830cfc3c), final Safe payout transaction for qualified Phase 2 recipients

Phase 1 paid 22 qualified recipients and distributed 8,125 USDC. Phase 2 paid 47 qualified recipients and distributed 5,510 USDC, including 34 traders in the shared pool for crossing $50K in volume and 13 traders in the early-volume pool for being among the first to reach $10K in volume.

No manual claim was required. Rewards were distributed automatically in **USDC on Nibiru (ERC-20)**.

Final eligibility was determined after reviewing onchain payout data, trade- level records, how long positions were open, and competition source data from the CSV and JSON artifacts used to evaluate thresholds, trading activity, and integrity checks. This review included the underlying leaderboard and payout artifacts, plus trade-level fields used to confirm that rewarded activity was substantive and not artificial.

Recipients can verify payout via:

1. The two Safe payout transactions listed above, which are the canonical public payout record
2. Onchain transaction history on Nibiru
3. Sai dashboard records and leaderboard outcomes

> **Finality Note:** Distribution is complete and final as of March 27, 2026.

## Eligibility Requirements

To receive rewards, participants must meet **all** of the following criteria:

1. Appeared on the [Compete Leaderboard](https://app.sai.fun/leaderboard/) during the competition period
2. Met all minimum requirements (volume, PNL, and milestone thresholds) for their respective phase
3. Did not engage in prohibited conduct, including:
   * Wash trading
   * Sybil activity (multi-accounting)
   * Artificial volume manipulation
4. Passed internal audit checks conducted by the Sai team
5. Complied with Sai's [Terms of Service](/resources/legal/terms-of-use)

## Reward Structure

The reward tables below describe the campaign rules and payout criteria. The final payout transactions reflect the results after integrity review and the set of actually qualified recipients.

| Phase   | Category                           | Prize Pool  | Winners | Distribution Type                     |
| ------- | ---------------------------------- | ----------- | ------- | ------------------------------------- |
| Phase 1 | PNL (By % ROI and positive $ made) | 18,750 USDC | 25      | Ranked                                |
| Phase 2 | Volume (Be Early)                  | 6,250 USDC  | 50+     | First Come First Serve & Proportional |

### Phase 1: PNL Competition

**Reward Pool:** $18,750 USDC | **Focus:** Percentage ROI across closed positions

| Rank          | Prize         | Volume Requirement | Min PNL ($) |
| ------------- | ------------- | ------------------ | ----------- |
| Rank 1        | 6,250 USDC    | $1M+               | $100        |
| Rank 2        | 3,125 USDC    | $1M+               | $100        |
| Rank 3        | 1,250 USDC    | $1M+               | $100        |
| Rank 4 to 10  | 625 USDC each | $50K+              | $50         |
| Rank 11 to 25 | 250 USDC each | $50K+              | None        |

#### Phase 1 Rules

* Rankings are determined by **percentage PNL (ROI)**, not absolute dollar profit
* Only **closed positions** count toward PNL standings
* Participants must meet **both** the volume and minimum PNL thresholds to qualify for their rank tier

### Phase 2: Volume-Based Rewards

**Reward Pool:** $6,250 USDC | **Focus:** Volume-based rewards across three tracks

| Prize Pool | How to Qualify                                                                                                                                                        |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 4,750 USDC | Shared among all traders who cross $50K in Phase 2 volume. The more volume you trade, the larger your share of this pool. All Phase 1 activity counts toward Phase 2. |
| 1,000 USDC | Split evenly among the **first 50 traders** to reach $10K in Phase 2 volume.                                                                                          |
| 500 USDC   | Awarded to the **single trader** with the highest total volume in Phase 2.                                                                                            |

#### Phase 2 Rules

* Reach **$10K volume** → Eligible for the First Come First Serve pool (first 50 traders only)
* Reach **$50K+ volume** → Eligible for proportional share of the $4,750 pool
* Compete for **highest total volume** → Win the $500 bonus (stackable with other Phase 2 rewards)

## What the Competition Achieved

Let's Go Saicho rewarded both performance and participation, from high-skill traders generating strong PNL to early users actively building volume on Sai.

Across both phases, traders competed on:

* **Strategy and execution** in the PNL phase
* **Consistency and sustained activity** in the volume phase

The competition helped stress-test Sai's trading engine, onboard new users, and establish a foundation for future incentive programs.

## What's Next for Sai

Sai will continue to evolve with:

* More markets across crypto, equities, and commodities
* Mobile app
* Improved trading infrastructure and UX
* New earning opportunities through trading, liquidity, and integrations

Stay ready. Trade like a pro.

## FAQ

{% tabs %}
{% tab title="Eligibility & Disqualification" %}
**How do I know if I qualified?**

Eligibility was finalized after the competition and internal review. If your account met the published thresholds, passed integrity checks, and was included in the final reward payouts linked above, you were eligible for rewards.

**What could disqualify me?**

Engaging in wash trading, sybil activity (multi-accounting), or any form of artificial volume manipulation results in disqualification and forfeiture of all rewards. The Sai team audited activity and removed accounts that showed obvious gaming patterns, including ultra-short repetitive trades with unrealistically small P\&L changes that appeared designed to manipulate competition metrics. That review was based on a broader analysis of onchain data, trade-level records, position duration, and the competition CSV and JSON source data used to verify thresholds and filter out low-substance or manipulative behavior.
{% endtab %}

{% tab title="Rewards & Distribution" %}
**Do I need to do anything to claim my rewards?**

No. Rewards were distributed automatically. See **Final Reward Payouts** above for payout details and verification methods.

**When were rewards distributed?**

See **Final Reward Payouts** above for the completed payout transactions from March 27, 2026.

**What currency are rewards paid in?**

All prizes are paid in **USDC on Nibiru (ERC-20)**.

**Can I earn from multiple Phase 2 tiers at once?**

Yes. If you qualify for the early-volume pool and also cross the $50,000 volume threshold for the $4,750 shared pool, you earn from both. The top-volume bonus also stacks on top of any other Phase 2 rewards.
{% endtab %}

{% tab title="Phase Rules" %}
**Does Phase 1 volume count toward Phase 2?**

Yes. All trading activity from Phase 1 counts toward your Phase 2 volume total.

**How many traders ultimately qualified for Phase 2 rewards?**

Phase 2 paid 47 qualified recipients. Of those, 34 traders qualified for the shared pool for crossing $50K in volume, and 13 traders qualified for the early-volume pool for being among the first to reach $10K in volume.

**What if I'm not in the top 50 for Phase 2's First Come First Serve pool?**

If you crossed $50K in volume, you're still eligible for a proportional share of the $4,750 pool, and if you had the highest total volume overall, you're eligible for the $500 top-volume bonus.
{% endtab %}
{% endtabs %}

## Questions?

For more details on Sai's trading mechanics, collateral options, and market listings, explore the [Sai documentation](/).

Follow us for future competition announcements and updates:

* **X / Twitter:** [@SaiDotFun](https://x.com/SaiDotFun)
* **Telegram:** [Join our community](https://t.me/saidotfun)
* **Discord:** [Join our server](https://discord.com/invite/saidotfun)
* **TikTok:** [@SaiDotFun](https://www.tiktok.com/@saidotfun)
* **Instagram:** [@SaiDotFun](https://instagram.com/saidotfun)

***

**Ready for the next one?** Head to [app.sai.fun](https://app.sai.fun)


# 06 - Sai Launches Stock Trading on Perps

Sai expands beyond crypto. Trade popular stocks long or short through perpetual markets — fully onchain, no gas fees, no custody.

**Sai is expanding beyond crypto.** Users can now long or short popular stocks through perpetual markets without owning the underlying asset. Same fast, gasless, CEX-like experience — fully onchain.

Trade equities with leverage, hedge your portfolio, or express a view on traditional markets, all from the same platform you already use for crypto perps.

## Why Stock Perps on Sai

Sai brings the same infrastructure behind its crypto perps into traditional markets, unlocking a new category of onchain trading.

* **Access global markets** — trade equities alongside crypto from a single interface
* **Leveraged long and short exposure** to popular stocks
* **Oracle-based pricing** for fair, transparent execution
* **Unified collateral** across crypto and stock markets
* **No custody, no gas fees, minimal slippage** — the same onchain UX you expect from Sai
* **Built for everyone** — designed for both retail and advanced traders

## Rolling Market Launch

Stock markets are launching on a **rolling basis**. Nvidia and Brent Oil are tradable now, with more major equities being added regularly.

As new markets go live, they'll appear directly in the Sai trading interface — no migration, no setup. Just connect and trade.

## How It Compares

Most perp DEXs stop at crypto. Sai gives traders access to equities and crypto from a single platform with shared collateral.

No need to jump between apps for crypto and equities. One interface, every market.

| Feature         | Typical Perp DEX | Sai |
| --------------- | ---------------- | --- |
| Crypto Perps    | ✅                | ✅   |
| Stock Perps     | ❌                | ✅   |
| Gasless Trading | Varies           | ✅   |
| Fully Onchain   | Varies           | ✅   |

## What's Next

This is just the beginning. Sai will continue expanding market coverage across equities, commodities, and more — all accessible from the same unified trading experience.

## Get Started

Stock perps are live. Head to [app.sai.fun](https://app.sai.fun) and start trading.

***

Follow us for new market launches and updates:

* **X / Twitter:** [@SaiDotFun](https://x.com/SaiDotFun)
* **YouTube:** [@saidotfun](https://www.youtube.com/@saidotfun)
* **Telegram:** [Join our community](https://t.me/saidotfun)
* **Discord:** [Join our server](https://discord.com/invite/saidotfun)
* **TikTok:** [@SaiDotFun](https://www.tiktok.com/@saidotfun)
* **Instagram:** [@SaiDotFun](https://instagram.com/saidotfun)

***

**Ready to trade stocks onchain?** Head to [app.sai.fun](https://app.sai.fun)


# 07 - Sai Cookout Trading Competition

Sai Cookout: Sai Trading Competition with $5,100 Prize Pool Across Crypto, Stocks, and Commodities

Sai is back with a new trading competition. More markets and ways to win.

This month-long competition rewards traders across all live perps markets on Sai, from crypto majors to newly launched stock and commodity markets. Whether you trade one asset or a wider basket, there's ways to climb the leaderboard.

## Overview

The Menu features a wide range of markets for users to choose from, spanning perps on blue-chip crypto assets, commodities, and equities. Additional markets will roll out throughout the competition.

* **Total Prize Pool:** $5,100
* **Timeline:** April 22nd → May 20th (\~4 weeks)
* **Structure:** $4,000 Main Leaderboard + $1,000 Stock Side Pot + $100 Biggest Loser

## Main Event - Cook Out

Trade anything. Perps on crypto, stocks, commodities. All markets count.

The Main Event rewards the top traders based on cumulative realized profits across every market on Sai. There are no restrictions. Focus on what you trade best and scale from there.

**Prize Pool:** Up to $4,000

### Top 25 Distribution

| Rank      | Title         | Per Trader | Tier Total | % of Pool |
| --------- | ------------- | ---------- | ---------- | --------- |
| 1st       | Head Chef     | $800       |            | 20.0%     |
| 2nd       | Sous Chef     | $500       |            | 12.5%     |
| 3rd       | Pastry Chef   | $350       |            | 8.75%     |
| 4th       | Saucier       | $250       |            | 6.25%     |
| 5th       | Grill Master  | $200       |            | 5.0%      |
| **Top 5** |               |            | **$2,100** | **52.5%** |
| 6th–10th  | Line Cooks    | $140 each  | $700       | 17.5%     |
| 11th–15th | Prep Cooks    | $100 each  | $500       | 12.5%     |
| 16th–20th | Dishwashers   | $80 each   | $400       | 10.0%     |
| 21st–25th | Taste Testers | $60 each   | $300       | 7.5%      |
| **Total** |               |            | **$4,000** | **100%**  |

## The Side Pot - Perps Stock Markets

The Side Pot runs in parallel to the main leaderboard and rewards traders who generate profits specifically on perps for stocks. Perps Stock PnL counts toward both leaderboards.

**Timeline:** April 22nd → May 20th (\~4 weeks)\
**Prize Pool:** Up to $1,000

### Top 10 Distribution

| Rank      | Title             | Per Trader | Tier Total | % of Pool |
| --------- | ----------------- | ---------- | ---------- | --------- |
| 1st       | Iron Chef         | $250       |            | 25.0%     |
| 2nd       | Wok Star          | $162       |            | 16.2%     |
| 3rd       | Hibachi King      | $113       |            | 11.3%     |
| 4th–5th   | Stock Cook        | $75 each   | $150       | 15.0%     |
| **Top 5** |                   |            | **$675**   | **67.5%** |
| 6th–10th  | Short Order Cooks | $65 each   | $325       | 32.5%     |
| **Total** |                   |            | **$1,000** | **100%**  |

## How Scoring Works

The competition is based on **realized profits**, not net PnL.

* **(E.g):** If you make $1,000 and later lose $500, your score still references the $1,000 PnL
* **(E.g):** If another trader makes $500 total, they rank below you

This structure rewards traders who can generate strong upside without punishing continued participation.

Your score can only go up over time.

## Qualification Requirements

Traders must meet minimum activity and performance thresholds to qualify:

| Requirement              | Main Leaderboard    | Stock Side Pot     |
| ------------------------ | ------------------- | ------------------ |
| Minimum deposited margin | $100                | $100               |
| Minimum notional volume  | $100K (all markets) | $50K (stocks only) |
| Minimum realized PnL     | > $50               | > $50              |

Users can be eligible for prizes from each leaderboard. If you trade stock, RWAs/commodity markets, that PnL will also count for the Cook Out leaderboard.

### Minimum PnL Threshold

To qualify for any leaderboard prize, a trader must hit a **minimum of greater than $50 in realized PnL** (peak cumulative realized profit, as described above). Traders below this threshold will not appear on the leaderboards regardless of rank.

**If there aren't enough qualified participants to fill every prize slot**, the distribution scales down and rewards are paid out based on the same percentage breakdown across the qualifying field. Sai's team reserves the right to distribute remaining prizes to the next most qualified participants, or not at all, as it sees fit. If an insufficient number of qualified people participate, it is possible that the entirety of the planned prize pools are not distributed.

### Minimum Holding Time

To discourage instantaneous round-trip trades purely to inflate volume, a minimum position holding time may be required for a trade to count toward volume qualification. Trades that fail to meet this threshold will be excluded from the leaderboard volume calculation.

### How Perps Markets Are Counted

* **Stock PnL**
  * Counts toward both the main leaderboard and the stock side pot
* **Crypto PnL**
  * Counts toward the main leaderboard only
* **Main leaderboard score**
  * Total realized profits across all markets

## The Biggest Loser - Rekt

**Prize:** $100

At the end of the competition, the trader with the largest **net realized loss** takes home a consolation prize and the title of rekt.

### Scoring

The Biggest Loser is scored **separately** from the Main Event and Side Pot. Unlike the profit leaderboards (which reference peak cumulative realized profit), the Biggest Loser is based on **net negative realized PnL** over the full competition window. A trader who profited $1,000 early and then lost $2,000 has a net PnL of -$1,000 for this track.

This means Biggest Loser eligibility is entirely independent of whether a trader scored on the main leaderboards. If you posted a profit high at some point but finished net negative, only your final net loss counts here.

### Rules

* Must be the single largest net realized loser
* Minimum 10 round-trip trades
* No single trade blow-ups — losses must come from sustained trading activity, not a single outsized trade designed to game the consolation prize
* Winner will be publicly named

## Trade on Sai

Trade consistently. Scale what works. Crypto, stocks, or both. It all counts.

Head to [app.sai.fun](https://app.sai.fun), connect your wallet, deposit collateral, and start trading. You're automatically entered once you make your first trade.

## Rules and Eligibility Criteria

Sai’s Cookout competition will enforce rules against abusive practices and set minimum requirements for trades to qualify:

* **How to Enter**: Any user of the SAI platform is automatically entered into the competition.
* **Participant Eligibility:** The competition is open to all traders who are at least 18 years of age and not residents of restricted jurisdictions (as set forth in Sai’s Terms of Use: <https://docs.sai.fun/resources/legal/terms-of-use>. Traders from the United States or other sanctioned regions are NOT eligible to participate. By entering, participants confirm they are abiding by all local laws and the platform’s terms of use.
* **Competition Duration**: The competition will start on 00:00 UTC and ends at 23:59 UTC on the dates prescribed.
* **Unique Accounts Only:** Each individual may participate with one account. Any form of sybil attack (using multiple accounts by the same person) is strictly prohibited. Only genuine, unique user accounts will be eligible for prizes.
* **No Wash Trading or Fake Volume:** Wash trading, self-dealing, or any fake volume generation is forbidden. Trades must reflect authentic market activity. Self-trades or coordinated trades between colluding accounts to inflate volume or PNL will result in immediate disqualification. The platform will monitor trade patterns and reserves the right to disqualify and/or ban any accounts showing abusive behavior.
* **No Bot Manipulation:** While algorithmic trading isn’t outright banned, any malicious bot-driven manipulation of the competition is prohibited. Fair-use trading bots are allowed, but if they are detected to engage in the above forbidden tactics, those trades will be void.
* **Minimum Holding Time:** For the volume minimum, a minimum position holding time (e.g. at least 10-20 minutes) may be required for a trade to qualify. This discourages instantaneous round-trip trades purely to inflate volume.
* **Minimum PNL**: For PNL competitions, the minimum profit that must be achieved to qualify to be a winner is $50.
* **Leaderboard Updates:** The competition leaderboards will be updated regularly (e.g., daily or in real-time on the platform’s dashboard) so participants can track their standings.
* **Reward Distribution:** Winners will be announced after the trading competition closes. At that time, instructions will be provided on how to receive rewards. Prize payouts will be distributed within a reasonable timeframe **after the competition closes.** If there are less qualified participants than prizes available as determined in the Sai’s team’s sole discretion, then all qualifying participants shall earn a prize, and Sai’s team reserves the right to distribute the prizes to the next most qualified participants, or not at all, as it sees fit. Thus, if an insufficient number of qualified people participate, it is possible that the entirety of the planned prize pools are not distributed.
* **Integrity and Auditing**: Sai’s team will closely monitor the competition. Any participant found engaging in prohibited behavior (sybil attacks, wash trading, etc.) will be immediately disqualified and forfeit any rewards. The organizers reserve the right to audit trading activity and adjust or remove suspicious entries to ensure fairness. This policy is essential to maintain a level playing field and uphold the integrity of the competition.
* **Participation is at User’s Own Risk**: Trading, especially perps trading, is inherently risky. Your participation in this competition is at your own risk. You disclaim and release any claims from any legal action taken by or upon you through your use of the Sai platform. Sai is not responsible in any way for loss of assets or value. If you are not experienced with similar trading platforms, you should not participate. Even experienced users may lose all of their cryptocurrency assets or value.
* **Sai is Sole Decision Maker**: Sai reserves the right to disqualify any participant for not following the spirit or the letter of the rules of this competition. Sai reserves the sole and absolute right to disqualify any participant that it deems ineligible for participation for any reason at its sole discretion. This includes, but is not limited to, violating these rules, the terms and conditions of Sai and any other conduct that Sai consideres inappropriate or unacceptable. All decisions of Sai shall be final.
* **You Are Responsible for Your Own Legal and Tax Situation:** You understand, acknowledge, and agree that you are responsible for any tax or legal issues that result in your participation in the competition. In participating, you represent that no jurisdiction with authority prohibits your participation.
* **Sai’s Terms of Use and Privacy Policy Apply**: If you do not agree to Sai’s Terms of Use and Privacy Policy, then you must not participate in the competition. Sections 21 to 25 of SAI’s Terms of Use (INDEMNIFICATION, GOVERNING LAW & JURISDICTION, ARBITRATION; CLASS ARBITRATION WAIVER, LIMITATION ON TIME TO FILE CLAIMS, AND WAIVER & SEVERABILITY) specifically are incorporated by reference herein and apply to this competition.
* **These Terms Supersede Promotional Statements:** These terms, along with the Sai Terms of Use and Privacy Policy, are the only agreement between us and you. Promotional statements by any party do not constitute an agreement.

## FAQ

{% tabs %}
{% tab title="Eligibility & Geography" %}
**Can I participate from the United States?**

No. US residents and those in sanctioned regions are not eligible to participate. Check our [eligibility section](#eligibility-and-legal-requirements) above for a full list of restrictions.

**Can I participate with multiple accounts?**

Absolutely not. One account per person. Multiple accounts by the same individual result in immediate disqualification and removal from all prize considerations.
{% endtab %}

{% tab title="Trading & Participation" %}
**Do my stock trades count toward both leaderboards?**

Yes. Perps Stock PnL counts toward both the Main Event leaderboard and the Stock Side Pot. If you focus on stocks, you're automatically competing on both tracks.

**What's the minimum PnL to earn rewards?**

You need **more than $50 in realized PnL** (peak cumulative realized profit) to qualify for either the Main Event or the Stock Side Pot. Traders at or below that threshold won't appear on the leaderboards. The Biggest Loser track is the only one that rewards losses — it goes to the trader with the most net negative realized PnL, and requires at least 10 round-trip trades.

**How is the Biggest Loser scored differently from the main leaderboards?**

The profit leaderboards use peak cumulative realized profit — your score references your highest high and never goes down. The Biggest Loser track is completely separate and uses **net negative realized PnL** over the full competition window. So a trader who profited $1,000 and then lost $2,000 would have a Main Event score frozen at $1,000 (if they qualified) and a Biggest Loser score of -$1,000. The prize goes to the single user with the largest net realized losses, provided they completed at least 10 round-trip trades.

**Can I use a trading bot?**

Yes, fair-use algorithmic trading is permitted. Bots are welcome as long as they don't engage in wash trading, sybil attacks, or other prohibited tactics.

**Do all markets count toward the Main Event?**

Yes. All markets on Sai are eligible for the Main Event, including crypto majors, stock perps, and commodities. Only perps stock PnL counts toward the Side Pot.

**How does realized PnL scoring work?**

Your score only references your peak cumulative realized profit. If you make $1,000 and later give back $500, your leaderboard score still references the $1,000 high. This encourages continued participation without penalty.
{% endtab %}

{% tab title="Prizes & Payouts" %}
**When will prizes be distributed?**

Winners will be announced after the competition closes, and instructions for receiving rewards will be provided at that time. Payouts will be distributed within a reasonable timeframe after the competition closes.

**Can I earn from multiple tracks at once?**

Yes. If you qualify for the Main Event and the Stock Side Pot, you earn from both. Stock PnL counts toward both leaderboards simultaneously.

**What if not all prize slots are filled?**

If there aren't enough qualified participants, the distribution scales down and rewards pay out based on the same percentage breakdown across the qualifying field. Sai reserves the right to distribute remaining prizes to the next most qualified participants, or not at all, as it sees fit. It is possible that the entirety of the planned prize pools are not distributed if turnout is insufficient.

**What are the minimum qualification thresholds?**

For the Main Event: $100 minimum deposited margin, $100K minimum notional volume across all markets, and more than $50 in realized PnL. For the Stock Side Pot: $100 minimum deposited margin, $50K minimum notional volume on stocks only, and more than $50 in realized PnL.
{% endtab %}

{% tab title="Getting Started" %}
**How do I enter the competition?**

Just start trading! Visit [app.sai.fun](https://app.sai.fun), connect your wallet, deposit collateral, and make your first trade. You're automatically entered, so no separate registration needed.

**What collateral can I use?**

You can deposit USDC, stNIBI, or other platform-supported assets. Check the Sai app for the full list of supported collateral.

**How do I track my progress?**

Both the Main Event and Stock Side Pot leaderboards update regularly on the platform. Check the **Leaderboard** tab in the Sai app to monitor your standing in real-time throughout the competition.

**What markets should I trade?**

Trade what you know best. The Main Event rewards realized profits across any market, so crypto specialists, equity traders, and commodity traders all have a path to the top. Stock traders get the added bonus of dual-leaderboard eligibility.
{% endtab %}
{% endtabs %}

## Questions?

For more details on Sai's trading mechanics, collateral options, and market listings, explore the [Sai documentation](/).

Follow us for competition updates and real-time announcements:

* **X / Twitter:** [@SaiDotFun](https://x.com/SaiDotFun)
* **Telegram:** [Join our community](https://t.me/saidotfun)
* **Discord:** [Join our server](https://discord.com/invite/saidotfun)
* **TikTok:** [@SaiDotFun](https://www.tiktok.com/@saidotfun)
* **Instagram:** [@SaiDotFun](https://instagram.com/saidotfun)

***

**Ready to trade?** Head to [app.sai.fun](https://app.sai.fun)


# 08 - Sai Grand Cup 2026: Trade for Your Region

Climb the Leaderboard. Split the Pool.

Sai Grand Cup is a month-long, team-based trading competition running alongside this summer's biggest football tournament. Instead of trading solo, you're assigned a region based on your trading history, and your activity contributes to that continent's position on the leaderboard. Traders are assigned to a country automatically. When the competition ends, the top continents split a team prize pool, and the best individual traders earn separate rewards.

If you already trade on Sai, this is a way to put your trading to work. If you're new, it's the easiest time to start: every qualifying trade also earns Sai Points.

### Timeline

<figure><img src="/files/wnUXoeCYqzAhUqnpokqL" alt=""><figcaption></figcaption></figure>

The competition runs June 25 – July 19, 2026. Each day starts at 00:00 UTC and ends at 23:59 UTC on the dates prescribed. Winners are announced after the leaderboard closes, sometime after July 19th. Your continent is assigned based on your historical trading volume and is locked for the duration of the competition.

### How the Competition Works

Region rankings are determined by a combination of realized PnL, trading volume, and active traders. Profitable trading, sustained activity, and team participation all matter.

**Worked example:**

| Region        | **Realized PnL** | **Volume** | **Active Traders** | **Result** |
| ------------- | ---------------- | ---------- | ------------------ | ---------- |
| Asia          | $50,000          | $5,000,000 | 15                 | 1st        |
| Europe        | $55,000          | $2,000,000 | 8                  | 2nd        |
| South America | $40,000          | $6,000,000 | 20                 | 3rd        |

Asia brought the highest PnL, but Europe won on a stronger balance of profitability, volume, and participation.

***Tie-breakers, in order:** higher total realized PnL, then higher total volume, then more qualifying traders.*

### Prize Pool: $5,000 USDC credits

Rewards split into a region and an individual pool.

**Region prizes: $3,500**\
Split among a continent's qualifying traders, weighted by each trader's contribution to the region’s final score.

<figure><img src="/files/Lsn9tGWiLGLgnLCvO6oN" alt=""><figcaption></figcaption></figure>

**Individual prizes: $1,500**\
Awarded to the top individual traders across the full competition, regardless of region.

<figure><img src="/files/dmxSHfYzbXj0JaG6FAn7" alt=""><figcaption></figcaption></figure>

Rewards are paid in USDC credits on the Sai platform (non-withdrawable until a certain trading-activity threshold is reached). Final rewards may be adjusted based on eligibility, wash-trading checks, and competition rules.

### How to qualify

To count toward your continent's score and be eligible for prizes:

* Connect your wallet to Sai’s Trading App
* Go to “Compete” | <https://app.sai.fun/leaderboard/>

<figure><img src="/files/YU6jbxZMjoR9jAKu2VVy" alt=""><figcaption></figcaption></figure>

* New to Sai? Deposit at least $100 in margin to get started
* If you're an existing Sai user and have traded before (at least $100 in margin), you're eligible to participate.

That's it to enter. From there, your trading volume and realized PnL feed your region’s score for the rest of the competition.

### Grand Cup Stacks with Sai Points

The Grand Cup runs on top of the [Sai Points Program](https://app.sai.fun/?ref=POINTS). Every qualifying trade during the Cup still earns Points across all the usual activities, plus Cup-specific boosts:

* Boosted points on all qualifying trades during the event window
* Football-themed weekly bounties slotted into the regular bounty cadence
* A loyalty booster for traders active across all weeks of the Cup

So a single trade during the Cup can move your continent up the leaderboard and grow your personal Points total at the same time.

More details on Sai Points will be provided soon.

### Rules and eligibility

* **How to enter:** Any Sai user who trades with under a region.
* **Eligibility:** open to traders 18 or older who are not residents of restricted jurisdictions, as set out in Sai's [Terms of Use](https://docs.sai.fun/resources/legal/terms-of-use). Traders in the United States or other sanctioned regions are **not** eligible. By entering, you confirm you are abiding by all local laws and the platform's terms.
* **Competition duration:** the competition starts at 00:00 UTC and ends at 23:59 UTC on the prescribed dates.
* **Unique accounts only:** one account per person. Any form of sybil attack (multiple accounts by the same person) is prohibited. Each participating account must be funded from a unique source; funding multiple wallets to gain an unfair advantage results in disqualification.
* **No wash trading or fake volume:** wash trading, self-dealing, and fake volume generation are forbidden. Self-trades or coordinated trades between colluding accounts to inflate volume or PnL result in immediate disqualification. Sai monitors trade patterns and reserves the right to disqualify or ban abusive accounts.
* **No bot manipulation:** fair-use trading bots are allowed, but malicious bot-driven manipulation is prohibited and offending trades will be voided.
* **Minimum holding time:** a minimum position hold time may be required for a trade to count toward volume. Positions held open below that time may not count.
* **Leaderboard updates:** leaderboards update regularly so you can track your country's standing.
* **Minimum activity threshold:** if the competition does not reach a minimum level of activity, as determined by the Sai team, Sai reserves the right not to distribute some or all of the prizes. If there are fewer qualifying participants than prizes available, all qualifying participants may earn a prize and Sai may redistribute or withhold remaining prizes at its discretion — meaning the full planned pool may not be distributed.
* **Reward distribution:** winners are announced after the competition closes, with instructions on how to receive rewards. Payouts are distributed within a reasonable timeframe.
* **Integrity and auditing:** Sai monitors the competition and may audit activity, disqualify participants, and adjust or remove suspicious entries to ensure a level playing field. All decisions are final.
* **Participation is at your own risk:** trading perps is inherently risky. You participate at your own risk and release Sai from any claims. Sai is not responsible for any loss of assets or value. Even experienced traders can lose their entire balance. If you're not experienced with platforms like this, you should not participate.
* **Your own legal and tax situation:** you are responsible for any tax or legal issues arising from participation, and represent that no jurisdiction with authority prohibits it.
* **Terms apply:** Sai's [Terms of Use](https://docs.sai.fun/resources/legal/terms-of-use) and Privacy Policy govern your participation, including Sections 21–25 (indemnification, governing law, arbitration and class-arbitration waiver, time to file claims, and waiver & severability). These terms supersede any promotional statements.

***

*Get in:* [*app.sai.fun*](http://app.sai.fun)*. Every trade counts toward your team — and your Sai Points.*


# 09 - Sai Points: Get Rewarded Every Time You Trade

Trade. Earn Points. Rise Through the Rankings.

Sai Points rewards you for [trading on Sai](https://app.sai.fun/?ref=POINTS) and contributing to the community. Every qualifying trade, completed bounty, and referral contributes to your points. The more you trade, the more you earn.

If you already trade on Sai, this is how your activity starts working for you. If you're new, there's no better time to start.

### Timeline

Activity, streaks, and decay are tracked seasonally. The points pool settles each season; you start earning the moment you trade.

### How it works

Points are cumulative, per season. Every activity feeds one running total, and the points pool is distributed by your share of total points on the leaderboard. Your season points are divided by all traders' points combined.

<figure><img src="/files/CcIRteK9Iluui8U10iGC" alt=""><figcaption></figcaption></figure>

### Points Pool

Each season, the rewards pool is distributed pro-rata by your share of total points. Your share equals your season points divided by all traders' season points combined.

Specific weighting is set by the Sai team and may adjust at any time to account for new market launches, campaigns, or anti-sybil measures.

### Decay

Stay active to protect your points. After two weeks without a $10K+ volume trade, which you can hit in a single trade or across several smaller trades in the same week, your accumulated points decay by 10% each week. Complete a $10K+ volume trade at any time to stop and reset the decay.

<figure><img src="/files/HlqAtaLmL47K3fLRF9AN" alt=""><figcaption></figcaption></figure>

Once points have decayed, they are forfeited permanently.

#### Bounties

Bounties are time-based missions that reward specific trading activity. New bounties drop regularly with a set expiration. Completing a bounty earns a fixed amount of points.

Weekly bounties drop each week at random times and expire one week after going live.

Examples:

* First trader to hit $XYZ PnL on any market
* First 10 traders to clear $XYZ volume
* Top 10 traders by PnL for the week
* First person to trade a newly listed market

Flash bounties can drop any hour, any day. Watch Sai's X or Discord to catch them.

Examples:

* Trade BTC markets in the next hour
* New market launch: first 50 traders to open a position receive a boosted multiplier

### **Referrals**

Every trader who signs up with your referral code earns you a cut of their points. There is no cap on how many people you can refer. Referees keep 100% of their own points.

**How the ongoing referral works:** the 10% monthly bonus activates only in months where your referred wallet hits $5,000 in cumulative volume. Months where the referee trades below $5K contribute no ongoing bonus; it resets each calendar month. The one-time 1,000 pt bonus for a referee's first $1K trade always pays out regardless.

**Referral points cap:** total points earned from referrals (one-time bonuses and ongoing monthly share combined) are capped at 50,000 points per wallet per season across all referrals.

Note: Third-party tools and copy trading: using third-party trading tools, strategy automation, or copy trading platforms is permitted, provided no wash trading occurs. Wash trading is defined as being the controlling party on both sides of a trade, directly or through coordinated accounts. Wallets clustered to the same funding source are flagged and excluded (so wallet farming does not work)

### Ready to trade?

Sai Points is live. Every trade you make from this moment counts toward your season total. The leaderboard is open, bounties are dropping, and the top 5 traders at season close take home the biggest share.

Your points start the moment you open your first position.

[**Get started today!**](https://app.sai.fun/?ref=POINTS)&#x20;

🔥 New to Sai? Use code **POINTS** for a trading fee discount.

<details>

<summary>Rules and Eligibility Criteria</summary>

The competition will enforce rules against abusive practices and set minimum requirements for trades to qualify:

* **How to Enter**: Any user of the SAI platform is automatically eligible to receive Sai Points based on the points criteria.
* **Participant Eligibility:** The points program is open to all traders who are at least 18 years of age and not residents of restricted jurisdictions (as set forth in Sai’s Terms of Use: <https://docs.sai.fun/resources/legal/terms-of-use>. Traders from the United States or other sanctioned regions are NOT eligible to participate. By entering, participants confirm they are abiding by all local laws and the platform’s terms of use.
* **Unique Accounts Only:** Each individual may participate with one account. Any form of sybil attack (using multiple accounts by the same person) is strictly prohibited. Only genuine, unique user accounts will be eligible for prizes.
* **No Wash Trading or Fake Volume:** Wash trading, self-dealing, or any fake volume generation is forbidden. Trades must reflect authentic market activity. Self-trades or coordinated trades between colluding accounts to inflate volume or PNL will result in immediate disqualification. The platform will monitor trade patterns and reserves the right to disqualify and/or ban any accounts showing abusive behavior.
* **No Bot Manipulation:** While algorithmic trading isn’t outright banned, any malicious bot-driven manipulation of the points program is prohibited. Fair-use trading bots are allowed, but if they are detected to engage in the above forbidden tactics, those trades will be void.
* **Minimum Holding Time:** For any volume criteria, a minimum position holding time (e.g. at least 2-4 minutes) may be required for a trade to qualify. Sai’s team reserves the right to not count any positions held open less than this time. This discourages instantaneous round-trip trades purely to inflate volume.
* **Leaderboard Updates:** The points leaderboards will be updated regularly (e.g., daily or in real-time on the platform’s dashboard) so participants can track their standings.
* **Reward Distribution:** Should any rewards for points be distributed at the discretion of the Sai team, at that time, instructions will be provided on how to receive rewards. Rewards will be distributed within a reasonable timeframe\*\*.\*\* The Sai team reserves the right to not distribute any rewards or to distribute rewards periodically or at irregular intervals.
* **Integrity and Auditing**: Sai’s team will closely monitor the points program. Any participant found engaging in prohibited behavior (sybil attacks, wash trading, etc.) will be immediately disqualified and forfeit any rewards. The organizers reserve the right to audit trading activity and adjust or remove suspicious entries to ensure fairness. This policy is essential to maintain a level playing field and uphold the integrity of the competition.
* **Participation is at User’s Own Risk**: Trading, especially perps trading, is inherently risky. Your participation in this competition is at your own risk. You disclaim and release any claims from any legal action taken by or upon you through your use of the Sai platform. Sai is not responsible in any way for loss of assets or value. If you are not experienced with similar trading platforms, you should not participate. Even experienced users may lose all of their cryptocurrency assets or value.
* **Sai is Sole Decision Maker**: Sai reserves the right to disqualify any participant for not following the spirit or the letter of the rules of this program. Sai reserves the sole and absolute right to disqualify any participant that it deems ineligible for participation for any reason at its sole discretion. This includes, but is not limited to, violating these rules, the terms and conditions of Sai and any other conduct that Sai considers inappropriate or unacceptable. All decisions of Sai shall be final.
* **You Are Responsible for Your Own Legal and Tax Situation:** You understand, acknowledge, and agree that you are responsible for any tax or legal issues that result in your participation in the program. In participating, you represent that no jurisdiction with authority prohibits your participation.
* **Sai’s Terms of Use and Privacy Policy Apply**: If you do not agree to Sai’s Terms of Use and Privacy Policy, then you must not participate in the competition. Sections 21 to 25 of SAI’s Terms of Use (INDEMNIFICATION, GOVERNING LAW & JURISDICTION, ARBITRATION; CLASS ARBITRATION WAIVER, LIMITATION ON TIME TO FILE CLAIMS, AND WAIVER & SEVERABILITY) specifically are incorporated by reference herein and apply to this competition.
* **These Terms Supersede Promotional Statements:** These terms, along with the Sai Terms of Use and Privacy Policy, are the only agreement between us and you. Promotional statements by any party do not constitute an agreement.

</details>


# Legal

Legal and regulatory documents for Sai and Sai.fun.

This section contains important legal documents, terms of service, and regulatory information for Sai and Sai.fun. Please review these documents carefully before using our services.

## Documents

{% content-ref url="/pages/9RQeGvoH0S9OrXBSUuYN" %}
[Terms of Use](/resources/legal/terms-of-use)
{% endcontent-ref %}

{% content-ref url="/pages/wok5e4UCMbXb0BXOPycz" %}
[Sai Disclosures](/resources/legal/disclosures)
{% endcontent-ref %}


# Terms of Use

This article outlines the Terms of Use governing your access to Sai.fun.

Last Modified: September 1, 2025

Sai is a blockchain-oriented tool enabling certain on-chain functionalities. Using these functionalities (including via the interface or the website) poses significant risks to you and your digital assets. This document contains very important information regarding these risks and your rights and obligations, as well as conditions, limitations, and exclusions that might apply to you and your rights. Please read it carefully. Sai interacts or facilitates access to third party services that are independent from and unaffiliated with Sai, and the user assumes all risk from use of such third-party services.

These terms require the use of arbitration on an individual basis to resolve disputes, rather than jury trials or class actions.

By using the website, the interface, or any of our services, you accept and are bound by these terms of use.

You may not use our website, interface, or services if you: (a) do not agree to these terms; (b) are not the older of: (i) at least eighteen (18) years of age or (ii) legal age to form a binding contract; or (c) are prohibited from accessing or using the website or services or any associated functionalities by applicable law.

You represent to us that you are: (1) not subject to sanctions or otherwise designated on any list of prohibited or restricted parties, including but not limited to the lists maintained by the United Nations Security Council, the U.S. government (*i.e.*, the Specially Designated Nationals list and Foreign Sanctions Evaders list of the U.S. Department of Treasury and the Entity List of the U.S. Department of Commerce), the European Union or its member states, the United Kingdom, or other applicable government authority; and (2) not located in any country subject to a comprehensive sanctions program implemented by the United States.

## 1. Acceptance of the Terms of Use.

These Terms of Use are entered into by and between you ("you" or the "User") and the Liquiditea Corporation and its affiliates (*collectively*, "Sai," "we," "our," or "us"). The following Terms of Use, together with any documents they expressly incorporate by reference (*collectively*, this "Agreement" or these "Terms of Use"), govern Users' access to and use of our Services (as defined below), whether through our website, <https://app.sai.fun> (the "Website") or the website of a third party, or through any associated software application (the "Interface").

The User must read the Terms of Use carefully before using the Website or Interface. By using the Website or Interface, the User accepts and agrees to be bound and abide by these Terms of Use and our Privacy Policy, incorporated herein by reference. If the User does not want to agree to these Terms of Use, the Privacy Policy, or any documents that are incorporated herein by reference, the User must not access the Website or use the Interface.

The Website and Interface are offered and available to users who are eighteen (18) years of age or older. By using the Website or Interface, the User represents and warrants that the User is at least the higher of legal age to form a binding contract with Sai in the User's applicable jurisdiction or eighteen (18) years of age, and meets all of the eligibility requirements set forth herein. Further, by using the Website or Interface, the User represents and warrants that the User is not a citizen or resident of, nor is located in, any country against which the United States has sanctioned or embargoed or where the use of the Website or Interface is otherwise illegal or impermissible, whether by rule, statute, regulation, bylaw, court adjudication or order, protocol, administrative statement, code, decree, or other directive, requirement or guideline, whether applicable on Sai, the Website or Interface, or on the User (or multiple of the foregoing) by an authority with valid and enforceable jurisdiction ("Applicable Laws"). If the User does not meet all of these requirements, the User must not access or use the Website or Interface.

## 2. Prohibited Conduct

The User may access or use the Website, the Interface, and the Services only for lawful purposes and in accordance with these Terms of Use. The User represents and warrants that the User agrees not to use or access the Website, the Interface, or the Services, including through the use of the User's Wallet or Sai Account:

1. In any way that violates any applicable federal, state, local, or international laws or regulations (including, without limitation, any applicable anti-money laundering, anti-proliferation and anti-terrorism financing laws or any laws regarding the export of data or software to and from certain countries pursuant to Applicable Laws).
2. In any manner, directly or indirectly, designed to cause or to result in, or that has constituted, or which might reasonably be expected to constitute, the unlawful stabilization or manipulation of the price of any digital assets on any of blockchain networks or other blockchains or any other digital assets, including but not limited to fungible digital assets or non-fungible tokens.
3. For the purpose of exploiting, harming, or attempting to exploit or harm minors in any way by exposing them to inappropriate content, asking for personally identifiable information, or otherwise.
4. To transmit, or procure the sending of, any advertising or promotional material, including any "junk mail," "chain letter," "spam," or any other similar solicitation.
5. To impersonate or attempt to impersonate Sai, anyone affiliated with Sai, another user, or any other person or entity (including, without limitation, by using email addresses, screen names, similarly named or commonly misspelled URLs, or associated blockchain identities).
6. To engage in any other conduct that restricts or inhibits anyone's use or enjoyment of the Website, the Interface, or the Services, or which, as determined by us, may harm Sai or Users, or expose them to liability.
7. If the User is a citizen of or otherwise accessing the Website, the Interface, the Wallet, or the Services from the nations of the United States of America, Canada, Cuba, Iran, North Korea, Syria, certain sanctioned areas of Ukraine (including without limitation, the regions of Crimea, Donetsk, and Luhansk), or other countries or geographic regions sanctioned by the United States Department of the Treasury (*collectively*, "Prohibited Jurisdictions"), or if the User is otherwise listed as a Specially Designated National by the United States Department of the Treasury's Office of Foreign Asset Control ("OFAC").
8. If doing so is illegal or impermissible according to any Applicable Laws, including without limitation those promulgated by the United Nations Security Council, the United Kingdom, the United States (including those prohibiting dealings with sanctioned persons identified by the OFAC as Specially Designated Nationals and Blocked Persons ("SDN"), or other U.S. non-SDN restricted or prohibited parties lists, and those prohibiting dealings with persons organized, resident, or located in comprehensively sanctioned jurisdictions), and/or any other applicable national, provincial, federal, state, municipal or local laws and regulations (each as amended from time to time).
9. To cause the Website, the Interface, and the Services, any of their underlying blockchain networks or technologies, or any other functionality with which they interact to work other than as intended.
10. To damage the reputation of Sai or impair any of Sai's legal rights or interests.
11. To circumvent any filtering or geo-blocking, access control measures, security measures, or content filtering that Sai deploys on the Website, the Interface, or the Services, including, without limitation, by using a VPN.

Additionally, the User agrees not to:

1. Be likely to deceive or defraud, or attempt to deceive or defraud, any person, including (without limitation) providing any false, inaccurate, or misleading information (whether directly through the Website, the Interface, or the Services or through an external means that affects the Website, the Interface, or the Services) with the intent to unlawfully obtain the property of another or to provide knowingly or recklessly false information, including in any way that causes inaccuracy among the content on the Website, the Interface, or the Services.
2. Use the Website, the Interface, or the Services to manipulate or defraud any exchange, oracle system, or blockchain network, or the users thereof.
3. Promote any illegal activity, or advocate, promote, or assist any unlawful act.
4. Cause annoyance, inconvenience, or needless anxiety or be likely to upset, embarrass, alarm, or annoy any other person.
5. Impersonate any person, misrepresent the User's identity, or misrepresent its affiliation with any person or organization.
6. Misuse Sai's intellectual property, name, or logo, including any trade or service marks, without express consent from Sai or in any manner that otherwise harms Sai, including any action that implies an untrue endorsement by or affiliation with Sai;
7. Engage in any activity or behavior that violates any Applicable Laws concerning, or otherwise damages, the integrity of the Website, the Interface, or the Services, or any other service or software which relies on the Interface or the Services.
8. Give the impression that they emanate from or are endorsed by us or any other person or entity if this is not the case as it relates to the Website, the Interface, or the Services.
9. Use the Website or the Interface in any manner that could disable, overburden, damage, impair, or interfere with any other party's use of the Website or the Interface, including the ability to engage in real time activities through the Website or the Interface or with the Services.
10. Use any robot, spider, or other automatic device, process, or means to access the Website, the Interface, or the Services for any purpose, including monitoring or copying any of the material on the Website or the Interface.
11. Use any manual process to monitor or copy any of the material on the Website or the Interface, or for any other purpose not expressly authorized in these Terms of Use, without our prior written consent.
12. Use any device, software, or routine that interferes with the proper working of the Website, the Interface, or the Services.
13. Introduce any viruses, Trojan horses, worms, logic bombs, or other material that is malicious or technologically harmful to the Website, the Interface, the Services, the Users, any underlying blockchain, or any of the Services' related utilities or functionalities.
14. Attempt to gain unauthorized access to, interfere with, damage, or disrupt any parts of the Website or the Interface, the server on which the either is stored, or any server, computer, or database connected to the Website or the Interface, including any underlying blockchain.
15. Violate the legal rights (including the rights of publicity and privacy) of others or contain any material that could give rise to any civil or criminal liability under applicable laws or regulations or that otherwise may be in conflict with these Terms of Use and our Privacy Policy.
16. Engage in any activity that would infringe or violate any copyright, trademark, right of publicity or privacy or any other proprietary right under Applicable Law, including but not limited to sales, distribution, or access to licensed materials without the appropriate authorization from the rights holder.
17. Attack the Website, the Interface, the Services, or any of the Services' underlying blockchain networks or technologies, or any other functionality with which the Services interact via a denial-of-service attack or a distributed denial-of-service attack.
18. Use the Website, the Interface, or the Services in any way that is, in our sole discretion, libelous, defamatory, profane, obscene, pornographic, sexually explicit, indecent, lewd, vulgar, suggestive, harassing, stalking, hateful, threatening, offensive, discriminatory, bigoted, abusive, inflammatory, fraudulent, deceptive, or otherwise objectionable or likely or intended to incite, threaten, facilitate, promote, or encourage hate, racial intolerance, or violent acts against others.
19. Use the Website, the Interface, or the Services to harass, abuse, or harm another person or entity, including anyone on the Sai team or its service providers.
20. Encourage or induce any third party to engage in any of the activities prohibited under these Terms.
21. Otherwise attempt to interfere with the proper working of the Website, the Interface, or the Services.

## 3. User Acknowledgements Required to Access the Website, the Interface, and the Services

As a condition to accessing or using the Website, the Interface, or the Services, you acknowledge, understand, and agree to the following:

1. The information provided in the Website, the Interface, and for the Services is for general informational purpose only and Sai reserves the right to update, modify, or amend any contents therein, at its sole discretion and without prior notice. Nothing herein should be used or considered as legal, financial, tax, or any other advice, nor as an instruction or invitation to act in any way by anyone;
2. Sai does not act as an agent for you or any other user of the Website, the Interface, or the Services;
3. You are solely responsible for your use of and access to the Website, the Interface, and the Services, including, without limitation, all of your use of, transfers and receipts of, and all other transactions involving digital assets;
4. to the fullest not prohibited by Applicable Laws, We owe no fiduciary duties or liabilities to you or any other party, and that to the extent any such duties or liabilities may exist at law or in equity, you hereby irrevocably disclaim, waive, and eliminate those duties and liabilities;
5. you are solely responsible for reporting and paying any taxes incurred in connection with your use of the Website, the Interface, and the Services; and
6. we have no control over, or liability for, the delivery, quality, safety, legality, or any other aspect of any digital assets that you may transfer to or from a third party, and we are not responsible for ensuring that an entity with whom you transact completes the transaction or is authorized to do so (or repays any amounts it may be required to repay to you), and if you experience a problem with any transactions in digital assets using the Website, the Interface, or the Services, then you bear the entire risk.

### 4. User Covenants

As a condition to accessing or using the Website, the Interface, or the Services, you covenant to Sai the following:

* In connection with interacting with the Website, the Interface, or the Services, you will only engage with digital assets that belong to you;
* You will obey all Applicable Laws in connection with using the Website, the Interface, and the Services, and you will not use the Website, the Interface, or the Services if the laws of your country, or any other Applicable Law, prohibit you from doing so; and
* In addition to complying with all restrictions, prohibitions, and other provisions of these Terms, you will (i) ensure that, at all times, all information that you provide to us during your use of the Website, the Interface, or the Services is current, complete, and accurate; (ii) maintain the security and confidentiality of your private keys associated with your digital assets, passwords, API keys and other related credentials(iii) not sell NIBI Tokens to persons from Prohibited Jurisdictions; and (v) if you are a U.S. Person, not to seek to acquire NIBI Tokens using the Website, the Interface, or the Services.

## 5. Services; The Interface; Blockchain Fees.

We provide software that allows users to connect their digital asset wallet to our Website or Interface. As a result, the connection with your wallet relies on third-party services.

You agree to assume all risk related to any bugs, errors, vulnerabilities, or exploits that may exist in the services and any third-party services (as defined below), whether foreseeable or unforeseeable. You acknowledge and agree that your use or interaction with the website, interface, or services is at your own risk and Sai waives all liability or responsibility and makes no warranties related to the services.

To use the Services, you will have to create an account (a "Sai Account") via the Website or Interface. You agree that you will not disclose your Sai Account access credentials, including any usernames and passwords associated with your Sai Account, to anyone or otherwise provide anyone else with access to your Sai Account credentials. You are solely responsible for all activities that occur under your Sai Account, or are otherwise referable to your Sai Account credentials, whether or not you know about such activities.

Once you create your Sai Account, you will be permitted to connect your digital asset wallet via the supported blockchain networks.

You acknowledge and understand that, in certain circumstances, such as if you lose or are otherwise unable to access your login credentials associated with your Sai Account, you may be unable to access the Website, the Interface, or the Services, and to engage in any of the features allowed by those platforms.

Your full use and enjoyment of the Website, the Interface, or the Services may require you to pay transactional fees, or other types of fees, both to Sai and possibly as may be required by the underlying blockchain or distributed ledger service or that are used to encourage intended use among the underlying blockchain's participants ("Blockchain Fees"). While certain fees may be set and levied by Sai, including for transactions and activities such as opening and closing a position, or a funding rate associated with a transaction, certain Blockchain Fees are not levied directly by Sai, but rather are determined by your use of the Services and the rules placed by corresponding blockchain communities at large. You acknowledge that Sai has no control over Blockchain Fees, (including without limitation their applicability, payment, amounts, transmission, intended operation, and effectiveness) whether related to your use of the Services or otherwise, and agree that in no event will Sai be responsible to you or any other party for the payment, repayment, refund, disbursement, indemnity, or for any other aspect of your use or transmission of Blockchain Fees. For further information regarding blockchain technology, digital assets, and the associated risks, *see* the Section below entitled **Nature of Blockchain; Assumption of Risk; Waiver of Claims**.

## 6. Monitoring & Enforcement; Termination

We have the right to:

· Take appropriate legal action, including without limitation, referral to law enforcement, for any illegal or unauthorized use of the Website, the Interface, or the Services.

· Terminate, prevent, or suspend your access to all or part of the Website, the Interface, or the Services for any or no reason, including without limitation, any violation of these Terms of Use.

Without limiting the foregoing, we have the right to cooperate fully with any law enforcement authorities or court order requesting or directing us to disclose the identity or other information of anyone posting any materials on or through the website, the interface, or the services. You waive and hold harmless Sai and its affiliates, licensees, and service providers from any claims resulting from any action taken by Sai/any of the foregoing parties during, or taken as a consequence of, investigations by either such parties or law enforcement authorities.

However, we cannot review interactions or activities before they are executed through the Interface, and, given the nature of blockchain and functionalities like those offered via the Services, cannot ensure prompt removal or rectification of objectionable interactions or activities after they have been executed. Accordingly, the User agrees that we assume no liability for any action or inaction regarding transmissions, communications, transactions, blockchain operations, or content provided by any User or third party, including any that may cause a malfunction or inaccuracy on the Website or among the Services. We have no liability or responsibility to anyone for any other party's performance or nonperformance of the activities described in this Section, nor for any harms or damages created by others' interactions with any blockchain underlying the Services or reliance on the information or content presented on the Website.

## 7. Changes to the Terms of Use

We may revise and update these Terms of Use from time to time in our sole discretion. All changes are effective immediately when we post them and apply to all access to and use of the Website thereafter. However, any changes to the dispute resolution provisions set out in the Section below entitled Governing Law & Jurisdiction will not apply to any disputes for which the parties have actual notice before the date the change is posted on the Website or the Interface.

The User's continued use of the Website, the Interface, or the Services following the posting of revised Terms of Use means that the User accepts and agrees to the changes. **The User is expected to check this page each time it accesses this Website, the Interface, or the Services so it is aware of any changes, as they are binding on the User**.

## 8. Accessing the Website or the Interface & User Security.

We reserve the right to withdraw or amend the Website or the Interface, and any other Services or material we provide on the Website or the Interface, in our sole discretion without notice. We will not be liable if for any reason all or any part of the Website, the Interface, the public blockchains underlying the Services, or any of the Services are unavailable at any time or for any period. From time to time, we may restrict access to some parts of or all of the Website or the Interface to Users.

The User is responsible for both:

1. Making all arrangements necessary for the User to have access to the Website, the Interface, and the Services.
2. Ensuring that all persons who access the Website, the Interface, or the Services through the User's internet connection are aware of these Terms of Use and comply with them.

To access certain Services or some of the resources offered on the Website or the Interface, the User may be asked to provide certain registration details or other information. Other Services or resources offered on the Website or Interface may require the User to utilize certain Web3 capabilities, such a digital asset wallet capable of interacting with the User's web browser or relevant blockchain nodes ("Web3 Utilities"). It is a condition of the User's use of the Website, the Interface, and the Services that the User only operate such Web3 Utilities with a private key(s) that the User created or has the direct, explicit permission of the party who created the relevant private key(s). The User agrees that all information it provides to interact with the Website, Interface, Services, or otherwise, including, but not limited to, through the use of any interactive features on the Website (such as registration information) is correct, current, and complete, and is governed by our Privacy Policy. The User consents to all actions we take with respect to the User's information as is consistent with our Privacy Policy.

If the User utilizes a Web3 Utility that relies on a separate username, password, private key, or any other piece of information as part of its security procedures, the User must treat such information as confidential, and the User must not disclose that information to any other person or entity. The User also acknowledges that any identity linked to its Web3 Utility is personal to the User and agrees not to provide any other person with access to such identity. The User also agrees to ensure that it will lock or otherwise prevent its Web3 Utility from unauthorized use on the Website, the Interface, or the Services at the end of each session. The User should use particular caution when accessing the Website, the Interface, or the Services from a public or shared computer so that others are not able to view or record the User's username, password, private key, or other personal information. For further information regarding Magic Keys, Safe Words, and Wallet security, *see* the Section below entitled Nature of Blockchain; Assumption of Risk; Waiver of Claims.

We have the right to disable the User's access, including those associated with a Web3 Utility (such as that represented by a public address), to the Website, the Interface, or the Services, or to block any IP address from accessing the Website, the Interface, or the Services at any time in our sole discretion for any or no reason, including if, in our opinion, the User or that identity has violated any provision of these Terms of Use.

## 9. Intellectual Property Rights.

Except any open-source software, or other material incorporated in the Website, the Interface, or the Services, the Website and the Interface and their entire contents, features, and functionality (including but not limited to all information, software, text, displays, images, video, and audio, and the design, selection, and arrangement thereof) are owned by Sai, its licensors, or other providers of such material and are protected by United States and international copyright, trademark, patent, trade secret, and other intellectual property or proprietary rights laws. The User must not reproduce, distribute, modify, create derivative works of, publicly display, publicly perform, republish, download, store, or transmit any of the material on the Website or the Interface, except as follows:

1. The User's computer may temporarily store copies of such materials in RAM incidental to the User's accessing and viewing those materials.
2. The User may store files that are automatically cached by the User's web browser for display enhancement purposes.
3. The User may print or download copies of a reasonable number of pages of the Website or the Interface for its own personal, non-commercial use and not for further reproduction, publication, or distribution.
4. If we provide desktop, mobile, or other applications for download, the User may download a single copy to its computer or mobile device, provided the User agrees to be bound by any applicable end user license agreement or other similar agreement for such applications.
5. For any open-source materials provided on the Website, the Interface, or through the Services, the User may perform any activities only as is consistent with the open-source license applicable to such materials.

The User must not:

1. Modify copies of any materials from the Website or the Interface.
2. Use any illustrations, photographs, video or audio sequences, or any graphics separately from the accompanying text.
3. Delete or alter any copyright, trademark, or other proprietary rights notices from copies of materials from the Website or the Interface.

If the User wishes to make any use of material on the Website or the Interface other than that set out in this Section, it should address its request to: sai { at } nibiru.org.

If the User prints, copies, modifies, downloads, or otherwise uses or provides any other person with access to any part of the Website or the Interface in breach of these Terms of Use, the User's right to access the Website and the Interface will stop immediately and the User must, at our option, return or destroy any copies of the materials the User has made. No right, title, or interest in or to the Website, the Interface, or any content on either is transferred to the User, and all rights not expressly granted are reserved by Sai.

Notwithstanding anything to the contrary in these Terms of Use, the User may freely use any open-sourced materials up to the limits provided, but in accordance with any requirements placed, by those materials' or properties' applicable licenses.

Any use of the Website not expressly permitted by these Terms of Use is a breach of these Terms of Use and may violate copyright, trademark, and other laws.

## 10. Trademarks

The Sai name and all related names, logos, product and service names, designs, and slogans are trademarks of Sai or its affiliates or licensors. You must not use such marks without the prior written permission of Sai; provided, however, User is hereby granted a limited, revocable, non-transferable permission and license to use the term "Sai" and any related names (excluding the Sai name), logos (excluding the Company logo), product and service names, designs, and slogans in any way that they desire so long as such usage is not done in a way that: (1) is deceitful, fraudulent, or manipulative; (2) implies any relationship between User and Sai beyond that reasonably typical of the administrator of a website and its users; or (3) to cause confusion in any way to gain digital assets of, or personal information about, another party other than that intended by the Services, the Wallet, the Interface or any related or interacting functionality (for example, you may not use the foregoing marks to execute phishing attacks, spearphishing attacks, social engineering, or in any way that may cause a party to transmit digital assets to an unintended recipient or to reveal private information, like a private key or password). All other names, logos, product and service names, designs, and slogans on the Website and the Interface are the trademarks of their respective owners, if applicable.

## 11. Reliance on Information Posted

The information presented on or through the Website and the Interface is made available solely for general information and education purposes. We do not warrant the accuracy, completeness, or usefulness of this information. Any information posted to the Website, the Interface, or through the Services should not be construed as an intention to form a contract, and in no case should any information be construed as Sai's offer to buy, sell, transact with, or exchange digital assets. Any reliance the User places on such information is strictly at the User's own risk, and as is common in the blockchain space, the User is assuming a high amount of risk related to others or technical harms when operating via the Website, the Interface, and the Services. We disclaim all liability and responsibility arising from any reliance placed on such materials by the User or any other User, by anyone who may be informed of any of the Website's or the Services' contents, or by the actions or omissions of others interacting with the Wallet or any underlying blockchain.

The Website, the Interface, and the Services may include content provided by third parties, including without limitation materials provided by other Users, bloggers, and third-party licensors, syndicators, blockchain users, decentralized applications, aggregators, and/or reporting services. All statements, alleged facts, and/or opinions expressed in these materials, and all articles and responses to questions and other content, other than the content provided by Sai, are solely the opinions and the responsibility of the person or entity providing those materials. These materials do not necessarily reflect the opinion of Sai or even the factual status of reality. We are not responsible, or liable to the User or any third party, for the content or accuracy of any materials provided by any third parties, and User agrees that it bears sole and absolute responsibility to evaluate and select any third-party functionality with which it interacts via the Services.

## 12. Changes to the Website, the Interface, and the Services

We may update the content on, design of, or functionalities available through the Website or the Interface, or through the Services from time to time, but the Website, the Interface, and the Services are not necessarily complete or up-to-date. Any of the material on the Website or the Interface, or provided through the Services may be out of date at any given time, and we are under no obligation to update such material.

## 13. Information About the User & The User's Visits to the Website and the Interface

All information we collect on the Website or through the Interface or the Services is subject to our Privacy Policy. By using the Website, the Interface, and the Services, the User consents to all actions taken by us with respect to the User's information in compliance with the Privacy Policy.

## 14. Warranty Disclaimer

Sai is a developer of software and does not unilaterally offer, operate, or administer any blockchain networks, digital assets, or Dapps. The Services merely attempt to assist Users in more easily participating in blockchain networks generally. Nonetheless, Sai has no oversight on or control over any particular digital asset, Dapp, or blockchain network.

The User is responsible for its use of the Services, the functionalities they enable, transactions engaged through the Website or the Interface, and the use of the information derived thereof. The User is solely responsible for complying with all Applicable Laws related to its transactions and activities that directly or indirectly incorporate our provision of the Services. The User acknowledges its understanding that Sai is not registered or licensed with, nor have our Website, Interface, or Services (or the software contained therein) been reviewed by, any financial or banking regulator.

Sai may link to or offer "Third-Party Services" on the Website or otherwise through the Services, including without limitation, Dapps, Passkeys, private keys or "Sign In with Apple" and "Google Sign-In" Access Credential services. Any purchase, enabling, or engagement of Third-Party Services, including but not limited to implementation, customization, consulting services, and any exchange of data between the User and any Third-Party Service, is solely between you and the applicable Third-Party Service provider and is subject to the terms and conditions of such Third-Party Service provider. Sai does not warrant, endorse or support Third-Party Services and is not responsible or liable for such Services or any losses or issues that result from the User's use of such services. If the User purchases, enables or engages any Third-Party Service for use in connection with the Services, the User acknowledges that Sai may allow providers of those Third-Party Services to access your data used in connection with the Services as required for the interoperation of such Third-Party Services with the Services. The User represents and warrants that use of any Third-Party Service signifies independent consent to the access and use of your data by the Third-Party Service provider, and that such consent, use, and access is outside of Sai's control. Sai will not be responsible or liable for any disclosure, modification or deletion of data resulting from any such access by Third-Party Service providers.

The User understands that we cannot and do not guarantee or warrant that files available for download from the Website, the Interface, or through the Services will be free of viruses or other destructive code. The User is responsible for implementing sufficient procedures and checkpoints to satisfy the User's particular requirements for: (1) an appropriate Web3 Utility; (2) anti-virus protection and accuracy of data input and output; (3) its participation in and use of the Wallet and any of the Services' underlying blockchain and related technologies; and (4) maintaining a means external to our site to reconstruct any lost data.

To the fullest extent provided by law, we will not be liable for any loss or damage caused by a distributed denial-of-service attack, man-in-the-middle attack, viruses, or other technologically harmful material that may infect the user's computer equipment, computer programs, data, or other proprietary material due to the user's use of the website, the interface, or any services or items obtained through the website or the interface, or due to the user's downloading of any material posted on it, or on any website linked to it.

The user's use of the website and the interface, their content, and any of the services is at the user's sole risk. The website, the interface, and the services are provided on an "as is'' and "as available" basis. To the fullest extent legally permissible, we, nor any person associated with Sai, make, and we explicitly disclaim, any and all representations or warranties of any kind related to the website, the interface, and the services, whether express, implied, or statutory, including (without limitation) the warranties of merchantability, non-infringement, and fitness for a particular purpose. Neither Sai nor any person associated with Sai makes any warranty or representation with respect to the completeness, security, reliability, quality, accuracy, or availability of the website, the interface, or the services. Sai and any person associated with Sai does not represent or warrant that: (1) access to the website, the interface, or the services will be continuous, uninterrupted, timely, without delay, error-free, secure, or free from defects; (2) that the information contained or presented on the website or via the services is accurate, reliable, complete, concise, current, or relevant; (3) that the website, the interface, the services, or any software contained therein will be free from defects, malicious software, errors, or any other harmful elements, or that any of such will be corrected; or (4) that the website, the interface, or the services will meet the user's expectations. No information or statement that we make, including documentation or our private communications, should be treated as offering any warranty concerning the website, the interface, or the services. We do not endorse, guarantee, or assume any liability or responsibility for any content, advertisements, offers, statements, or actions by any third party either regarding the website or the services.

The foregoing does not affect any warranties that cannot be excluded or limited under applicable law.

## 15. Limitation of Liability

To the fullest extent provided by law, in no event will Sai, its affiliates, or their licensors, service providers, employees, agents, officers, or directors be liable for damages of any kind, under any legal theory, arising out of or in connection with the user's use, or inability to use, the website, the interface, the services, any websites linked to it, any content on the website or such other websites, including any direct, indirect, special, incidental, consequential, or punitive damages, including but not limited to, personal injury, pain and suffering, emotional distress, loss of revenue, loss of profits, loss of business or anticipated savings, loss of use, loss of goodwill, loss of data, and whether caused by tort (including negligence), breach of contract, or otherwise, even if foreseeable. This disclaimer of liability extends to any and all damages caused by any third party (including, without limitation, those caused by fraud, deceit, or manipulation), whether or not a user, or any failure, exploit, or vulnerability of the website, services, the APIs, the user's Web3 Utilities, or the underlying blockchains or related blockchain functionalities. To the fullest extent provided by law, in no event will the collective liability of Sai and its subsidiaries and affiliates, and their licensors, service providers, employees, agents, officers, and directors, to any party (regardless of the form of action, whether in contract, tort, or otherwise) exceed the greater of $100 or the amount you have paid directly to Sai (not including digital asset deposits into your non-custodial wallet or Sai account) for the applicable content or services in the last six months out of which liability arose.

The foregoing does not affect any liability that cannot be excluded or limited under applicable law.

## 16. Nature of Blockchain; Assumption of Risk; Waiver of Claims

Blockchains, digital assets, the Wallet, Smart Accounts, Dapps, Web3 Utilities, and their related technologies and functionalities are still emerging innovations that carry a relatively high amount of foreseeable and unforeseeable risk from security, financial, technical, political, social, and personal safety standpoints. The mere access to and interaction with blockchains requires high degrees of skill and knowledge to operate with a relative degree of safety and proficiency. digital assets are highly volatile in nature due to many diverse factors, including without limitation use and adoption, speculation, manipulation, technology, security, and legal and regulatory developments and application. Further, the speed and cost of transacting with cryptographic technologies, such as blockchains like those underlying the Wallet, is variable and highly volatile. Moreover, the transparent nature of many blockchains means that any interactions the User has with any blockchain may be publicly visible and readable in human form.

By accessing and using the Website, the Interface, or the Services, the User acknowledges the foregoing, and agrees and represents that it understands such and other risks involved with blockchains, the Wallet, and related technologies (including without limitation any specific technical language used in this Agreement). The User further represents that it has all knowledge sufficient to work, and is informed of all foreseeable risks, and the possibility of unforeseeable risks, associated with blockchains, digital assets, Web3 Utilities, smart contracts, the Interface, the Wallet, and the Services. The User further acknowledges, and assumes all risk related to the possibility, that any information presented via the Website, Interface, or Services may be inaccurate, possibly due to another party's malicious activities and possibly to the User's severe harm or detriment. The User agrees that we are not responsible for any of these or related risks, do not own or control any blockchain and cannot guarantee the safe or accurate functioning of the Services, and shall not be held liable for any resulting harms, damages, or losses incurred by or against the User experiences while accessing or using the Website or the Services. Accordingly, the User acknowledges the foregoing, represents its understanding of the foregoing, and agrees to assume full responsibility for all of the risks of accessing and using the Website and interacting with the Services, whether mentioned in this Section or otherwise. The User further expressly waives and releases us from any and all liability, claims, causes of action, or damages arising from or in any way relating to the User's use of the Website and the Interface, and the User's interaction with the Services.

If the User is a California resident, the User expressly and explicitly waives the benefits and protections of California Civil Code § 1542, which states: "\[a] general release does not extend to claims that the creditor or releasing party does not know or suspect to exist in his or her favor at the time of executing the release and that, if known by him or her, would have materially affected his or her settlement with the debtor or released party."

## 17. No Professional Advice

All information or content provided or displayed by the Website (including without limitation, on the Interface) is for informational purposes only and should not be construed as professional advice (including, without limitation, tax, legal, or financial advice). The User should not take, or refrain from taking, any action based on any information or content displayed or provided on the Website, on the Interface, or through the Services. The User should seek independent professional advice from an individual licensed and qualified in the area appropriate for such before the User makes any financial, legal, or other decisions where such is considered prudent. The User acknowledges and agrees that, to the fullest extent permissible by law, it has not relied on Sai, the content on the Website, the Interface, or the Services for any professional advice related to its financial or legal behaviors.

Additionally, it is your sole responsibility to determine whether, and to what extent, any taxes apply to any transactions you conduct through the Website, the Interface, the Wallet and/or the Services, and to withhold, collect, report, and remit the correct amounts of taxes to the appropriate tax authorities.

## 18. No Fiduciary Duties

These Terms of Use, and the provision of the Website, the Interface, and the Services, are not intended to create any fiduciary duties between us and the User or any third party. To the fullest extent permissible by law, the User agrees that neither the User's use of the Website or the Interface, or of the Services causes us or any User to owe fiduciary duties or liabilities to the User or any third party. Further, the User acknowledges and agrees to the fullest extent such duties or liabilities are afforded by law or by equity, those duties and liabilities are hereby irrevocably disclaimed, waived, and eliminated, and that we and any other User shall be held completely harmless in relation thereof. The User further agrees that the only duties and obligations that we or any User owes the User, and the only rights the User has related to this Agreement or the User's use of the Website, the Interface, or the Services, are those set out expressly in this Agreement or that cannot be waived by law.

## 19. No Insurance

Your digital asset wallet and digital asset balances contained therein are not checking or savings accounts, and we do not provide any kind of insurance to you against any type of loss, including (without limitation) losses due to decrease in value of assets, assets lost due to a cybersecurity failure, or from your or other individuals' errors or malfeasance. In most jurisdictions digital assets are not legal tender, and most digital assets are not backed by any government. Your digital asset balances are not covered by Federal Deposit Insurance Corporation ("FDIC") or Securities Investor Protection Corporation ("SIPC") protections.

## 20. Links from the Website or the Interface.

The Website or the Interface may contain links to other sites and resources provided by third parties; these links are provided for convenience only. This includes links contained in advertisements, including banner advertisements and sponsored links. We have no control over the contents of those sites or resources, and the User acknowledges and agrees that we do not and will not accept any responsibility for them or for any loss or damage that may arise from the User's use of them. If the User decides to access any of the third-party websites linked to the Website, the User does so entirely at its own risk and subject to the terms and conditions of use for such websites.

## 21. Indemnification

The User agrees to defend, indemnify, and hold harmless Sai, its affiliates, licensors, and service providers, and its and their respective officers, directors, employees, contractors, agents, licensors, suppliers, successors, and assigns from and against any claims, liabilities, damages, judgments, awards, losses, costs, expenses, or fees (including reasonable attorneys' fees) arising out of or relating to: (1) the User's violation of these Terms of Use; (2) the User's use of the Website, the Interface, or the Services, including but not limited to the User's interactions with the Interface or other features which incorporate the Services, use of or reliance on the content on the Website or the Interface, services, and products other than as expressly authorized in these Terms of Use; (3) the User's use or reliance on of any information obtained from the Website or the Interface; or (4) any other party's access and use of the Website, the Interface, or the Services with the User's assistance or by using any device or account that the User owns or control.

## 22. Governing Law & Jurisdiction

All matters relating to the Website, the Interface, the Services, and these Terms of Use, and any dispute or claim arising therefrom or related thereto (in each case, including non-contractual disputes or claims), shall be governed by and construed in accordance with the internal laws of Panama without giving effect to any choice or conflict of law provision or rule (whether of Panama or any other jurisdiction).

## 23. Arbitration; Class Arbitration Waiver

In the event of a dispute between you, the User, and Sai (each a "Party" and *collectively*, the "Parties") related to these Terms of Use or the breach thereof, the Parties shall participate in at least one (1) live or teleconferenced (*i.e.*, using Zoom or a similar videoconferencing software that allows the Parties to communicate in real time) mediation session with the International Center for Dispute Resolution ("ICDR") neutral. The Parties agree to participate in mediation in good faith and the Parties agree to share equally in the cost of such mediation.

Should the Dispute not be settled within seven (7) days following the live mediation session, either Party may then commence a binding arbitration administered in Panama, or if the ICDR is unable to administer the arbitration in Panama, then the nearest jurisdiction where it can do so. A single arbitrator shall preside, and proceedings shall be conducted remotely to the maximum extent possible. Each Party shall pay its own expenses in such arbitration, including its attorneys' fees, subject to reapportionment by the arbitrator in a final award. The language of the arbitration shall be English. The prevailing Party shall be entitled to recover reasonable attorneys' fees and other costs incurred in such proceeding in addition to any other relief to which it may be entitled. Any interim or provisional relief that would be available from a court of law shall be available in accordance with the rules of Panama, however, nothing in this Agreement shall preclude the Parties from obtaining preliminary injunctive relief in a court of competent jurisdiction located in Panama or another mutually acceptable jurisdiction if necessary to prevent irreparable harm pending the conclusion of any arbitration. The final arbitration award may be confirmed in a court located in the Panama and the Parties agree to waive any claim of improper venue or *forum non conveniens*. Except as may be required by law, neither a Party nor an arbitrator may disclose the existence, content, or results of any arbitration hereunder without the prior written consent of both parties. The Parties agree to arbitrate solely on an individual basis, and that these Terms of Use do not permit class arbitration or any claims brought as a plaintiff or class member in any class or representative arbitration proceeding. The arbitral tribunal may not consolidate more than one person's claims and may not otherwise preside over any form of a representative or class proceeding. In the event the prohibition on class arbitration is deemed invalid or unenforceable, then the remaining portions of the arbitration agreement will remain in force.

## 24. Limitation on Time to File Claims

Any cause of action or claim either Sai or the user may have arising out of or relating to these terms of use or its use of the website, the interface, or any of the services, must be commenced within six (6) months after the cause of action accrues; otherwise, such cause of action or claim is permanently barred.

## 25. Waiver & Severability

No waiver by Sai of any term or condition set out in these Terms of Use shall be deemed a further or continuing waiver of such term or condition or a waiver of any other term or condition, and any failure of Sai to assert a right or provision under these Terms of Use shall not constitute a waiver of such right or provision.

If any provision of these Terms of Use is held by a court or other tribunal of competent jurisdiction to be invalid, illegal, or unenforceable for any reason, such provision shall be eliminated or limited to the minimum extent such that the remaining provisions of the Terms of Use will continue in full force and effect.

## 26. Entire Agreement

The Terms of Use, the Privacy Policy, and any other document incorporated by reference herein constitute the sole and entire agreement between the User and Sai regarding the Website and supersede all prior and contemporaneous understandings, agreements, representations, and warranties, both written and oral, regarding the Website.


# Sai Disclosures

These disclosures and disclamers apply to all statements made by Nibiru about Sai, which is located at sai.fun.

## Sai is Independent from Nibiru

Sai is run on the Nibiru chain, but is otherwise owned and managed by entities independent from Nibiru. Sai and Nibiru each have their own terms of use. Any user of either must read and agree to the respective terms of use prior to using.

## No Offer or Solicitation

Any content, promotion, statements regarding, or other material about Sai ("Content") does not constitute an offer to sell or a solicitation of an offer to buy any securities, tokens, or any other form of investment in any jurisdiction. The distribution or dissemination of this document may be restricted by law in certain jurisdictions, and it is the responsibility of any person in possession of this Update to comply with any such laws and regulations.

## NIBI In Any Form Provides No Rights to Holders

Purchase of NIBI tokens in any form or any other digital assets mentioned in any Content (each and collectively the "Tokens") does not represent or confer any ownership right or stake, share, security, or equivalent rights, or any right to receive future revenue shares, dividends, intellectual property rights or any other form of participation in or relating to Nibiru, any of its affiliates, Nibiru and its related products, and/or services or any part thereof. The reader acknowledges and accepts that at no time and under no circumstances shall they be entitled, as a holder of any Tokens, to vote, receive dividends or be deemed the holder of equity or capital stock of any entity for any purpose, nor will anything contained herein be construed to confer on the reader such rights.

## Forward-Looking Statements

Certain statements contained in Content may be forward-looking, including but not limited to plans, goals, and expectations regarding the future business, operations, and performance of the project. These statements are based on current beliefs, assumptions, and projections and are subject to risks, uncertainties, and changes beyond the control of the project. Actual results may differ materially from those expressed or implied in any forward-looking statements.

## Regulatory Status

The regulatory status of tokens and blockchain technology, digital assets, and cryptocurrencies generally is uncertain and evolving. Content and the project described within may be impacted by legal, regulatory, and compliance requirements. It is the responsibility of potential participants to determine whether they can legally acquire, hold, or participate in Nibiru's activities, including Sai, under the laws of their jurisdiction.

## No Liability

Nibiru, its affiliates, contractors, and their respective officers, employees, and agents shall not be held liable for any loss, damage, or liability arising out of or in connection with the use of Content, the project, or any associated products or services. Participation in the project and any related activities is done at your own risk.

## No Guarantees

There is no guarantee or assurance that the project, its platform, or any tokens described in Content will achieve any of its goals, intended outcomes, or objectives. The value and functionality of any Tokens are not guaranteed, and they may be subject to significant volatility, market forces, and other risks.

## Risk Factors - Trading on Sai is Risky

Participation in Sai and the use of tokens involve significant risks, including but not limited to financial, regulatory, technological, and market risks. It is strongly recommended that participants fully understand these risks before engaging with the project or acquiring any tokens. Trading in perpetuals is particularly risky, and one should only engage in such trading, or any other activities on Sai, if significantly experienced. Any funds or assets of value connected or used with the Sai platform could be lost, and a user should only connect or use assets or funds that they can afford to lose.

## Independent Advice

All should seek independent professional advice regarding their individual circumstances before engaging in any activity related to Nibiru or Sai.

## Amendments and Updates

Nibiru reserves the right to amend, modify, or update any Content at any time without prior notice. It is the responsibility of readers to stay informed of any changes.


