Dashboard
N
Nexus Docs
Live on Ethereum Sepolia

Nexus Protocol

Technical Documentation Β· v1.0

Nexus is a fully on-chain perpetuals exchange built on Ethereum Sepolia. Trade BTC and ETH with up to 50Γ— leverage β€” no off-chain order books, no trusted operators, no proxy admins.

Five composable layers: Vault Layer for capital and solvency, Trading Engine for positions and orders, Risk Engine for open-keeper liquidations, Oracle & Math Layer for Chainlink-secured pricing, and Account Abstraction Layer for gasless ERC-4337 accounts.

🏦

Single-Vault Design

All LP liquidity and trader collateral in one PerpsVault. No fragmented capital.

⚑

Open Keeper Model

Any address calls batchLiquidate and earns 10% fee. Always prompt liquidations.

πŸ”

Gasless via ERC-4337

NexusPaymaster sponsors every UserOperation. Full self-custody, zero ETH needed.

⛓️

Cross-Chain CCIP

Trade from Arbitrum, Base, or Optimism. Chainlink CCIP relays margin and params.

πŸ”—

Chainlink Oracles

Heartbeat staleness guards on every getPrice call. Stale feeds revert STALE_PRICE.

πŸ“

18-Dec Precision

DECIMALS_SCALAR normalises USDC to 1e18 internally. No rounding or dust exploits.

System Architecture

Five-layer composable design

Capital flows uni-directionally: USDC deposits into PerpsVault, minting an internal balance. Opening a position through PositionManager atomically locks collateral. PriceOracle validates Chainlink freshness; PnLCalculator checks health. Below threshold, LiquidationEngine is callable by any keeper.

Cross-chain flow: a CCIP message from any EVM chain delivers trade params and USDC to MessageReceiver on Sepolia, which calls PositionManager identically to a local trade.

Core Design Invariants

No off-chain trustPrice discovery, execution, liquidation, settlement β€” all fully on-chain.
18-decimal precisionDECIMALS_SCALAR = 10^(18 βˆ’ tokenDecimals) normalises USDC (6 dec) to 1e18 internally.
Vault solvency128 runs Γ— 50 calls = 6,400 randomised state mutations, zero reverts against balance invariant.
Isolated margin defaultCross-margin mode uses _calculateGlobalPnL iterating all active positions.

Vault Layer

PerpsVault.sol

└─ ERC-20 Collateral

└─ LP Share Tokens

└─ Lock / Release API

Trading Engine

PositionManager.sol

└─ ISOLATED / CROSS

└─ Market + Limit Orders

└─ Cross-Chain (CCIP)

Risk Engine

LiquidationEngine.sol

└─ Batch Liquidate (20)

└─ Keeper Rewards

└─ Emergency Rescue

Oracle & Math

PriceOracle.sol

└─ PnLCalculator.sol

└─ Chainlink Heartbeat

└─ int256 safe math

Acct Abstraction

SmartAccount.sol

└─ NexusPaymaster.sol

└─ AccountFactory.sol

└─ ERC-4337 compliant

Cross-Chain

CrossChainRouter.sol

└─ MessageReceiver.sol

└─ Chainlink CCIP

└─ Multi-chain positions

Contract Addresses

Ethereum Sepolia Testnet

PerpsVault

The capital layer β€” all funds live here

PerpsVault.sol is the financial backbone of Nexus. Every dollar β€” LP yield deposits or trader margin β€” lives here. A single vault eliminates capital fragmentation and makes solvency auditable from one storage slot.

Two separate internal pools: the LP pool for passive liquidity providers, and the collateral pool for active traders. These are strictly isolated β€” LP funds cannot be silently consumed to cover trader losses.

OwnableReentrancyGuardPausableERC-20 LP TokensMINIMUM_LIQUIDITY = 1000DECIMALS_SCALAR = 1e12
01

LP Liquidity Engine

LPs call addLiquidity(uint256 amount) to deposit USDC, receiving shares via shares = (amount Γ— totalSupply) / totalAssets. The first deposit permanently burns MINIMUM_LIQUIDITY (1,000) shares to address(0) β€” preventing the share-price inflation attack.

removeLiquidity(uint256 lpAmount) redeems LP tokens for unlocked assets only. Assets backing open positions remain locked and unavailable for LP withdrawal.

PerpsVault.sol β€” LP share minting
SOLIDITY
function addLiquidity(uint256 amount) external nonReentrant whenNotPaused {
  uint256 normalised = amount * DECIMALS_SCALAR;
  uint256 supply     = totalSupply();

  uint256 shares = supply == 0
    ? normalised - MINIMUM_LIQUIDITY
    : (normalised * supply) / totalAssets();

  _mint(msg.sender, shares);
}
02

Trader Collateral

