# Welcome

**What blockchain rails and infra stack can support the bulk of global trading volumes?**

Fermi Labs was born to find the most optimized, performant, answer to this question. From the ground up, we've designed a custom blockchain stack - with one, invariant constraint:

> The market microstructure of onchain orderbooks must match offchain orderbooks.

Previous blockchains all make severe compromises on this front, leading to orderbooks which are **strictly worse** than offchain counterparts. We don't expect to onboard the world by offering an inferior product. Instead, Continuum ChaIn offers feature-parity with NASDAQ/NYSE, while offering blockchain guarantees of trustlessness, immutability, and verifiability.

Welcome to the verifiable finance revolution.

### Jump in:

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><i class="fa-bolt">:bolt:</i></h4></td><td><a href="/pages/QPzbTvC6XsT5gERiU43E"><strong>Overview</strong></a></td><td>Understand what we do</td><td></td><td></td><td><a href="/pages/7FvWQMF0kTK7HGhlQfmo">/pages/7FvWQMF0kTK7HGhlQfmo</a></td></tr><tr><td><h4><i class="fa-leaf">:leaf:</i></h4></td><td><a href="/pages/pDaGxKSfMNixioNjU7ht"><strong>Run a full node</strong> </a></td><td>Learn how to participate in the network</td><td></td><td></td><td><a href="https://github.com/GitbookIO/gitbook-templates/blob/main/product-docs/broken-reference/README.md">https://github.com/GitbookIO/gitbook-templates/blob/main/product-docs/broken-reference/README.md</a></td></tr><tr><td><h4><i class="fa-globe-pointer">:globe-pointer:</i></h4></td><td><a href="/pages/7FvWQMF0kTK7HGhlQfmo"><strong>T</strong></a><a href="/pages/7FvWQMF0kTK7HGhlQfmo"><strong>rade on Fermi Trade</strong></a></td><td>Place trades on the most powerful perps DEX ever.</td><td></td><td></td><td><a href="/pages/QPzbTvC6XsT5gERiU43E">/pages/QPzbTvC6XsT5gERiU43E</a></td></tr></tbody></table>


# Quickstart

Fermi Trade is a powerful trading platform, built on a custom Layer-1 Blockchain, Continuum.

<figure><img src="/files/w5ee853Hply2QIIKvxNe" alt=""><figcaption></figcaption></figure>

In these docs, you can find more information about the architecture, performance, and security properties of Continuum/Fermi Trade. You can also learn more about how to trade on Fermi, how to use programmatic tools/SDK, how to monitor transactions via the block exploer/API, and how to run a full node.

Here is a quick video on how to use the Fermi Trade platform to trade perps. it's currently live on testnet, watch out for announcements of mainnet!

{% hint style="info" %}
Want to learn about writing content from scratch? Head to the [Basics](/architecture/editor) section to learn more.
{% endhint %}

### Explorer

You can view your transaction history, trades, and balances, using the continuum explorer:

<div data-full-width="false"><figure><img src="/files/wZGjFoKeD1r7QajyrS6j" alt=""><figcaption></figcaption></figure></div>

### Sequencing/Execution Tabs

Unlike typical explorers, you will find two tabs - sequencing and execution. This is because sequencing and execution are handled by different layers of the stack on Continuum Blockchain.


# Running a full node

Interested in running a full node? Read on below for a guide

Testnet Full nodes are currently permissioned, but we typically respond fast to any request for access. Please email us at <validators@fermilabs.xyz> , or raise a ticket on discord, for an API key. The rest of the guide assumes you have a valid API key already.

Fermi Trade aims to allow any user to verify the state and execution of its onchain perpetuals orderbook program - including margin calculation, order matching, settlement logic, interest rate calculation, and honest queue positioning. A user may do so by running a light node, or a full node.

### Hardware requirements

Since continuum is a high throuput chain, running a full node and keeping up with state requires a reasonably well-speced machine. Recommended Machine specs:\
\
**RAM:** >64 GB

**Disc:** > 6 TB

**Bandwidth:** > 300 Mbps

### 1. Download the Full Node Binary

Full node binary is available on request.

### 2. Gossip & Restore state (catch up to present state)

To get your full node up to the current state, you have two options:\
1\. Full replay from genesis - replay all transactions since inception.\
2\. Restore from snapshot - quickly get up to speed on the current state

### Gossip Restore Endpoints

Allows whitelisted IPs to request SledDB data for bootstrapping new nodes.

**GossipReceiver (`gossip_stream.rs`)**

For receiving and processing gossip streams:

```rust
pub struct GossipReceiver {
    explorer: Arc<BlockExplorer>,
}
```

Methods:

* `receive_full_dump(stream)` - Process full dump stream
* `receive_delta_restore(stream)` - Process delta restore stream

#### Endpoints

| Endpoint                    | Method | Description             | Response                               |
| --------------------------- | ------ | ----------------------- | -------------------------------------- |
| `/gossip/status`            | GET    | Discovery endpoint      | JSON with available snapshots, heights |
| `/gossip/snapshot/latest`   | GET    | Get latest snapshot     | Binary (bcs-encoded StoredSnapshot)    |
| `/gossip/full-dump`         | GET    | Stream entire SledDB    | Chunked binary stream                  |
| `/gossip/delta/:from_block` | GET    | Snapshot + blocks since | Chunked binary stream                  |

All endpoints:

* Check IP whitelist before processing
* Return 403 Forbidden if IP not allowed
* Return 503 Service Unavailable if gossip disabled

#### CLI Arguments

```
--gossip-config <PATH>  Path to gossip configuration TOML file
```

***

### Example

