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
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.
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.
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);
}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.
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.
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.
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.
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.
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;
}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.
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.
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.
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);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.
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.
SmartAccount
ERC-4337ERC-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 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.
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
Foundry95 tests Β· 0 failures Β· 14 test suites
Full Foundry test suite covering unit, integration, fuzz, and invariant layers.
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
β 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
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
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