deposit and withdraw are callable only by the registered PositionManager β€” traders never interact with the vault directly. All margin checks occur in PositionManager before any capital moves.

lockCollateral moves funds from free β†’ locked on position open. A normal close calls unlockCollateral; a liquidation calls settleTrade.

β›” Access Control β€” onlyPositionManager

deposit, withdraw, lockCollateral, unlockCollateral, and settleTrade all carry the onlyPositionManager modifier. Set once in the constructor via a two-step Ownable transfer. No backdoor exists.

03

Trade Settlement

settleTrade(address trader, int256 pnl, uint256 keeperFee) atomically applies realised PnL to locked collateral, pays the keeperFee, and unlocks the residual to the trader's free balance.

LP solvency invariant: after every settlement, the vault asserts totalAssets() β‰₯ totalLockedCollateral(). Any settlement that would make the pool insolvent reverts entirely.

PerpsVault.sol β€” solvency invariant
SOLIDITY
function settleTrade(address trader, int256 pnl, uint256 keeperFee)
  external onlyPositionManager
{
  uint256 released = _applyPnL(trader, pnl, keeperFee);
  PROTOCOL_ASSET.safeTransfer(msg.sender, keeperFee);
  _collateralBalances[trader].free += released;

  require(totalAssets() > totalLockedCollateral(), "VAULT_INSOLVENT");
}

⚠ Dust Withdrawal β€” Patched

Transfer amount snapped to the nearest DECIMALS_SCALAR multiple via modulo subtraction before any safeTransfer. Eliminates repeated fractional-wei drain exploits.

PositionManager

The trading engine β€” all position logic

PositionManager.sol is the only contract allowed to mutate vault collateral. Every trader action routes here. Position struct stores: trader address, asset, size, entry price, collateral, margin mode, and direction flag.

Risk parameters enforced at creation time: leverage ≀ maxLeverage, size β‰₯ minimum, oracle freshness check. Validate first, mutate after β€” no partial states ever written.

OwnableReentrancyGuardPausableLIQUIDATION_THRESHOLD = 8000 bpsLIQUIDATOR_FEE = 1000 bpsmaxLeverage = 50Γ—
01

Margin Modes: ISOLATED vs CROSS

ISOLATED

Each position has its own ring-fenced collateral pool. Only that position's margin is at risk on liquidation.

CROSS

All free collateral counts as margin across every cross position β€” higher effective leverage, cascade liquidation risk.

LIQUIDATION_THRESHOLD = 8,000 bps (80%): a position becomes liquidatable when 80% of its margin is consumed. Remaining 20% splits: 10% keeper fee + 10% protocol buffer.

02

Market Orders

openPosition(asset, size, collateral, isLong, mode) opens at the current oracle price. Queries PriceOracle, validates leverage, locks collateral, writes the Position struct β€” all atomic and nonReentrant.

closePosition(bytes32 positionId) calculates realised PnL against the current oracle price, then calls settleTrade. LP exposure changes by exactly the trade PnL.

PositionManager.sol β€” PnL calculation
SOLIDITY
function _calcPnL(
  Position memory pos,
  uint256 currentPrice
) internal pure returns (int256) {
  int256 priceDelta = int256(currentPrice) - int256(pos.entryPrice);
  int256 rawPnL = (priceDelta * int256(pos.size)) / int256(pos.entryPrice);
  return pos.isLong ? rawPnL : -rawPnL;
}
03

Limit Orders

placeLimitOrder(..., triggerPrice, ...) immediately locks collateral on-chain. No position opened yet β€” prevents griefing attacks where orders are placed without backing funds.

Anyone calls executeLimitOrder(bytes32 orderId) once oracle crosses triggerPrice, earning 0.1% of collateral. Traders call cancelLimitOrder anytime to reclaim collateral.

04

Cross-Chain Trade Execution

executeCrossChainTrade(bytes32 messageId, address trader, ...) opens a position identically to openPosition. The messageId is stored β€” duplicate CCIP deliveries revert with DUPLICATE_MESSAGE.

If the position-open fails after collateral has already arrived, MessageReceiver credits collateral to the trader's free vault balance. Bridged funds are never silently lost.

LiquidationEngine

The risk engine β€” open-keeper model

LiquidationEngine.sol replaces the privileged-admin pattern with an open-keeper model paying a 10% fee to any caller. A competitive keeper market ensures immediate response to price dislocations.

01

Batch Processing Architecture

batchLiquidate(bytes32[] calldata positionIds) accepts up to maxBatchSize (20) IDs. For each: isLiquidatable() β†’ liquidate() β†’ accumulate fee. Single safeTransfer at the end.

Each attempt wrapped in try/catch. Without this, two keepers submitting overlapping batches would both fail if any ID was stale. With isolation, successful liquidations proceed regardless.