Use a whitelisted IP. You can also directly start up you full node with the appropriate gossip flags pointing to the public RPC:

```
fermi-rollup-node \                                                                                         
    --bootstrap-mode gossip-snapshot \                                                                        
    --gossip-peer http://10.0.0.5:8080 \         
```

Test endpoints:

```bash
# Status
curl http://localhost:8080/gossip/status

# Latest snapshot
curl http://localhost:8080/gossip/snapshot/latest -o snapshot.bin

# Full dump (streaming)
curl http://localhost:8080/gossip/full-dump -o dump.bin

# Delta from block 0
curl http://localhost:8080/gossip/delta/0 -o delta.bin
```

1. Test from non-whitelisted IP - should get 403 Forbidden
2. Modify config file - should hot-reload (check logs for "gossip config reloaded successfully")

####


# Continuum Overview

Continuum is a modular blockchain with separation of concerns - Sequencing, Execution, Consensus, and DA are handled at different levels of the stack.

The architecture of the Continuum blockchain is focused on optimizing market microstructure for onchain orderbooks. With this goal in mind, we've rewritten various parts of the stack.

<figure><img src="/files/ZZKtrmsG859zBYPlY3zu" alt=""><figcaption></figcaption></figure>

### Performance Optimization

It is critical that Continuum Chain be able to handle the maximum amount of transactions that a single threaded system would allow. In attempting to approach this theoretical limit, we've extensively optimized various parts of the blockchain stack, from ordering to execution to consensus. Very importantly, we've moved consensus out of the hot path for sequencing finality - this is the big change that allows ordering finality to reduce from 100-400 ms down to 1-2 ms for the first time.

### Transaction Flow

{% stepper %}
{% step %}

#### Order Submitted to Sequencer

Order may be unencrypted or timelock encrypted; submitted to Sequencer, which produces an instant signed reciept including the exact "Tick" at which the transaction was included (90 microseconds granular). Inclusion and ordering is **Final** at this time.&#x20;
{% endstep %}

{% step %}

#### Orders consumed by execution layer

The "leader" is just another full node that executes the state transition function upon the ordered list of transactions produced by the Sequencer. Since ordering and inclusion is already final, execution is **purely deterministic** - there is no leader privledge or discretion like other blockchains.
{% endstep %}

{% step %}

#### contBFT voting

Once the "leader" produces a finalized block, all validators vote on the correctness of the execution of the STF given the ordered tx. list from (1). This step is important for **economic finality**, but effective finality is reached right after (1). Crucially, there are no fork choice rules needed, as the execution layer can only produce ONE valid block from an ordered list of transactions.
{% endstep %}
{% endstepper %}


# Proof of Sequence

Proof of Sequence embeds transactions in a proof of elapsed time. It achieves verifiable fair ordering layer subject to VDF limits.

## POSq

POSq is designed to produce a canonical order of transactions at the protocol level. Unlike other blockchains that leave sub-block ordering to the discretion of the validator, on Continuum disordering transactions causes an invalid state transition function. (**protocol enshrined ordering**).\
\
The core mechanism of the Proof of Sequence is built on a recursive RSA-Group VDF, producing an ongoing proof of elapsed time, and anchoring transactions in that timechain. Transaction hashes are embedded in each sequential "tick" of VDF computation, making it prohibitively difficult to rewrite the past chain rapidly.&#x20;

![](/files/26b9BZiIWbslp69vGTEM)\
\
Sequenced transactions are continously streamed by the leader to all other validators (turbine like propagation planned if validator set is to be expanded beyond \~80). There is no delay for execution, and the leader signs all included ticks, committing to the order of transactions. \
\
Unlike just inclusion receipts, this prevents rewriting of history structurally, especially after a significant delay.  Even if the users are not constantly comparing receipts to detect misordering, this makes reordering structurally hard, and detectable by other validators.

#### Proof of Inclusion - Reciepts

Immediately on tx. reciept, the leader produces an instant proof of inclusion - a signed reciept, including the:\
i)  Transaction hash, and  \
ii) VDF hash at the inclusion tick number.\
\
For colocated clients (i.e. in same AZ as the leader), confirmations are to be expected in <1ms. This enshrines the position of the transaction in the timechain, and serves as a simple fraud proof if the sequencer produces another signed timechain with a different transaction at that tick. \
\
Targeted, selective delay of transactions is prevented by Encryption (described below):\
\
Wide, significant delay in inclusion of transactions should be detectable by by the social layer (leading to forced rotation), and is unlikely to yield much benefit to the sequencer.\
\
In regular operations, all users can essentially treat proof of inclusion as a binding proof of order. More details on #[finality](/architecture/contbft#finality).

### Timelock Encryption

