> ## Documentation Index
> Fetch the complete documentation index at: https://xoxno.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Gas optimization

> Use real-time gas statistics and network state to submit transactions at the selected gas price.

Use real-time gas statistics from the Relayer to submit transactions at the selected gas price for cost and confirmation speed.

## Gas pricing fundamentals

Every MultiversX transaction specifies a `gasPrice` in atomic EGLD units (10^-18 EGLD) and a `gasLimit` in gas units:

```text theme={"system"}
fee = gasPrice * gasUsed
```

The network enforces a minimum `gasPrice` of `1,000,000,000` (10^9). Validators prioritize higher-priced transactions, so the minimum price during congestion risks delayed inclusion.

**PPU (price per unit)** is the effective cost per gas unit. For transactions with a non-empty `data` field, each byte costs an additional 1,500 gas units. PPU accounts for this overhead, so it compares costs across transaction types more accurately than raw `gasPrice`.

## The sliding window

Gas statistics are computed over a **30-minute sliding window** of **10-second buckets** (180 buckets total). As time advances, the oldest bucket expires and the window shifts forward.

| Metric      | Measures                             | Spam sensitivity                         |
| ----------- | ------------------------------------ | ---------------------------------------- |
| `avg`       | Mean over all transactions in window | Biased low by spam volume                |
| `bucketAvg` | Mean of per-bucket averages          | Less sensitive to volume spikes          |
| `p50`       | 50th percentile gas price            | Often at minimum during spam             |
| `p75`       | 75th percentile                      | Spam-resistant; typical legitimate price |
| `p90`       | 90th percentile                      | Upper bound; high congestion             |

Spam attacks cluster at the minimum price. The `p75` percentile filters these out and reflects what legitimate transactions paid, making it a stronger default for most applications.

## Choosing the right percentile

| Scenario                  | Recommended                             | Rationale                                        |
| ------------------------- | --------------------------------------- | ------------------------------------------------ |
| Background operations     | `p50` or `bucketAvg`                    | Cheapest with historical inclusion               |
| Standard user swaps       | `p75`                                   | Reliable inclusion without overpay               |
| Time-sensitive arbitrage  | `p90`                                   | Competitive even during congestion               |
| Mempool pressure > 0.5    | Shift up one tier (e.g., `p75` → `p90`) | Queue is growing; higher price improves ordering |
| Shard has no transactions | `1,000,000,000` (minimum)               | Shard is idle; minimum is sufficient             |

When a shard reports all gas price fields as `1000000000` and PPU as `0`, it has no recent transaction history. The minimum price is sufficient.

## Using mempool pressure

The `networkStats` stream on `wss://relayer.xoxno.com/ws/stats` pushes updates at adaptive intervals. Each message includes `mempool.pressure` per shard, representing current queue depth relative to throughput capacity.

```ts theme={"system"}
function selectPercentile(pressure, baseline = 'normal') {
  if (pressure > 0.75) return 'p90';   // heavy congestion
  if (pressure > 0.50) return 'p75';   // moderate
  if (pressure > 0.25) return 'p75';   // light
  return baseline === 'low' ? 'bucketAvg' : 'p75';
}
```

## Block time expectations

| Transaction type | Current network                 | After Supernova (600 ms blocks) |
| ---------------- | ------------------------------- | ------------------------------- |
| Same-shard       | \~6 seconds                     | \~600 ms                        |
| Cross-shard      | \~12 seconds (two block rounds) | \~1.2 seconds                   |

The `blockTimeMsP50ByShard` field in `networkStats` gives the current median block time per shard. For cross-shard transactions, sum both shards.

## Adaptive gas pricing example

<Tabs>
  <Tab title="cURL">
    ```bash theme={"system"}
    printf '{"action":"subscribe","topic":"gasStats"}\n' \
      | websocat wss://relayer.xoxno.com/ws
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={"system"}
    const ws = new WebSocket('wss://relayer.xoxno.com/ws');
    const gasStats = new Map(); // shard → latest stats

    ws.onopen = () => {
      ws.send(JSON.stringify({
        action: 'subscribe', topic: 'gasStats'
      }));
    };

    ws.onmessage = (e) => {
      const msg = JSON.parse(e.data);
      if (msg.type === 'gasStats') {
        gasStats.set(msg.data.shard, msg.data);
      }
    };

    function getGasPrice(shard, priority = 'normal') {
      const stats = gasStats.get(shard);
      if (!stats) return 1_000_000_000; // network minimum

      const p = stats.gasPrice.percentiles;
      switch (priority) {
        case 'low':    return stats.gasPrice.bucketAvg;
        case 'normal': return p.p75;
        case 'high':   return p.p90;
        default:       return p.p75;
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"system"}
    import asyncio
    import json
    import websockets

    gas_stats: dict[int, dict] = {}

    async def listen():
        async with websockets.connect("wss://relayer.xoxno.com/ws") as ws:
            await ws.send(json.dumps({
                "action": "subscribe", "topic": "gasStats"
            }))
            async for msg in ws:
                data = json.loads(msg)
                if data.get("type") == "gasStats":
                    gas_stats[data["data"]["shard"]] = data["data"]

    def get_gas_price(shard: int, priority: str = "normal") -> int:
        stats = gas_stats.get(shard)
        if not stats:
            return 1_000_000_000

        p = stats["gasPrice"]["percentiles"]
        if priority == "low":
            return stats["gasPrice"]["bucketAvg"]
        elif priority == "high":
            return p["p90"]
        else:
            return p["p75"]
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={"system"}
    use futures_util::{SinkExt, StreamExt};
    use tokio_tungstenite::{connect_async, tungstenite::Message};

    let (mut ws, _) = connect_async("wss://relayer.xoxno.com/ws").await?;

    let subscribe = serde_json::json!({
        "action": "subscribe",
        "topic": "gasStats"
    })
    .to_string();

    ws.send(Message::Text(subscribe.into())).await?;

    while let Some(message) = ws.next().await {
        let text = message?.into_text()?;
        let event: serde_json::Value = serde_json::from_str(&text)?;

        if event["type"] == "gasStats" {
            println!("{}", event["data"]["gasPrice"]["percentiles"]["p75"]);
        }
    }
    ```
  </Tab>
</Tabs>

## Practical guidelines

**Do:**

* Subscribe to `gasStats` before your first transaction; the first message arrives immediately
* Default to `p75` for user-facing swaps
* Escalate to `p90` during periods of high network activity
* Use `bucketAvg` for background or non-urgent operations

**Avoid:**

* Using the raw `avg` field (spam volume biases it low)
* Setting `gasPrice` below `1,000,000,000` (the network rejects it)
* Caching gas stats beyond a few seconds; conditions shift within a single block

## Related pages

<CardGroup cols={2}>
  <Card title="Gas statistics" icon="chart-bar" href="/relayer/gas-statistics">
    Full gasStats message schema and field descriptions.
  </Card>

  <Card title="Network stats" icon="globe" href="/relayer/network-stats">
    networkStats stream with mempool pressure data.
  </Card>
</CardGroup>
