Trade perps. Stay private. Powered by Fully Homomorphic Encryption.
NOX is the first decentralized perpetuals exchange where position size, direction, liquidation threshold, vault balance, and open interest are all stored on-chain as Fully Homomorphic Encryption (FHE) ciphertext. Liquidations, PnL settlement, and funding rate computation all execute directly on encrypted state — without ever decrypting it on the EVM. Built on Zama's FHEVM, NOX delivers what every other on-chain perpetual protocol structurally cannot: a book that nobody can read.
- Why NOX
- Core Innovations
- Feature Matrix
- Architecture
- The FHE Lifecycle of a Position
- Smart Contracts
- Tech Stack
- Repository Layout
- Setup & Deployment
- Running the Stack
- Testing
- Testnet
- Security & Threat Model
- Roadmap
- Glossary
- Contributing
- License & Disclaimer
Decentralized perpetual exchanges have proven that on-chain derivatives can rival centralized venues in throughput and liquidity. They have not solved the structural privacy problem of public blockchains. Every position size, leverage ratio, entry price, and liquidation threshold is broadcast to the world the moment it is opened.
| Risk | Description |
|---|---|
| Strategy Leakage | Sophisticated traders' positions are reverse-engineered by copy-trading bots within seconds of being opened. Months of alpha work become public domain on confirmation. |
| Mempool Front-Running | Searchers observe order intent, sandwich the trade, and capture the spread. MEV extraction on perps runs in the tens of millions of dollars per year. |
| Liquidation Hunting | Whale liquidation prices are visible. Coordinated dumps and pumps trigger cascading liquidations and harvest the resulting fees. |
| Institutional Lockout | Banks, prop desks, and treasury managers are legally and reputationally barred from venues that publish their full position book in real time. |
NOX leverages Fully Homomorphic Encryption (FHE) through Zama's FHEVM. FHE allows arithmetic and comparison operations to run directly on encrypted values, with the result remaining encrypted at every step. The chain validates state transitions; the chain learns nothing.
On NOX:
- Your position size and direction are stored as
euint64andeboolciphertext. - Your liquidation price is computed homomorphically and stored as
euint64ciphertext. - Your vault balance is an encrypted
euint64— invisible even to the market contract. - The protocol's long and short open interest are tracked as separate ciphertexts.
- PnL at close is computed entirely under FHE —
FHE.mul,FHE.div,FHE.add,FHE.sub,FHE.select. - Liquidation eligibility is decided by
FHE.le/FHE.gerunning on encrypted thresholds. - Compliance status (optional institutional gate) is an encrypted
eboolevaluated throughFHE.select.
Only the position owner holds the decryption key, managed entirely client-side via the Zama Relayer SDK.
Hyperliquid proved a thesis that mattered: on-chain perpetuals can be fast, self-custodial, and multi-asset without sacrificing the UX of a centralized exchange. It is the most credible answer to "why are derivatives still trading on Binance?" that DeFi has produced.
But its design — like every other on-chain perp — leaves one structural problem untouched: the order book is public. Bots see exactly where each whale will be liquidated. Institutions cannot participate without doxxing their book.
NOX takes Hyperliquid's three pillars — self-custody, multi-asset, on-chain settlement — and asks: what does this look like when the book is private by default?
| Hyperliquid | NOX | |
|---|---|---|
| Self-custody | ✅ Non-custodial | ✅ Same — encrypted vault |
| Multi-asset | ✅ Dozens of markets at scale | ✅ BTC-USDC + ETH-USDC live; same architecture scales |
| On-chain settlement | ✅ All trades on-chain | ✅ Same |
| Real-time prices | ✅ Internal oracle | ✅ Chainlink AggregatorV3 with staleness guard |
| Speed | ✅ Sub-second matching | |
| Position size | ❌ Public on every fill | ✅ euint64 ciphertext |
| Direction (long/short) | ❌ Public | ✅ ebool ciphertext |
| Liquidation threshold | ❌ Visible to MEV bots | ✅ Only a yes/no boolean ever leaks |
| Open interest skew | ❌ Long/short OI public | ✅ Separate euint64 ciphertexts; only post-aggregation imbalance revealed |
| PnL at close | ❌ Trivially derived from public size + price | ✅ Computed under FHE; only the final payout is decrypted |
| Compliance | ❌ Public allowlist or none | ✅ Optional encrypted-credential gate |
NOX explicitly trades raw throughput for cryptographic privacy — the right trade for sophisticated traders, institutions, and market makers for whom the leak of size and direction is far more expensive than the latency of an FHE round trip.
Hyperliquid showed the world that on-chain perps can be serious. NOX shows what they look like when your book is nobody's business — same self-custody, same Chainlink prices, same on-chain settlement, encrypted by default.
NOX is not a Hyperliquid competitor in the gross-volume sense. It is the privacy layer Hyperliquid is structurally missing — something HL cannot ship without rebuilding its execution engine on FHEVM. The relationship is additive: HL completes the speed/UX side of the on-chain-perps story; NOX completes the privacy side.
NOX is the first protocol to integrate every layer of a perpetual exchange under FHE. Each item below is a primitive that does not exist in any other on-chain perp.
-
Encrypted PnL Settlement — PnL computed entirely on ciphertext at close (
FHE.mul,FHE.div,FHE.select). Size and direction are never revealed; only the final payout amount exits the encrypted domain. -
Homomorphic Liquidations —
FHE.le(currentPrice, encLiquidationPrice)runs on ciphertext. The liquidation threshold is never revealed — only a yes/no boolean reaches the keeper. -
Encrypted Open Interest — Long and short OI are separate
euint64ciphertexts updated viaFHE.select. The directional skew of the entire book — the most valuable signal in TradFi market-making — is unreadable on-chain. -
Two-Phase Funding Rate —
prepareFundingUpdate()computes the encrypted OI imbalance; only the delta and sign are gateway-decrypted. Raw OI figures are never exposed. -
Encrypted Position NFTs (ERC-721) — Each position is a tradeable NFT. The encrypted size, direction, and liquidation price stay in contract storage. On transfer,
_updatere-grants the FHE ACL to the new owner — they immediately gain decrypt rights over what they just purchased. -
Privacy-Preserving Compliance — An optional
IComplianceOracleissues users an encryptedebool isApproved. Non-approved users receive a silent zero-size position that returns their margin on close — no revert, no flag, no observable difference on-chain. Regulators get enforcement; traders get privacy. -
Client-Side Decryption — All decryption happens in the browser. A signed EIP-712 message authorizes the Zama Gateway to release ciphertext keys for that session. Plaintext never touches a server or the chain.
| Feature | Implementation | Encrypted? |
|---|---|---|
| Vault balance | euint64 in NoxVault |
✅ |
| Position size | euint64 in NoxMarket |
✅ |
| Position direction | ebool in NoxMarket |
✅ |
| Liquidation price | euint64, via FHE.select |
✅ |
| PnL computation | Homomorphic at close | ✅ |
| Open interest (long / short) | Separate euint64 ciphertexts |
✅ |
| Compliance credential | ebool per user |
✅ |
| Funding rate (raw OI) | Encrypted skew, gateway-decrypted delta | Raw OI: ✅ / Rate: ❌ |
| Final close payout | Gateway-decrypted, paid to vault | ❌ (minimum necessary reveal) |
| Margin (collateral) | uint256 |
❌ (public) |
| Entry price | uint256 from Chainlink |
❌ (public) |
| Position ownership | ERC-721 NFT | ❌ (public) |
| Price oracle | Chainlink AggregatorV3 + staleness guard | ❌ (public) |
| Multi-asset markets | NoxMarketBTC + NoxMarketETH |
— |
| Keeper automation | TypeScript bot | — |
┌──────────────────────────────────────────┐
│ Browser (Client) │
│ • Encrypts size + direction (WASM) │
│ • Decrypts own position (EIP-712) │
│ • Signs transactions (MetaMask) │
└──────────────────┬───────────────────────┘
│ ciphertext + input proof
▼
┌─────────────────────────────────────────────────────┐
│ Ethereum Sepolia (FHEVM) │
│ │
│ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ NoxVault │◀───│ NoxMarket-BTC │◀───┼── Chainlink BTC/USD
│ │ encrypted │ │ encrypted positions, │ │
│ │ balances │ │ FHE PnL, ERC-721 NFTs │ │
│ └──────▲──────┘ └────────────┬────────────┘ │
│ │ │ │
│ │ ┌────────────▼────────────┐ │
│ │ │ NoxFundingRate-BTC │ │
│ │ │ encrypted long/short │ │
│ │ │ OI (euint64) │ │
│ │ └─────────────────────────┘ │
│ │ │
│ ┌──────┴──────┐ ┌─────────────────────────┐ │
│ │ NoxVault │◀───│ NoxMarket-ETH │◀───┼── Chainlink ETH/USD
│ │ (shared) │ └────────────┬────────────┘ │
│ └─────────────┘ │ │
│ ┌───────────▼─────────────┐ │
│ │ NoxFundingRate-ETH │ │
│ └─────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ NoxComplianceOracle (optional) │ │
│ │ encrypted ebool per address │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────┘
│
┌───────────▼───────────┐
│ Zama Gateway │
│ selectively decrypts │
│ publicly-decryptable │
│ ciphertexts only │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ NOX Keeper Bot │
│ • Sync Chainlink │
│ • Request liquidations│
│ • Execute close & liq│
│ • Update funding rate│
└───────────────────────┘
Data flow summary:
- Browser encrypts trade inputs locally via WASM TFHE.
- Contracts compute on ciphertext — storage is opaque
bytes32handles. - Gateway decrypts only values explicitly marked
FHE.makePubliclyDecryptable(a payout amount or ashouldLiquidateboolean) — never raw inputs. - Keeper relays gateway results back as plaintext arguments to phase-2 functions.
- Client decrypts its own position in-browser via signed EIP-712 authorization.
Each phase is designed so the smallest possible amount of data ever leaves the encrypted domain.
The user's size and direction are encrypted locally before the transaction is signed:
const sizeInput = instance.createEncryptedInput(marketAddress, userAddress);
sizeInput.add64(sizeUsd);
const encSize = await sizeInput.encrypt(); // → ciphertext handle + ZK input proof
const dirInput = instance.createEncryptedInput(marketAddress, userAddress);
dirInput.addBool(isLong);
const encDir = await dirInput.encrypt();Two separate encrypted inputs are produced — one euint64 for size, one ebool for direction. Each carries a ZK input proof binding it to the encrypting wallet and destination contract.
NoxMarket.openPosition validates the proofs, optionally gates on the compliance oracle, computes the encrypted liquidation price for both directions via FHE.select, and stores the full struct:
struct EncryptedPosition {
euint64 size; // encrypted position size (8 decimals)
ebool isLong; // encrypted direction
euint64 liquidationPrice; // encrypted threshold, selected per direction
uint256 margin; // plaintext USDC margin (6 decimals)
uint256 entryPrice; // public Chainlink price at open
address owner;
bool isOpen;
}The contract grants FHE ACL permissions to itself and the trader. An ERC-721 NFT is minted. Encrypted OI is updated atomically on the funding rate tracker.
While the position is open, nothing readable changes in the encrypted state. Size, direction, liq price, and PnL all appear as ●●● in the UI. The trader can decrypt their own position at any time via a one-time EIP-712 signature — the plaintext is only ever held in browser memory for that session.
Phase 4a — closePosition(positionId) (trader):
The contract computes encrypted PnL for both long and short branches, selects the correct branch with FHE.select(pos.isLong, longPayout, shortPayout), and marks only the final payout publicly decryptable. OI is removed from the funding rate tracker.
Phase 4b — executeClose(positionId, payoutPlain) (keeper, after gateway decryption):
The vault is credited and the position NFT is burned. Only the final payout amount is ever revealed. Size and direction stay encrypted.
Phase 5a — requestLiquidation(positionId) (anyone):
ebool longTriggered = FHE.le(currentPrice, pos.liquidationPrice);
ebool shortTriggered = FHE.ge(currentPrice, pos.liquidationPrice);
ebool shouldLiquidate = FHE.select(pos.isLong, longTriggered, shortTriggered);
shouldLiquidate = FHE.makePubliclyDecryptable(shouldLiquidate);Phase 5b — executeLiquidation(positionId, shouldLiquidatePlain) (keeper):
If the gateway returned true, the trader's margin is split — 5% to the liquidator as a bonus, the remainder back to the trader's vault. OI is removed and the NFT is burned.
The liquidator earns a reward without ever learning the trader's threshold or size. The chain learns exactly one bit per liquidation.
All Solidity sources are in packages/contracts/contracts/.
Shared margin treasury. One vault serves all markets.
| Function | Description |
|---|---|
deposit(uint256 amount) |
Pulls ERC-20, adds to encrypted balance, updates plaintext mirror. |
withdraw(uint256 amount) |
Reduces encrypted balance, transfers ERC-20 back. |
deductMargin(address, uint256) |
Market-only. Locks margin into a position. |
creditMargin(address, uint256) |
Market-only. Returns funds on close / liquidation. |
getEncryptedBalance(address) → euint64 |
Returns the ciphertext handle. ACL-gated. |
addMarket / removeMarket |
Owner-only. Authorizes market contracts. |
The vault keeps a plaintext lockedBalance[user] mirror for insufficient-balance reverts and withdrawal limits. The encrypted _encryptedBalance[user] is what's exposed externally.
Core trading engine. One instance per asset. Inherits ERC721Enumerable.
Key constants: MIN_MARGIN_BPS = 500 (5% liq threshold), LIQUIDATOR_BONUS_BPS = 500 (5% keeper reward), MAX_PRICE_STALENESS = 1 hour.
openPosition(externalEuint64 size, externalEbool isLong, uint256 margin, bytes sizeProof, bytes dirProof)
closePosition(uint256 positionId)
executeClose(uint256 positionId, uint64 payoutPlain) // owner-only (keeper)
requestLiquidation(uint256 positionId)
executeLiquidation(uint256 positionId, bool shouldLiquidate) // owner-only (keeper)
syncPrice() // pulls Chainlink latestRoundData
updatePrice(uint256 newPrice) // owner-only (demo / emergency)
setFundingRate(address) // wire NoxFundingRate
setComplianceOracle(address) // wire optional complianceThe _update ERC-721 hook is overridden to call FHE.allow(handle, newOwner) on every encrypted field whenever the NFT is transferred. The buyer immediately gains decrypt rights; the seller's view ceases to function.
Tracks encrypted long/short OI and publishes the funding rate each interval.
addOI(euint64 size, ebool isLong) // market-only, on openPosition
removeOI(euint64 size, ebool isLong) // market-only, on close/liquidate
prepareFundingUpdate() // anyone (after interval) or owner anytime
setFundingRate(uint64 diff, bool longsDominate) // owner-only (keeper, post-gateway)Internal pattern: FHE.select(isLong, size, zero) adds size to long OI only if isLong=true. This keeps both the per-position direction and the aggregate skew private.
Funding formula: rate = min(diffPlain / 1e6, 100) — 1 bps per $1M imbalance, capped at 100 bps. Positive means longs pay shorts.
Optional institutional gate. Stores one ebool _approvals[user] per address.
setApproval(address user, externalEbool encryptedStatus, bytes proof) // admin-only
isApproved(address user) → ebool // ciphertext, ACL-gatedWhen wired to a market, openPosition evaluates FHE.select(isApproved, size, 0). Non-approved users silently receive a zero-size position — no revert, no event, no observable difference on-chain. The oracle enforces; the chain says nothing.
NoxSettlement.sol— Legacy plaintext settlement contract, retained for reference. Active flow is the FHE PnL path insideNoxMarket.interfaces/IComplianceOracle.sol— DecouplesNoxMarketfrom any specific oracle implementation.mocks/MockUSDC.sol,mocks/MockV3Aggregator.sol— Used in the local Hardhat suite.
- Solidity 0.8.27, Cancun EVM target
- Hardhat 2.26 +
hardhat-deploy - OpenZeppelin 5.4 —
Ownable,ReentrancyGuard,ERC721Enumerable,SafeERC20 - Chainlink Contracts 1.5 —
AggregatorV3Interface @fhevm/solidity0.11.1,@fhevm/hardhat-plugin0.4.2- TypeChain (ethers-v6 target)
- Zama FHEVM Gateway (Sepolia) for selective decryption
@zama-fhe/relayer-sdk0.4.1 — browser WASM library for client-side encrypt + decrypt@fhevm/mock-utils0.4.2 — synchronous mock gateway for Hardhat tests
- Next.js 14 (App Router), React 18, TypeScript 5
- Wagmi 2 + Viem 2 — type-safe contract reads/writes
- TanStack Query 5 — wallet state caching
- Tailwind CSS 3 — NOX yellow
#F5C600on near-black#0D0C09 - Lightweight Charts 4 — price chart rendering
- Node.js ≥ 20, pnpm 10, ethers v6 keeper script
- Optional deploy to Railway / Render for persistent uptime
private-perps/
├── package.json # workspace root
├── pnpm-workspace.yaml
├── FEATURES.md # roadmap & differentiation strategy
├── DEMO.md # 8-minute demo script
├── TESTING_GUIDE.md # step-by-step UI test walkthrough
│
├── packages/
│ ├── contracts/ # @nox/contracts
│ │ ├── contracts/
│ │ │ ├── NoxMarket.sol # core engine: encrypted positions + FHE PnL
│ │ │ ├── NoxVault.sol # encrypted margin treasury
│ │ │ ├── NoxFundingRate.sol # encrypted OI + funding rate
│ │ │ ├── NoxComplianceOracle.sol
│ │ │ ├── NoxSettlement.sol # legacy reference
│ │ │ ├── interfaces/
│ │ │ └── mocks/
│ │ ├── deploy/ # 00_vault → 01_market → 02_funding_rate
│ │ ├── deployments/localhost/ + sepolia/
│ │ ├── scripts/
│ │ │ ├── keeper.ts # production keeper bot
│ │ │ ├── demo-flow.ts
│ │ │ └── sync-prices.ts
│ │ ├── test/
│ │ │ ├── NoxVault.test.ts
│ │ │ ├── NoxMarket.test.ts
│ │ │ ├── NoxMarketCompliance.test.ts
│ │ │ ├── NoxFundingRate.test.ts
│ │ │ └── NoxComplianceOracle.test.ts
│ │ └── hardhat.config.ts
│ │
│ └── frontend/ # @nox/frontend
│ └── src/
│ ├── app/page.tsx # landing page
│ ├── app/trade/page.tsx # trading interface
│ ├── components/ # Header, TradingPanel, PositionsTabs, DemoWidget, ...
│ ├── hooks/ # useFhevm, useMarket, useVault
│ └── lib/ # contracts.ts, fhevm.ts, decrypt.ts, wagmi.ts
- Node.js ≥ 20, pnpm ≥ 10 (
npm install -g pnpm) - MetaMask (or any wagmi-compatible wallet)
- Sepolia ETH for gas — any Sepolia faucet
- (Optional) Alchemy / Infura key for a reliable RPC endpoint
git clone https://github.com/your-org/nox-perps.git
cd nox-perps
pnpm installcp .env.example .env# .env (root)
PRIVATE_KEY=0xYOUR_DEPLOYER_PRIVATE_KEY # testnet only — never commit
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
ETHERSCAN_API_KEY= # optional, for hardhat-verify# packages/frontend/.env.local
NEXT_PUBLIC_SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY# Compile contracts + generate TypeChain bindings
pnpm contracts:compile
# Spin up a local Hardhat node and deploy
pnpm chain # terminal 1 — hardhat node on :8545
pnpm deploy:local # terminal 2 — runs 00 → 01 → 02 deploy scripts
# Run the test suite
pnpm contracts:test
# Start the frontend (points at Sepolia by default — update contracts.ts for local)
pnpm dev # → http://localhost:3000NOX is already live on Sepolia. To redeploy your own instance:
pnpm deploy:sepoliaThe three deploy scripts run in sequence:
00_deploy_vault.ts— uses Zama's mock USDC at0x9b5Cd13b8eFbB58Dc25A05CF411D8056058aDFfF.01_deploy_market.ts— deploysNoxMarketBTC(Chainlink BTC/USD) andNoxMarketETH(Chainlink ETH/USD), wires both to the vault.02_deploy_funding_rate.ts— deploys twoNoxFundingRateinstances and bidirectionally wires them to the markets.
Each script is idempotent and skips already-deployed contracts. Output is written to packages/contracts/deployments/sepolia/.
To verify on Etherscan:
cd packages/contracts
pnpm hardhat verify --network sepolia <address> <constructorArgs...>The keeper is a stateless TypeScript script that operates the protocol's off-chain critical path. Anyone can run one.
pnpm --filter @nox/contracts run keeper:sepolia| Trigger | Action |
|---|---|
| Polling loop (30s) | For each market: if Chainlink price has drifted > 0.5% from currentPrice, call syncPrice(). |
| Polling loop (30s) | For each open position: if price has moved > 5% from entry, call requestLiquidation(). |
CloseRequested event |
After gateway decrypts the payout, call executeClose(positionId, payoutPlain). |
LiquidationRequested event |
After gateway decrypts shouldLiquidate, call executeLiquidation(positionId, true). |
What the keeper never learns: position size, direction, exact liquidation threshold, or the protocol's directional OI skew. It sees only public state — entry prices, plaintext margins, NFT owners — and the minimal gateway outputs (a payout amount or a boolean).
For persistent uptime on testnet, deploy to Railway or Render. A single instance handles both BTC and ETH markets.
The trading dApp is a Next.js 14 (App Router) application. Two routes:
/— Landing page: live ticker, encrypted-positions preview, struct visualisation, "How it works" walkthrough./trade— Full trading interface: price chart, market selector, trading panel, positions table, stats bar, demo widget.
Key hooks:
useFhevm()— Lazily initialises the Zama relayer SDK, bootstraps WASM viainitSDK, builds Sepolia config. Falls back toSepoliaConfigV2if the primary relayer is under load.useMarket(marketId)— All market reads + write helpers (openPosition,closePosition,requestLiquidation,syncPrice, …) for'btc-usdc'or'eth-usdc'.useVault()—deposit,withdraw,faucet, reactive balance view.
Encryption flow:
const { encryptedSize, encryptedIsLong, sizeProof, directionProof } =
await encryptPosition(marketAddress, walletAddress, sizeUsd, isLong);
await openPosition({ encryptedSize, encryptedIsLong, marginAmount, sizeProof, directionProof });Decryption / Reveal PnL flow:
const pos = await getPosition(positionId);
const { size, isLong } = await decryptPosition(
marketAddress, userAddress, pos.sizeHandle, pos.isLongHandle, signTypedData
);The decrypted values live only in React component state — never persisted to disk, never sent to a server, never written to the chain.
Why COOP/COEP headers?
next.config.jssetsCross-Origin-Opener-Policy: same-originandCross-Origin-Embedder-Policy: require-corpto enablecrossOriginIsolatedmode. This lets the Zama TFHE WASM useSharedArrayBufferfor ~10× encryption speed. Without these headers, encryption falls back to single-threaded WASM.
pnpm contracts:test| Contract | Test File | What It Verifies |
|---|---|---|
NoxVault |
NoxVault.test.ts |
Encrypted deposit/withdraw, owner-only decryption, market access control, balance math. |
NoxMarket |
NoxMarket.test.ts |
Position open → deducts margin, encrypted size handle is non-zero, owner can decrypt, third parties cannot. Full close lifecycle. Liquidation lifecycle + 5% bonus. NFT mint / transfer / ACL re-grant / burn. |
NoxMarketCompliance |
NoxMarketCompliance.test.ts |
Non-approved users get size=0 ciphertext; approved users get their requested size. |
NoxFundingRate |
NoxFundingRate.test.ts |
OI accumulation, two-phase update, capped rate formula. |
NoxComplianceOracle |
NoxComplianceOracle.test.ts |
Default deny, admin issuance updates ciphertext. |
FHEVM mock vs. live gateway: In tests, @fhevm/mock-utils resolves FHE.makePubliclyDecryptable synchronously in-process. On Sepolia, the same call enqueues the ciphertext for Zama Gateway decryption (typically 1–2 blocks). The keeper bridges the gap.
Useful test helpers:
// Encrypt an input from a signer
const input = fhevm.createEncryptedInput(contractAddress, signer.address);
input.add64(value);
const { handles, inputProof } = await input.encrypt();
// Decrypt as a specific user (requires prior FHE.allow)
await fhevm.userDecryptEuint(FhevmType.euint64, handle, contractAddress, signer);A complete 8-minute demo script lives in DEMO.md. High-level flow:
- Landing page — encrypted size, direction, and liquidation price visible in the UI preview.
- Connect wallet — direct Sepolia connection, no backend.
- Deposit margin — funds enter the encrypted vault as
euint64. - Pick a market — BTC-USDC or ETH-USDC, both live Chainlink-priced.
- Open an encrypted long — MetaMask calldata shows ciphertext + ZK input proofs, never plaintext.
- Inspect positions — Side, Size, Liq Price, PnL all show
●●●. - Reveal PnL — sign EIP-712, cells decrypt in-browser only.
- Trigger liquidation — crash the oracle price,
requestLiquidationrunsFHE.leon ciphertext, keeper executes. - Funding rate — long/short OI encrypted; only the imbalance direction and magnitude are ever revealed.
CLI-only walkthrough:
pnpm demo:sepolia| Contract | Address |
|---|---|
NoxVault |
0x211AD06B4B5d7F8eBD204B2Ee6CaC239fE1Ab794 |
NoxMarketBTC |
0x93D1b72323b3B012cE2a43F41A9B02e120581517 |
NoxMarketETH |
0xD2ecbFa44712Bf0F7223aD8604c40d0c6E4b5513 |
| Mock USDC (Zama) | 0x9b5Cd13b8eFbB58Dc25A05CF411D8056058aDFfF |
| Chainlink BTC/USD | 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 |
| Chainlink ETH/USD | 0x694AA1769357215DE4FAC081bf1f309aDC325306 |
Need test USDC? Call
faucet()on the mock USDC contract — it mints 10,000 mUSDC to the caller.
NOX is testnet software. It has not been audited. Do not use with real funds.
- ✅ Position size and direction — never visible on-chain.
- ✅ Liquidation threshold — revealed only as a yes/no boolean.
- ✅ Vault balances — encrypted
euint64, only the user can decrypt. - ✅ Aggregate OI directional skew — only post-aggregation imbalance is exposed.
- ✅ Compliance status — encrypted
ebool; the protocol cannot enumerate the allowlist.
- ❌ Margin amount — plaintext in
openPositioncalldata. Visible on Etherscan. - ❌ Entry price — plaintext (it's the public oracle price at open).
- ❌ Position ownership — the ERC-721 NFT is publicly transferable and visible.
- ❌ Final close payout — gateway-decrypted (the minimum necessary to credit the vault).
- ❌ Transaction timing — open / close / liquidation are still public events with timestamps.
- Zama Gateway — trusted to honour
FHE.makePubliclyDecryptablecorrectly and enforce ACLs. NOX inherits Zama's threshold-decryption security model. - Chainlink oracles — NOX adds a 1-hour staleness guard but trusts feed integrity.
- Owner privileges —
executeCloseandexecuteLiquidationareonlyOwner. Production deployments should transfer ownership to a multisig and eventually replace these with a permissionless keeper auction.
- The
lockedBalanceplaintext mirror inNoxVaultexposes total margin per user across all positions. Required for cheap reverts but represents a partial privacy regression. entryPrice+margintogether allow an observer to infer plausible notional ranges given typical leverage. The encrypted size field hides the exact figure, not the upper bound.- FHE division has limited precision. PnL math normalises from 8-decimal size units to 6-decimal USDC units via
FHE.div(pnl, 100)to keep all values withineuint64range. - A malicious keeper owner could stall
executeClose, griefing a user. Mitigation: permissionless executor with an on-chain dispute window (roadmap item).
NOX optimizes for depth over breadth — every item tightens the FHE narrative.
- Encrypted vault balances
- Encrypted position size, direction, liquidation price
- Two-phase encrypted liquidations
- Two-phase encrypted PnL settlement at close
- Encrypted long/short OI + two-phase funding rate
- Chainlink AggregatorV3 oracle + staleness guard
- Multi-market: BTC-USDC + ETH-USDC, shared vault
- ERC-721 position NFTs with FHE ACL re-granting on transfer
- Privacy-preserving compliance oracle
- Hardhat test suite (vault, market, compliance, funding rate, oracle)
- Production keeper script
- Next.js frontend with live encrypt/decrypt + EIP-712 reveal
- Sepolia deployment
- Encrypted-margin variant (eliminate last plaintext leak in the vault)
- Permissionless keeper auction (replace
onlyOwneron phase-2 functions) - Market-maker rebates with encrypted volume tracking
- Cross-margin maintenance computed under FHE
- Mainnet deployment after audit
- Position-NFT secondary marketplace (sell encrypted exposure)
| Term | Definition |
|---|---|
| FHE | Fully Homomorphic Encryption. Arithmetic and comparisons run on ciphertext; the output stays encrypted. |
| FHEVM | Zama's Solidity-compatible EVM with built-in FHE opcodes. |
euint64 |
Encrypted unsigned 64-bit integer — backs position size, vault balance, OI. |
ebool |
Encrypted boolean — backs direction, compliance status, comparison results. |
externalEuint64 / externalEbool |
Calldata types for ciphertext arriving from outside the chain. Materialized via FHE.fromExternal() + ZK input proof. |
| Handle | The opaque bytes32 identifier a contract holds for a ciphertext. |
| Input Proof | ZK proof binding an encrypted input to the encrypting wallet and destination contract. |
| Gateway | Zama service that performs threshold decryption for ciphertexts marked publicly decryptable. |
| ACL | Access Control List — determines which addresses may decrypt a ciphertext. Set via FHE.allow, FHE.allowThis, FHE.allowTransient. |
| Public Decryptable | A ciphertext flagged via FHE.makePubliclyDecryptable for gateway resolution — the protocol's selective-reveal primitive. |
| EIP-712 | Signature standard used by the Zama SDK to authorize in-browser decryption of a user's own ciphertexts. |
| Two-Phase Action | Any operation requiring a gateway round-trip: phase 1 marks the result publicly decryptable, phase 2 (keeper) consumes the plaintext and finalises state. |
Contributions are welcome. Priority areas:
- Additional markets (SOL, ARB, AVAX) wired to Chainlink feeds.
- Hardening the keeper into a permissionless multi-runner.
- Audit-style reviews of the FHE PnL math and overflow guards.
- Test cases for edge conditions: zero-size approved closes, OI overflow at scale, NFT transfers mid-liquidation.
- Frontend polish: loading states, error toasts, decryption progress indicators.
Open an issue first for substantial changes. PRs should pass pnpm contracts:test, include test coverage for new contract code, and not expand plaintext leakage without justification.
NOX is released under the MIT License.
Disclaimer. NOX is experimental software deployed on Ethereum Sepolia testnet for research and demonstration purposes. It has not been audited. Do not deploy to mainnet without a comprehensive third-party audit. The authors accept no liability for any loss arising from use of this software. Use at your own risk.
Built with Zama FHEVM. On-chain perpetuals, encrypted by default.