To make reordering structurally infeasible even by the leader, users can opt for timelock encryption (details in white paper). This leads to blind ordering, with the transaction not decrypting till the proof of inclusion has been formed.\
\
**The** **combined effect of the three componensts is Verifiable Ordering** \
\
To illustrate, assume the sequencer wants to frontrun a transaction, and wishes to take the following steps:\
\
*At tick t* (let's say 0 ms) the transaction arrives. It is included in the timechain. \
\
Then at *tick t+10ms*, the transaction is decrypted. by then, the vdf computation has advanced, and as long as the difficulty retargeting is accurate, it becomes nearly infeasible for the sequencer to redo the chain rapidly. This is especially true at scale. This is essentially reduces to, for eg. the following unfeasible requirement to disguise the reordering\
\
where the transaction stream being broadcast is observed in real time by other validators. The key distinction is that \
\
in the naive case (no VDF), say a **10 ms delay** could at worst be attributed to jitter - allowing the sequencer to decrypt the transaction and frontrun it.\
\
In the POSq scheme, to plant a transaction 10ms in the past, the sequencer would have to catch up to 10ms of computation. This is hard to do in short order (would require a 20%-30%+ faster VDF machine, unlikely if tuned correctly). The problem then reduces to something like:\
\
&#x20;*in the next 40ms, produce a VDF of 50ms*\
\
Now, to complete the deception, the sequencer would have to justify a **>100ms** streaming delay, to create a forged timechain. 100ms is in any case considered a liveness fault, leading to sequencer rotation.\
\
**Note on VDF Difficulty**

VDFs are hard in a somewhat analgous way to Proof Of Work, in that they require a target time to compute on best case hardware - execept unlike POW, VDF calculations are not parallizable. Nevertheless, to account for VDF improvements, difficulty retargetting to maintain tick cadence is built in.\
\
The current design targets 27 iterations per 100 Microseconds, optimize

{% hint style="info" %}
This is subject to hardware limits of single threaded computation by design. V1 relies on an optimized CPU execution, while V2 will require FGPAs (and eventually potentially ASICs at the limit) - to provide security of elapsed time at the physical limit of available machines at a given time.
{% endhint %}

**User Flow**

<figure><img src="/files/146ZkevuIGAJuOZW0miH" alt=""><figcaption></figcaption></figure>

Users submit (optionally timelock encrypted) transactions to the Sequencer. The sequencer maintains a cryptographic, VDF-based "clock" as a trustless proof of elapsed time / timekeeping device which is not tamperable.\
\
Upon submitting the transaction, it is instantly assigned a canonical queue position (a VDF "tick"), and it is instantly broadcast to full nodes - in a continuous manner, for execution. In this way, transaction inclusion and execution is final the moment the transaction is accepted by the sequencer - there is no batching or waiting for blocks to learn the fate of the transaction. For all intents and purposes, transaction inclusion and execution is **Continous**.

{% hint style="info" %}
Blocks produces distinct boundaries around which reordering is profitable in classical blockchains. In continuum, transaction processing is continuous. Batches are put into "blocks" at a later stage in deterministic fashion, purely for voting and economic finality - however from a user or full node perspective, execution remains real time with no natural boundaries.\
\
POSq is a natural successor to previous onchain timekeeping protocols like Proof Of History (POH). While the tech stack is explained in more detail in our whitepaper , we examine the core sequencing primitive briefly here.
{% endhint %}


# Execution

Fermi Trade runs on a custom execution stack built on soverign sdk

Fermi Trades execution logic is written in low level Rust using the [soverign SDK](https://www.sovereign.xyz/). A full node binary is available for download. This custom logic deterministically executes the state transition function (STF) for the Fermi Trade Perps DEX.\
\
For real time verification of state, you can review the guide on running a full node:

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/pDaGxKSfMNixioNjU7ht">Running a Full node</a></td></tr><tr><td><a href="/pages/M0iU3YptYD3hENKIrmSs">Matching Logic</a></td></tr><tr><td><a href="https://github.com/Sovereign-Labs/sovereign-sdk/tree/nightly/crates/module-system/module-implementations/sov-bank">Bank Logic</a></td></tr></tbody></table>

{% hint style="info" %}
In the future, we plan to introduce a general purpose VM on top of Continuum. This can be neatly made interoperable with existing state - using modules such as sov-svm and sov-evm.
{% endhint %}


# ContBFT

ConfBFT is designed for consensus over deterministic blocks

### Overview

ContBFT is the consensus layer for the Continuum sequencer, implemented as a modified HotStuff protocol in `/contBFT/hotstuff_rs/`. It provides Byzantine fault-tolerant finality of transactions and the state transition function.

### Key Architectural Choices

Let's refer back to the diagram from the overview:

<figure><img src="/files/q94hqo2qhnOaZgWYdjCr" alt=""><figcaption></figcaption></figure>

### Block Production

"Blocks" in continuum are pre-defined sections of ticks that upon which\
A deterministic State Transition Function (STF) has been executed:

$$
S\_2 ;=; \mathrm{STF}!\left(S\_1,; \overrightarrow{\mathcal{T}}\right)
$$

Where,

$$
S\_1:\ \text{initial state},  S\_2:\ \text{final state},
$$

$$
\overrightarrow{\mathcal{T}} ;=; {, t\_1, t\_2, \ldots, t\_n ,} \quad\text{(ordered set of transactions)}
$$

### Determinism

The block application process is purely deterministic, and the leader has no discretion as the ordered list of transactions has already been produced and shared in real time.\
\
Due to [Proof of Sequence](/architecture/sequencing), reordering or proposing alternate timelines to different validators constitutes a faulty STF - leading to leader rotation (and potentially slashing).&#x20;

Deterministic blockchains have an interesting property - all full nodes can compute the next block simultaneously (given only their network latency from the ContSEQ stream). This allows us to skip the "propose block" step in consensus, and go directly to voting on the root hash calculated by every validator.

### Finality

**Effective Finality:** Due to the deterministic execution design, validators and full nodes recieving the POSq transaction stream can build the execution layer and updated state themselves. \
\
This leads to real-time effective finality, subject only to the physical latency between the Validator/Full node from the leader. This is a stronger guarantee than soft confirmations, as not respecting the POSq stream is considered a protocol level fault.\
\
**Economic Finality**\
Economic Finality is designed as a consensus backstop to misbehavior, rather than actively making choices (such as fork choice) in the usual hot path. Deterministic blocks are voted on every 100 ms and "finalized". \
\
Importantly, this doesnt prevent users from reacting in real time to the transaction stream. Consensus based rollbacks are designed as a backstop only in rare cases, where there is equivocation or misbehavior (or potentially liveness fault) on the part of the leader. \
\
In regular operations, all users can essentially treat proof of inclusion as a binding proof of order.

### Safety Guarantees

1. **No conflicting commits:** Locked PhaseCertificate mechanism prevents forks.
2. **Liveness:**\
   On-fault Sequencer rotation is active on testnet v2. Since the other properties of sequencer rotation (i.e.  Censorship resistance, MEV distribution) are achieved by other mechanisms (i.e. <br>
3. **Deterministic finality** No probabilistic confirmation; committed is final


# Security Properties & Risks

This page elaborates on the claimed security properties, as well as known risks, of the continuum model.

Risks unique to the continuum L1 architecure, boil down to the use of a single sequencer in v1 (we're exploring sequencer rotation for future version, but low latency remains a top priority).\
\
The sequencer is heavily constrained, cryptographically (VDF for timekeeping + blind order commitments), so the risk profile is different from naive single sequencer models (eg. L2s like Arbitrum, Optimism). Nevertheless its important to highlight two classes of risk, and what can be done to mitigate them over the long run.\
\
&#x20; **1. Liveness Risk**\
The sequencer is designed to auto rotate when a collective of validators vote that a leader is unreachable for >50ms. This is intended to be a rare occurrence, and can lead to slashing.<br>

2. **Indiscriminate Censorship / Griefing**

This risk comes down to the fact that while the sequencer cannot perform targetted sandwiching, it could indisriminately drop transactions (eg. drop 20% of txns randomly). The crux of this risk is that its deterimental to the network but not beneficial to the sequencer (negative-sum), so a rational sequencer shouldn't engage in this. Secondly, it is more easily detetected than targetted censorship - thus can easily lead to slashing and rotation of the sequencer.


# RPC reference for Continuum Chain

Interacting with Continuum Chain Testnet

## Direct Communication

## 1 . StreamTx&#x20;

GRPC endpoint / Kafka Producer that allows you to stream transactions in real time. Combined with colocation and running your own full node, this allows you to calculate the canonical state of the chain in **1-2 ms** at any given time.

Note that StreamTx is subject to aggressive rate limits and blocks - ensure your machine has enough RAM and bandwidth to keep up with the stream (approx 32 Mbit/s).

*Colocation Tips:* Currently the continuum sequencer operates out of EU-frankfurt Availability Zone. Select this option for \~1ms round trip latency.

## 2. SubmitTx

This allows for direct submission of transactions to continuum via grpc for minimized latency. Rate limits apply.

### RPC Endpoints

In addition, we also offer historical query APIs, via REST. you can use our official RPC endpoints, or point to any full node, to access this data.\
\
Our official RPC endpoint is:&#x20;

```
https://rpc.fermilabs.xyz/
```

#### Example usage:

```
curl https://rpc.fermilabs.xyz/status
```

#### List Blocks

Retrieve a paginated list of blocks, ordered by height (descending).

```
GET /blocks?limit=<n>&offset=<n>
```

**Parameters:**

| Parameter | Type | Default | Description                          |
| --------- | ---- | ------- | ------------------------------------ |
| `limit`   | int  | 20      | Number of blocks to return (max 100) |
| `offset`  | int  | 0       | Number of blocks to skip             |

**Example:**

```
curl "http://rpc.fermilabs.xyz/blocks?limit=10&offset=0"
```

**Response:**

```
[
  {
    "height": 3343881,
    "state_root": [18, 115, 186, ...],
    "applied_batches": 815368,
    "applied_orders": 7,
    "produced_at": 1767873641,
    "total_orders": 5,
    "total_cancels": 0,
    "batch_summaries": [...],
    "transaction_ids": ["0000000000330609-0000-0000", ...],
    "event_ids": ["0000000000330609-0001-0000", ...]
  }
]
```

***

#### Get Latest Block

Retrieve the most recent block with full transaction and event details.

```
GET /blocks/latest
```

**Example:**

```
curl "http://rpc.fermilabs.xyz/blocks/latest"
```

**Response:**

```
{
  "block": {
    "height": 3343881,
    "state_root": [...],
    "produced_at": 1767873641,
    "total_orders": 5,
    "total_cancels": 0,
    "batch_summaries": [...]
  },
  "transactions": [...],
  "events": [...]
}
```

***

#### Get Block by Height

Retrieve a specific block with full transaction and event details.

```
GET /blocks/:height
```

**Example:**

```
curl "http://rpc.fermilabs.xyz/blocks/3343881"
```

**Response:**

```
{
  "block": {
    "height": 3343881,
    "state_root": [...],
    "applied_batches": 815368,
    "applied_orders": 7,
    "produced_at": 1767873641,
    "total_orders": 5,
    "total_cancels": 0,
    "batch_summaries": [
      {
        "index": 0,
        "tick_number": 49858042756,
        "order_count": 1,
        "cancel_count": 0,
        "continuum_sequences": [11224478268823197984],
        "batch_hash": "927f4cfa6c3208163c3ef1081bf3b3d09c5a6e677c136283c152ef6dd7ae3bba"
      }
    ],
    "transaction_ids": [...],
    "event_ids": [...]
  },
  "transactions": [
    {
      "id": "0000000000330609-0000-0000",
      "block_height": 3343881,
      "batch_index": 0,
      "kind": "order",
      "market_id": "6f9ee497-1756-5bbd-b512-36cee35add8f",
      "market_name": "SOL-PERP",
      "market_kind": "Perp",
      "owner": "DeJpkURbXgmFvsi6dj9RX6rybuLiY7kgXzfFGZNWrte7",
      "side": "Sell",
      "price": 133750000,
      "quantity": 1000000000,
      "order_id": 39263332,
      "timestamp_ms": 1767871425638,
      "continuum_sequence": 11224478268823197984,
      "signature": "981259e9..."
    }
  ],
  "events": [...]
}
```

***

### Transaction Endpoints

#### Get Transaction by ID

Retrieve a specific transaction by its ID.

```
GET /transactions/:id
```

**Transaction ID Format:** `{block_height_hex}-{batch_index_hex}-{tx_index_hex}`

**Example:**

```
curl "http://rpc.fermilabs.xyz/transactions/0000000000330609-0000-0000"
```

**Response:**

```
{
  "id": "0000000000330609-0000-0000",
  "block_height": 3343881,
  "batch_index": 0,
  "kind": "order",
  "market_id": "6f9ee497-1756-5bbd-b512-36cee35add8f",
  "market_name": "SOL-PERP",
  "market_kind": "Perp",
  "owner": "DeJpkURbXgmFvsi6dj9RX6rybuLiY7kgXzfFGZNWrte7",
  "side": "Sell",
  "price": 133750000,
  "quantity": 1000000000,
  "base_mint": "11111111111111111111111111111112",
  "quote_mint": "11111111111111111111111111111113",
  "order_id": 39263332,
  "timestamp_ms": 1767871425638,
  "continuum_sequence": 11224478268823197984,
  "signature": "981259e9223eef01200204e20dfca3fb5010342363a61a2729f04d4ac5bf02798537f9359afc5d60129c241cfeb93197d7589b8751bfa7b399ee14138aeadf02"
}
```

**Transaction Kinds:**

* `order` - New order placement
* `cancel` - Order cancellation

***

### Event Endpoints

#### List Events

Retrieve a paginated list of events, optionally filtered by market.

```
GET /events?limit=<n>&offset=<n>&market_id=<uuid>
```

**Parameters:**

| Parameter   | Type   | Default | Description                          |
| ----------- | ------ | ------- | ------------------------------------ |
| `limit`     | int    | 50      | Number of events to return (max 200) |
| `offset`    | int    | 0       | Number of events to skip             |
| `market_id` | string | -       | Filter by market UUID (optional)     |

**Example:**

```
# All events
curl "http://rpc.fermilabs.xyz/events?limit=50"

# Events for specific market
curl "http://localhost:8080/events?market_id=6f9ee497-1756-5bbd-b512-36cee35add8f"
```

**Response:**

```
[
  {
    "id": "0000000000330609-0001-0000",
    "block_height": 3343881,
    "batch_index": 1,
    "market_id": "6f9ee497-1756-5bbd-b512-36cee35add8f",
    "market_name": "SOL-PERP",
    "applied_orders": 7,
    "batch_hash": "d311ca7d066a9bc2e3d9f635615dd5f8dd7871e093070d30fb1cd67fbfd4cf28"
  }
]
```

***

### WebSocket Subscriptions

#### Subscribe to New Blocks

Real-time block notifications via WebSocket.

```
WS /ws/blocks
```

**Example (using websocat):**

```
websocat ws://localhost:8080/ws/blocks
```

**Behavior:**

1. On connection, immediately receives the latest block details
2. Subsequently receives new blocks as they are produced

**Message Format:** Same as `GET /blocks/:height` response

***

### Query Patterns

#### Get Transactions in a Time Range

The API doesn't support direct time-range queries. Use this pattern:

```
# 1. Fetch blocks and filter by timestamp (produced_at is Unix timestamp)
START_TIME=1767870000
END_TIME=1767873000

curl -s "http://rpc.fermilabs.xyz/blocks?limit=1000" | \
  jq --argjson start $START_TIME --argjson end $END_TIME \
  '[.[] | select(.produced_at >= $start and .produced_at <= $end)]'

# 2. Get full details for blocks in range
curl "http://rpc.fermilabs.xyz/blocks/3343881"
```

#### Get All Transactions for a User

```
# Get user's open orders (current state)
curl "http://rpc.fermilabs.xyz/orders/user/DeJpkURbXgmFvsi6dj9RX6rybuLiY7kgXzfFGZNWrte7"

# For historical transactions, iterate through blocks
# and filter by owner field
```

#### Get Transactions for a Market

```
# Filter events by market, then fetch associated blocks
curl "http://rpc.fermilabs.xyz/events?market_id=6f9ee497-1756-5bbd-b512-36cee35add8f&limit=100"
```

#### Pagination Example

```
# Page 1 (blocks 0-99)
curl "http://rpc.fermilabs.xyz/blocks?limit=100&offset=0"

# Page 2 (blocks 100-199)
curl "http://rpc.fermilabs.xyz/blocks?limit=100&offset=100"

# Page 3 (blocks 200-299)
curl "http://rpc.fermilabs.xyzzx. /blocks?limit=100&offset=200"
```

***

### Data Models

#### BlockRecord

| Field             | Type      | Description                      |
| ----------------- | --------- | -------------------------------- |
| `height`          | u64       | Block height (sequential)        |
| `state_root`      | \[u8; 32] | Merkle root of state after block |
| `applied_batches` | u64       | Cumulative batch count           |
| `applied_orders`  | usize     | Orders matched in this block     |
| `produced_at`     | u64       | Unix timestamp (seconds)         |
| `total_orders`    | usize     | Total order transactions         |
| `total_cancels`   | usize     | Total cancel transactions        |
| `batch_summaries` | array     | Batch details (see below)        |
| `transaction_ids` | array     | Transaction IDs in this block    |
| `event_ids`       | array     | Event IDs in this block          |

#### BatchSummary

| Field                 | Type   | Description                |
| --------------------- | ------ | -------------------------- |
| `index`               | u32    | Batch index within block   |
| `tick_number`         | u64    | Continuum tick number      |
| `order_count`         | usize  | Orders in batch            |
| `cancel_count`        | usize  | Cancels in batch           |
| `continuum_sequences` | array  | Continuum sequence numbers |
| `batch_hash`          | string | SHA256 hash of batch       |

#### ExplorerTransaction

| Field                | Type   | Description                          |
| -------------------- | ------ | ------------------------------------ |
| `id`                 | string | Unique transaction ID                |
| `block_height`       | u64    | Block containing this tx             |
| `batch_index`        | u32    | Batch index within block             |
| `kind`               | string | `"order"` or `"cancel"`              |
| `market_id`          | string | Market UUID                          |
| `market_name`        | string | Human-readable market name           |
| `market_kind`        | string | `"Spot"` or `"Perp"`                 |
| `owner`              | string | Base58 public key                    |
| `side`               | string | `"Buy"` or `"Sell"` (orders only)    |
| `price`              | u64    | Price in quote units (orders only)   |
| `quantity`           | u64    | Quantity in base units (orders only) |
| `base_mint`          | string | Base token mint (orders only)        |
| `quote_mint`         | string | Quote token mint (orders only)       |
| `order_id`           | u64    | Client order ID                      |
| `timestamp_ms`       | u128   | Submission timestamp (ms)            |
| `continuum_sequence` | u64    | Continuum sequence number            |
| `signature`          | string | Ed25519 signature (hex)              |

#### ExplorerEvent

| Field            | Type   | Description                 |
| ---------------- | ------ | --------------------------- |
| `id`             | string | Unique event ID             |
| `block_height`   | u64    | Block containing this event |
| `batch_index`    | u32    | Batch index within block    |
| `market_id`      | string | Market UUID                 |
| `market_name`    | string | Human-readable market name  |
| `applied_orders` | usize  | Orders matched              |
| `batch_hash`     | string | Associated batch hash       |

***

### Node Status

#### Get Node Status

```
GET /status
```

**Response:**

```
{
  "block_height": 3343881,
  "state_root": [...],
  "applied_batches": 815368
}
```

***

### Storage Details

Block explorer data is persisted to the sled database at the path specified by `--db-path` (default: `./rollup_data`).

**Storage location:** `{db-path}/db/`

**Stored data:**

* `explorer.blocks` - Block records indexed by height
* `explorer.transactions` - Transactions indexed by ID
* `explorer.events` - Events indexed by ID

Data persists across node restarts and is automatically loaded on startup.


# SDK Usage for Fermi Trade

This page outlines how to programatically place orders and monitor positions on Fermi Trade, via Fermi SDK

Fermi SDK makes it easy for people to develop and execute their own trading strategies directly on our Fermi Trade Perpetuals Exchange. It is written in rust and open sourced here, so that you can add you own custom logic for order placement, cancellation, and modification.

The Fermi SDK is public here: <https://github.com/Fermi-DEX/Fermi-Trade-SDK>\
\
Follow the instructions in the README.md to get setup, and feel free to reach out on discord #Support if you need help!


# Using Continuum Sequenced Txns.

Any application requiring Fair Ordering an utilize continuum as a cryptographic ordering primitive.

Besides our flagship Fermi Trade platforms, other applications can be built on the FIFO ordered continuum transaction stream. Our recommended approach to doing so is outlined below.

<figure><img src="/files/pdYjOm9dzK70HJa4SqQ3" alt=""><figcaption></figcaption></figure>

Recommended flow:\
1\. Add a custom app header to all your transactions\
2\. After sequencing, use a centralised / elected leader as relayer, to bundle the transactions for onchain submission (with sequence numbers)\
3\. Append the transactions to an onchain queue, where transactions can only be popped off in order.\
4\. Any user can call "crank" to process the next N transactions in the queue.

{% hint style="info" %}
Transaction queing is limited by the throughput and transaction data limits of the underlying execution/state layer you choose. This is often the bottleneck - which is why we encourage building on the full continuum stack.&#x20;

Continuum Chain (Full Stack) will be opened up to permissionless building in the future - using sov-evm and sov-svm modules to run custom applications on the execution layer.
{% endhint %}


# Funding Rate Policies

Funding rates are the mechanism by which perpetual contract prices converge to the underlying index price. Payments flow between long and short traders based on the spread between mark and index prices.

#### Calculation

The funding rate is computed in basis points (bps) using the formula:

```
base_rate_bps = (mark_price - index_price) / index_price * 10,000
effective_rate_bps = base_rate_bps * ticks_elapsed
```

Where `ticks_elapsed` is the number of funding intervals since the last application.

#### Tick-Based Intervals

Funding is applied on a tick schedule rather than continuously:

* **Funding tick** = `timestamp / funding_interval`
* Funding only applies when `current_tick > last_funding_tick`
* Skipped if `index_price == 0` or `mark_price == index_price`

This batching approach ensures deterministic funding across all nodes.

#### Payment Direction

| Condition      | Long Traders | Short Traders |
| -------------- | ------------ | ------------- |
| `mark > index` | Pay          | Receive       |
| `mark < index` | Receive      | Pay           |

Payment amount per position:

```
notional = base_position * mark_price
payment = -(notional * effective_rate_bps / 10,000)
```

The negation ensures longs pay when the rate is positive (mark above index).

#### State Updates

On each funding application:

1. `collateral` balances adjusted by payment amount
2. `realized_pnl` updated on each position
3. Market state records:
   * `last_funding_tick` — prevents duplicate payments
   * `cumulative_funding_bps` — running total for historical reference
   * Updated `mark_price` and `index_price`

#### Zero-Sum Property

Funding is always zero-sum between counterparties. The system does not create or destroy value through funding—it only transfers between longs and shorts.


# Ordering & Inclusion

Order sequencing is deterministic, derived from the Continuum sequencing layer.

#### Continuum Sequence

Every order receives a globally unique sequence number:

```
continuum_sequence = (tick_number << 32) | sequence_in_tick
```

* `tick_number` — Block height from Continuum (VDF-proven)
* `sequence_in_tick` — Position within that tick (0-indexed)

This replaces timestamp-based ordering to ensure all nodes process orders identically.

#### Order Structure

```rust
TickOrder {
    order_id: OrderId,
    market_id: MarketId,
    account_id: AccountId,
    side: Side,                    // Buy | Sell
    price: i64,
    quantity: u64,

    // Sequencing
    tick_number: u64,
    sequence_in_tick: u64,
    continuum_sequence: u64,       // PRIMARY KEY

    // Perpetuals
    leverage: Option<u64>,
    position_effect: Option<PositionEffect>,  // Open | Close
    reduce_only: bool,
    margin_mode: Option<MarginMode>,          // Cross | Isolated
    liquidation: bool,             // System-generated liquidation
}
```

#### Inclusion Rules

1. **Zero-quantity rejection** — Orders with `quantity == 0` are rejected at insertion
2. **Margin validation** — Orders must pass `reserve_margin_for_order()` before inclusion
3. **Signature verification** — All orders are Ed25519-signed; invalid signatures rejected

#### Priority Queue Processing

Orders are processed via a binary min-heap keyed by `continuum_sequence`:

```
price_updates → triggers → regular_orders → liquidations
```

Within each category, earlier sequences execute first (FIFO from Continuum).

#### Cancellation Handling

Cancellations use the same `continuum_sequence` ordering:

```rust
TickCancellation {
    order_id: OrderId,
    continuum_sequence: u64,
}
```

Cancels are processed in sequence order alongside new orders, ensuring deterministic interleaving.

***


# Matching Logic

Core price-time prioerity logic explained

The matching engine implements a continuous limit order book with price-time priority.

#### Order Book Structure

```rust
Orderbook {
    bids: BTreeMap<OrderbookKey, Order>,  // Sorted high-to-low
    asks: BTreeMap<OrderbookKey, Order>,  // Sorted low-to-high
}

OrderbookKey {
    price: i64,      // Negated for bids
    timestamp: u64,
    order_id: OrderId,
}
```

* **Bids**: Price negated so `BTreeMap` iteration yields highest prices first
* **Asks**: Price as-is so iteration yields lowest prices first
* **Tie-breaking**: Secondary sort by `(timestamp, order_id)` for FIFO at same price level

#### Matching Algorithm

```
while best_bid.price >= best_ask.price:
    1. Prune any zero-quantity orders at top of book
    2. Check crossing conditions (price, market_id, self-trade)
    3. Determine maker (earlier timestamp wins)
    4. Execute at maker's price
    5. Update quantities; remove exhausted orders
```

#### Maker Determination

```rust
maker_is_bid = (bid.timestamp, bid.order_id) <= (ask.timestamp, ask.order_id)
price = if maker_is_bid { bid.price } else { ask.price }
```

The order that arrived first is the maker; the match executes at the maker's price. This protects passive liquidity providers.

#### Partial Fills

When order quantities differ:

```rust
fill_quantity = bid.quantity.min(ask.quantity)
```

* Smaller order is fully filled and removed
* Larger order has quantity decremented; remains in book
* Multiple trades may result from a single aggressive order

#### Trade Output

```rust
Trade {
    market_id: MarketId,
    maker_order_id: OrderId,
    taker_order_id: OrderId,
    price: i64,
    quantity: u64,
    timestamp: u64,  // max(maker.timestamp, taker.timestamp)
}
```

#### Safety Checks

* **Cross-market prevention**: Matching stops if `bid.market_id != ask.market_id`
* **Self-trade prevention**: Optional flag stops matching if `bid.account_id == ask.account_id`

***


# Margin Requirements

Clarifies how the trading system handles margin

Risk management is handled through liquidations. The current implementation uses direct liquidation orders rather than a separate insurance fund or ADL mechanism.

#### Margin Requirements

Two margin thresholds exist per market:

| Margin Type     | Purpose                      | Typical Ratio  |
| --------------- | ---------------------------- | -------------- |
| **Initial**     | Required to open positions   | 10% (1000 bps) |
| **Maintenance** | Minimum to avoid liquidation | 5% (500 bps)   |

Margin requirements are calculated as:

```
required = notional * margin_ratio / 10,000
notional = abs(base_position) * mark_price
```

#### Tiered Leverage

Large positions face higher margin requirements:

```rust
LeverageTier {
    notional: u64,              // Position size threshold
    initial_margin_ratio: u64,
    maintenance_margin_ratio: u64,
}
```

When position notional exceeds a tier threshold, the higher ratios apply.

#### Liquidation Detection

Accounts are checked against maintenance margin:

```rust
equity = collateral + sum(unrealized_pnl)
liquidatable = equity < maintenance_margin + reserved_margin
```

Where:

* `unrealized_pnl = base_position * mark_price - quote_spent`
* `reserved_margin` = margin locked for open orders

#### Liquidation Order Generation

For each under-margined account:

```rust
for position in account.positions:
    if position.base_position != 0:
        order = Order {
            order_id: LIQUIDATION_ORDER_START + counter,  // Bit 63 set
            side: opposite(position.direction),
            price: mark_price,
            quantity: abs(base_position),
        }
```

Liquidation orders:

* Have order IDs starting at `1 << 63` (bit 63 set) for identification
* Are priced at current mark price
* Close the entire position
* Process through the normal matching engine

#### Position Lifecycle

Trades affect positions in three ways:

| Effect       | Condition                            | Behavior                                         |
| ------------ | ------------------------------------ | ------------------------------------------------ |
| **Opening**  | No position or same direction        | Increases `base_position`, adds to `quote_spent` |
| **Reducing** | Opposite direction, partial close    | Decreases position, realizes PnL                 |
| **Flipping** | Opposite direction, exceeds position | Closes position, opens opposite side             |

Realized PnL calculation on close:

```
pnl_per_unit = exit_price - entry_price  // for longs
pnl_per_unit = entry_price - exit_price  // for shorts
realized = pnl_per_unit * quantity_closed
```

#### Reserved Margin

Margin is reserved when orders are placed and released when filled:

```rust
// On order placement
reserve_margin_for_order(account, market, price, quantity)

// On fill
release_margin_reservation(account, required_initial_margin(notional))
```

This prevents over-commitment of margin to multiple orders.


# Liquidation logic

Describes the logic used for liquidations, and handling liquidation cascades

#### Liquidation Trigger Condition

Accounts are liquidated when equity falls below maintenance:

```rust
fn is_liquidatable(account, markets) -> bool {
    let equity = calculate_equity(account, markets)
    let maintenance = calculate_maintenance_margin(account, markets)
    let reserved = account.reserved_margin.max(0)

    equity < maintenance + reserved
}
```

The inclusion of `reserved_margin` ensures pending orders don't create hidden risk.

#### Liquidation Detection Process

The `check_liquidations` function scans all accounts:

```rust
fn check_liquidations(state, mark_prices) -> Vec<Order> {
    let mut liquidation_orders = Vec::new()
    let mut liquidation_id = 1 << 63  // Bit 63 marks liquidation orders

    for (account_id, account) in state.margin_accounts {
        let equity = calculate_equity(account, markets)
        let maintenance = calculate_maintenance_margin(account, markets)

        if equity >= maintenance + reserved {
            continue  // Account is healthy
        }

        // Generate liquidation orders for all positions
        for (market_id, position) in account.positions {
            if position.base_position == 0 {
                continue
            }

            liquidation_orders.push(Order {
                order_id: OrderId(liquidation_id),
                market_id,
                account_id,
                side: if position.base_position > 0 { Sell } else { Buy },
                price: mark_price,
                quantity: abs(position.base_position),
                timestamp: mark_price_timestamp,
            })

            liquidation_id += 1
        }
    }

    liquidation_orders
}
```

#### Liquidation Order Properties

| Property  | Value                | Reason                                |
| --------- | -------------------- | ------------------------------------- |
| Order ID  | `≥ 1 << 63`          | Bit 63 set to identify as liquidation |
| Side      | Opposite to position | Closes the position                   |
| Price     | Current mark price   | Market order semantics                |
| Quantity  | Full position size   | Complete liquidation                  |
| Timestamp | Mark price timestamp | Deterministic ordering                |

#### Liquidation Execution Flow

```
1. Mark price update received
2. check_liquidations() scans all accounts
3. Liquidation orders generated for underwater accounts
4. Orders inserted into matching engine
5. Normal matching rules apply
6. Fills update position and collateral
7. If partially filled, position reduced proportionally
```

#### PnL Realization on Liquidation

When liquidation orders fill, PnL is realized:

```rust
// For a long being liquidated (selling)
pnl_per_unit = exit_price - entry_price
realized_pnl = pnl_per_unit × quantity

// For a short being liquidated (buying)
pnl_per_unit = entry_price - exit_price
realized_pnl = pnl_per_unit × quantity
```

The `collateral` balance is adjusted by `realized_pnl`, which may be negative.

#### Fill Classification

The system classifies each fill to determine accounting treatment:

```rust
enum PositionFillEffect {
    Opening,   // Increasing position or opening new
    Reducing,  // Partially closing position
    Flipping,  // Closing and reversing direction
}

fn classify_fill(base_position, side, quantity) -> PositionFillEffect {
    if base_position == 0 {
        return Opening
    }

    let direction = if side == Buy { 1 } else { -1 }

    if base_position.signum() == direction {
        return Opening  // Same direction = adding
    }

    if quantity > abs(base_position) {
        return Flipping  // Exceeds position = flip
    }

    return Reducing  // Partial close
}
```


# FLP token

Paticipate in liquidity provision, by depositing funds to the FLP vault

Fermi Liquidity Provider (FLP) is a automated market making bot, that provides liquidity to high volume pairs, and maintains tight spreads for users. It is also the fallback market maker when external MMs back out (due to volatility etc.), ensuring consistent liquidity on the platform.

Users can deposit their capital in the FLP vault, to earn passive yield from the market making activity. Funds deposited in the vault can also be used as margin to trade (upto 90% of NAV). This provides a low opportunity cost way to place limit orders, while earning yeild on your collateral assets. You can even continue to earn yield once your position is open - potentially offsetting some or all of the funding rate being paid.

Historical performance of the FLP token is shown below. Note that while aiming to minimize volatility in its portfolio, the value of FLP is not guarenteed to rise, and may fall during times of heightened volatility or adverse selection.