LiquidationEngine.sol β€” batch with isolation
SOLIDITY
for (uint i = 0; i < positionIds.length; i++) {
  try positionManager.liquidate(positionIds[i]) returns (uint256 fee) {
    totalFees += fee;
  } catch {
    // closed concurrently by another keeper β€” skip
  }
}
PROTOCOL_ASSET.safeTransfer(msg.sender, totalFees);
02

Keeper Reward System

Fee flow: locked collateral β†’ settleTrade β†’ LiquidationEngine β†’ keeper wallet. The LIQUIDATOR_FEE (10%) forwarded in bulk at batch end.

β›” PROTOCOL_ASSET Rescue Guard

rescueTokens(address token, uint256 amount) reverts unconditionally if token == PROTOCOL_ASSET β€” even a compromised owner cannot drain keeper rewards.

PriceOracle

Chainlink feeds with staleness validation

PriceOracle.sol wraps Chainlink's AggregatorV3Interface to serve 18-decimal normalised prices. Every getPrice(asset) call checks updatedAt against a per-asset heartbeat; exceeded β†’ reverts STALE_PRICE.

PriceOracle.sol β€” staleness check
SOLIDITY
function getPrice(address asset) external view returns (uint256) {
  AssetConfig memory cfg = assets[asset];
  require(cfg.feed != address(0), "ASSET_NOT_REGISTERED");

  (, int256 answer,, uint256 updatedAt,) = cfg.feed.latestRoundData();
  require(block.timestamp - updatedAt <= cfg.heartbeat, "STALE_PRICE");

  return uint256(answer) * 10 ** (18 - cfg.feedDecimals);
}

PnLCalculator

Pure math library β€” no storage

A pure library contract with no storage and no ownership. Centralising PnL math ensures PositionManager and LiquidationEngine never duplicate formulas or diverge on rounding.

calculatePnL(pos, currentPrice): size and price validated below int256.max / 2 before unchecked multiply β€” preventing silent wrap producing incorrect PnL.

⚠ int256 Overflow Safety

Both currentPrice and position.size are asserted below type(int256).max / 2. An unchecked wrap would silently produce enormous false profit or loss, bypassing all health checks.

PerpsErrors

Centralised custom error registry

All custom errors in a single file. Custom errors save ~200 bytes per revert string and reduce gas. Typed errors ABI-decode cleanly in block explorers and client libraries.

ZERO_AMOUNTDeposit or withdrawal of zero value
INSUFFICIENT_COLLATERALMargin below minimum to open position
LEVERAGE_TOO_HIGHLeverage exceeds asset maxLeverage
POSITION_NOT_FOUNDpositionId does not exist in storage
NOT_LIQUIDATABLEPosition health above liquidation threshold
STALE_PRICEChainlink price outside heartbeat window
DUPLICATE_MESSAGECCIP messageId already processed
ASSET_NOT_REGISTEREDNo feed configured for this asset
VAULT_INSOLVENTLP solvency invariant violated
BATCH_TOO_LARGEArray length exceeds maxBatchSize (20)

SmartAccount

ERC-4337

ERC-4337 compliant β€” gasless trading

Every Nexus user gets a smart contract wallet deployed by AccountFactory. Users sign UserOperations off-chain; bundler submits to EntryPoint. User never holds ETH.

EIP-712 typed signature validation
Nonce-based replay protection
executeBatch for multi-call
_disableInitializers() in constructor
StandardERC-4337
Gas SponsorNexusPaymaster
Sig SchemeEIP-712
DeploymentEIP-1167 Clone
AddressCounterfactual

β„Ή EIP-712 Typed Signing

Wallet displays target contract, value, calldata hash, nonce, and chainId. Prevents phishing attacks where malicious frontends trick users into signing unrelated operations.

NexusPaymaster

Gas sponsorship via EntryPoint

Pre-deposits ETH into EntryPoint. When a bundler submits a UserOperation, EntryPoint calls validatePaymasterUserOp: if approved, bundler is guaranteed payment and user never needs ETH.

Approval requires off-chain signature over (userOpHash, validUntil, validAfter, chainId). Including block.chainid prevents cross-chain replay. Packed storage: verifyingSigner (20 bytes) + maxCostLimit (12 bytes) fits in one slot.

NexusPaymaster.sol β€” signature verification
SOLIDITY
function _validateSignature(UserOperation calldata userOp) internal view {
  (uint48 validUntil, uint48 validAfter, bytes memory sig) =
    _decode(userOp.paymasterAndData);

  bytes32 hash = keccak256(abi.encode(
    userOpHash, validUntil, validAfter, block.chainid
  ));

  address recovered = ECDSA.recover(hash, sig);
  require(recovered == verifyingSigner, "INVALID_PAYMASTER_SIG");
}

AccountFactory

EIP-1167 minimal proxy deployment

