From fd3f0b632be49e8b6d825c7c375478aff6b9d7a4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 17 Jun 2026 18:07:07 +0000
Subject: [PATCH 1/6] Add perpetuals crate skeleton
---
Cargo.toml | 2 +
README.md | 2 +-
perpetuals/Cargo.toml | 27 ++
perpetuals/src/db.rs | 149 +++++++++
perpetuals/src/genesis.rs | 88 +++++
perpetuals/src/ledger.rs | 609 ++++++++++++++++++++++++++++++++++
perpetuals/src/lib.rs | 23 ++
perpetuals/src/rpc.rs | 364 ++++++++++++++++++++
perpetuals/src/transaction.rs | 222 +++++++++++++
perpetuals/src/types.rs | 178 ++++++++++
10 files changed, 1663 insertions(+), 1 deletion(-)
create mode 100644 perpetuals/Cargo.toml
create mode 100644 perpetuals/src/db.rs
create mode 100644 perpetuals/src/genesis.rs
create mode 100644 perpetuals/src/ledger.rs
create mode 100644 perpetuals/src/lib.rs
create mode 100644 perpetuals/src/rpc.rs
create mode 100644 perpetuals/src/transaction.rs
create mode 100644 perpetuals/src/types.rs
diff --git a/Cargo.toml b/Cargo.toml
index 41f8fda..b5804a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ members = [
"rpc",
"examples/template",
"examples/coins-chain",
+ "perpetuals",
]
resolver = "2"
@@ -28,6 +29,7 @@ nunchi-crypto = { version = "2026.5.0", path = "crypto" }
nunchi-dkg = { version = "2026.5.0", path = "dkg" }
nunchi-mempool = { version = "2026.5.0", path = "mempool" }
nunchi-rpc = { version = "2026.5.0", path = "rpc" }
+nunchi-perpetuals = { version = "2026.5.0", path = "perpetuals" }
nunchi-template = { version = "2026.5.0", path = "examples/template" }
nunchi-coins-chain = { version = "2026.5.0", path = "examples/coins-chain" }
commonware-actor = "2026.5.0"
diff --git a/README.md b/README.md
index 54f460d..604678e 100644
--- a/README.md
+++ b/README.md
@@ -44,10 +44,10 @@ This repository will contain modules for building public and private blockchains
### Financial Primitives
+* [`perpetuals`](perpetuals/) - defines perpetual swap markets, leveraged positions, liquidation checks, and RPC queries
* `margin` - user has BTC + nunchi and doesn't want to sell, and deposits BTC+nunchi and gets a stablecoin. Could be backed by other coins, not just btc and nunchi.
* `securities` - Non-synthetic perps contracts (delivery of tokenized stock)
* `vaults` - a module for running vaults composed of many types of capital, traded by an authorised offchain party
* `clob` - used on the global chain, provides liquidity between local chain tokens
* `derivatives` - ingests a price feed and creates derivatives products
* `stablecoin` - a wrapper of coins special for the needs of stablecoins
-
diff --git a/perpetuals/Cargo.toml b/perpetuals/Cargo.toml
new file mode 100644
index 0000000..e15e60a
--- /dev/null
+++ b/perpetuals/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "nunchi-perpetuals"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+
+[features]
+default = ["rpc"]
+rpc = ["dep:nunchi-rpc", "dep:jsonrpsee", "dep:futures"]
+
+[dependencies]
+async-trait = { workspace = true }
+bytes = { workspace = true }
+thiserror = { workspace = true }
+nunchi-coins = { workspace = true }
+nunchi-common = { workspace = true }
+nunchi-crypto = { workspace = true }
+nunchi-rpc = { workspace = true, optional = true }
+commonware-codec = { workspace = true }
+commonware-cryptography = { workspace = true }
+commonware-formatting = { workspace = true }
+jsonrpsee = { workspace = true, optional = true }
+serde = { workspace = true }
+futures = { workspace = true, optional = true }
+
+[dev-dependencies]
+commonware-runtime = { workspace = true }
diff --git a/perpetuals/src/db.rs b/perpetuals/src/db.rs
new file mode 100644
index 0000000..c7c1b39
--- /dev/null
+++ b/perpetuals/src/db.rs
@@ -0,0 +1,149 @@
+//! Persistence layer for the perpetuals module.
+
+use crate::ledger::LedgerError;
+use crate::{Market, MarketId, Position, PositionId, PERPETUALS_NAMESPACE};
+use async_trait::async_trait;
+use commonware_codec::{Encode, Read, ReadExt};
+use commonware_cryptography::sha256::Digest;
+use nunchi_common::state_db::{Namespace, StateStore};
+use nunchi_common::Address;
+
+const NS: Namespace = Namespace::new(PERPETUALS_NAMESPACE);
+
+#[repr(u8)]
+#[derive(Clone, Copy)]
+enum Table {
+ Account = 0,
+ MarketNonce = 1,
+ PositionNonce = 2,
+ Market = 3,
+ Position = 4,
+}
+
+impl From
for u8 {
+ fn from(table: Table) -> Self {
+ table as Self
+ }
+}
+
+fn encoded(value: &T) -> Vec {
+ value.encode().as_ref().to_vec()
+}
+
+fn decoded>(bytes: &[u8]) -> Result {
+ let mut buf = bytes;
+ T::read(&mut buf).map_err(|err| LedgerError::Storage(err.to_string()))
+}
+
+#[async_trait]
+pub trait PerpetualDB {
+ async fn nonce(&self, id: &Address) -> Result;
+
+ fn set_nonce(&mut self, id: &Address, nonce: u64);
+
+ async fn market_nonce(&self) -> Result;
+
+ fn set_market_nonce(&mut self, nonce: u64);
+
+ async fn position_nonce(&self) -> Result;
+
+ fn set_position_nonce(&mut self, nonce: u64);
+
+ async fn market(&self, market: &MarketId) -> Result