Deploys SmartAccount instances as EIP-1167 minimal proxies (~45k gas vs 500k+ for full deployment). All clones share implementation bytecode but maintain independent storage.

Counterfactual deployment via CREATE2(salt = keccak256(owner, nonce)): address deterministic before any on-chain tx. Bundler calls factory atomically within the same EntryPoint call on first UserOperation.

β„Ή Counterfactual Address Stability

Frontend displays smart account address the moment user connects signing key. Users receive funds and share their address immediately β€” contract deploys automatically on the first trade.

CrossChainRouter

Chainlink CCIP β€” source chain component

Lives on source chains (Arbitrum, Base, Optimism…). User approves USDC and calls openCrossChainPosition. Router encodes trade params and collateral into a CCIP message and forwards to Chainlink's CCIP router.

CCIP message carries both data (encoded params) and a token transfer (USDC). Chainlink's DON verifies and delivers to MessageReceiver on Sepolia. No Nexus operator can censor or front-run.

MessageReceiver

Chainlink CCIP β€” Sepolia destination

Deployed on Sepolia. On ccipReceive: decodes payload β†’ deposits USDC into PerpsVault β†’ calls PositionManager.executeCrossChainTrade.

Maintains allowlisted senders mapping keyed by (sourceChainSelector, sender address). Only known CrossChainRouter addresses on approved chains can trigger ccipReceive. Per-trader nonce deduplication prevents replay before execution.

β›” Sender Allowlist is Critical

Always verify sender matches an approved CrossChainRouter before trusting decoded parameters. Failing to do so would allow any address on the source chain to trigger arbitrary trade execution.

Test Suite & Coverage

Foundry

95 tests Β· 0 failures Β· 14 test suites

Full Foundry test suite covering unit, integration, fuzz, and invariant layers.

ContractSelectorCallsRevertsDiscards
PositionHandlerchangeOraclePrice1,54100
PositionHandlercreateTrader1,60301
PositionHandleropenRandomPosition1,65900
PositionHandlertryLiquidation1,59800
Terminal
SHELL
forge test        # run all 95 tests (~3s)
forge coverage    # generate coverage report
forge test -vvv   # verbose output with traces

Security Model

Defense-in-depth for every attack surface

Oracle manipulationChainlink latestRoundData() + heartbeat staleness revert on every getPrice call
ReentrancyReentrancyGuard on settleTrade, transferByManager, batchLiquidate
LP inflation attackMINIMUM_LIQUIDITY = 1000 permanently burned on genesis deposit
Dust sweep / drainscaledAmount % DECIMALS_SCALAR != 0 reverts on every withdrawal
Cross-chain replayPer-trader nonce map in MessageReceiver + block.chainid in NexusPaymaster
Unauthorised CCIP callsonlyCrossChainReceiver + source chain whitelist + sender whitelist (3 layers)
Over-withdrawallockedCollateral tracking blocks withdrawing margin from open positions
Paymaster forgerykeccak256(userOpHash, block.chainid, address(this)) β€” chain + contract bound
CCIP pipeline blockingtry/catch in _ccipReceive β€” failed trades emit TradeFailed, never block pipeline
Keeper reward rug pullrescueTokens() unconditionally reverts for PROTOCOL_ASSET
Impl. contract init attack_disableInitializers() in SmartAccount constructor

⚠ No External Audit

No formal external security audit has been conducted. Deployed on Sepolia testnet with testnet assets only. Do not use with real funds.

Local Setup

Foundry + Next.js 14

Smart Contracts

Terminal
SHELL
git clone https://github.com/NexTechArchitect/Nexus-Protocol.git
cd Nexus-Protocol

# Install Foundry dependencies
forge install

# Run full test suite (95 tests, ~3s)
forge test -vv

# Deploy to Sepolia
cp .env.example .env
# Fill: PRIVATE_KEY, RPC_URL, ETHERSCAN_API_KEY
forge script script/deploy/05_FullDeploy.s.sol \
  --rpc-url sepolia --broadcast --verify

Frontend

Terminal
SHELL
cd web3-app && npm install

cp .env.local.example .env.local
# NEXT_PUBLIC_ALCHEMY_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY

npm run dev   # β†’ http://localhost:3000

Frontend Stack

Next.js 14 App Router Β· zero backend reads

Framework

Next.js 14 Β· TypeScript Β· App Router

Blockchain

Wagmi v2 Β· Viem

Wallet UI

RainbowKit

Queries

TanStack Query v5

Styling

Tailwind CSS

RPC

Alchemy β†’ PublicNode β†’ Infura

N
NEXUS PROTOCOLSepolia

Deterministic perpetuals. Chainlink oracles. ERC-4337 gasless accounts. Open-keeper liquidations.

Β© 2026 Nexus Protocol Β· MIT LicenseGitHub Live App