Documentation

Security & Trust

Levant is unaudited software running on a public testnet. This page states plainly what you are trusting today, which risks are not yet mitigated, and which on-chain guards bound your loss — so you can decide for yourself rather than take our word for it.

Security posture: honest and early

Levant is a set of EIP-2535 Diamond smart contracts plus an off-chain keeper. The contracts are built with Foundry on Solidity 0.8.28 and ship with 171 passing tests, but they are unaudited and run only on Robinhood Chain testnet (chainId 4663). Tests and careful design reduce risk; they do not eliminate it. Treat everything here as experimental. The sections below describe exactly who and what you are trusting, and where the sharp edges are.

Warning
Trust today reduces to key custody. The price oracle is 1-of-1 (a single keeper key signs every price) and diamond ownership is still on the deployer's hot key. A compromise of either key could price the entire exchange or upgrade the contracts. Combined with unaudited code and a testnet-only deployment, this is not a system to secure real value on. Testnet tokens have no monetary value.

What Levant is — and is not — today

  • Testnet only. Deployed on Robinhood Chain mainnet, chainId 4663. Gas is Sepolia ETH. Nothing here has real-world value.
  • Unaudited. No third-party audit has been performed. 171 unit/integration tests pass, but that is not an audit.
  • Mock collateral. USDG is a MockStable test token (6 decimals) with an open mint — anyone can mint themselves test funds. There is no canonical USDG on testnet, and pretending otherwise would be dishonest.
  • $LVNT is separate from the exchange. Levant's token, $LVNT, launched on Virtuals and is not required to trade; the exchange runs on USDG. lvUSDG is only the ERC-4626 share receipt for the liquidity vault, not the token.
  • Single operator. One keeper runs pricing and execution today. It is a single point of failure for liveness (see below).
  • Non-custodial. Your collateral is escrowed by the Diamond contract, never by the keeper. No operator can move your funds at will.

The trust model right now

Every component sits somewhere on a spectrum from trustless to fully trusted. Here is where each one actually stands today, with no rounding up.

ComponentStatus todayWhat you are trusting
ContractsUnaudited, 171 testsThat the code is correct and the tests are representative
Price oracle1-of-1 signed (single keeper key)That one signing key is not compromised or misused
OwnershipDeployer hot keyThat one key controls all upgrades and every risk parameter
LiquidationsPermissionlessAnyone with a valid signed price can liquidate an underwater position
Custody of fundsEscrowed by the DiamondThe contract holds your collateral — the keeper never does
Collateral (USDG)Mock token, open mintTestnet only; no monetary value

Custody: the contract holds your funds, not the keeper

Trading is a two-step flow, and the split is what keeps it non-custodial. Step 1: you escrow collateral on-chain — submitOpenIntent for a market order, submitLimitIntent for a limit or stop. Step 2: a keeper fills the intent with a signed price report via executeOpen. Closing is symmetric: closePosition or closePartial, then executeClose. At no point does the keeper hold your money — funds are locked in the Diamond and released only by the contract's own logic. The keeper is bounded by on-chain guards: it can only ever fill at a verified price that also passes the slippage and liquidatable-at-open checks. Its key is funded with gas only.

Note
If your intent is never filled, your escrow is not stuck. Any pending intent can be reclaimed in full with cancelIntent — the funds were only ever escrowed, never spent.

The oracle and why it must be authenticated

Prices come from SignedPriceOracleFacet, a self-hosted EIP-712 m-of-n signed-price oracle. This design exists for one reason: permissionless liquidation is only safe when a price cannot be fabricated. liquidatePosition accepts a price report from *any* caller, so if the oracle did not authenticate its input, anyone could liquidate anyone at an invented price and collect the 5% reward. The signed oracle closes that hole by verifying every price on-chain before the engine will act on it.

Each price is an EIP-712 PriceReport signed by whitelisted signers. The Diamond re-derives the digest and checks a chain of conditions before returning a price; if any fails, the whole action (open, close, or liquidation) reverts.

EIP712Domain { name: "Levant", version: "1", chainId: 4663, verifyingContract: <the Diamond> }
PriceReport  { bytes32 marketId, uint256 price1e18, uint64 publishTime }

getVerifiedPrice rejects the report unless ALL hold:
  reportMarketId == the market being priced        // else MarketMismatch  (no cross-market replay)
  price1e18 != 0                                    // else BadPrice
  publishTime <= block.timestamp + 5s              // else FutureTimestamp (clock-skew guard)
  block.timestamp <= publishTime + 60s             // else StalePrice     (maxPriceAge = 60s)
  signatures.length >= threshold                   // else ThresholdNotMet
  recovered signers strictly ascending + whitelisted// else SignersOutOfOrder / NotSigner

// chainId + verifyingContract in the domain prevent cross-chain / cross-contract replay.
// OpenZeppelin ECDSA rejects malleable (high-s) and invalid-v signatures.
  • Forged prices are blocked by ECDSA over the EIP-712 digest; only owner-whitelisted keys can sign.
  • A single rogue key is contained by the m-of-n threshold — once more than one signer exists.
  • Duplicate signatures cannot pad a quorum: recovered signers must strictly ascend, so one key cannot satisfy the threshold by signing m times.
  • Stale prices are rejected 60 seconds after publishTime; a price dated more than 5 seconds in the future is rejected too.
  • Cross-market and cross-chain replay are blocked by binding marketId inside the struct and chainId + the Diamond address in the domain.

Under the hood, prices are sourced from a multi-provider feed — Gate.io first, then KuCoin, then CoinGecko as a fallback — and signed in chain time, so the keeper corrects for the chain clock running behind wall-clock rather than tripping the staleness check. removePriceSigner refuses to leave the threshold unsatisfiable, and setMaxPriceAge rejects a zero window, so an owner cannot accidentally freeze pricing (which would freeze every open, close, and liquidation).

Warning
The oracle is 1-of-1 today: the keeper is the sole signer, so trust reduces to custody of that one signing key. A single compromised key could price the entire exchange and drain the vault through the permissionless liquidation path. Running m-of-n signers on independent machines is on the roadmap and is a prerequisite before any real value.

On-chain guards that bound your risk

Independent of who the operator is, several guards are enforced by the contract itself. They cap the price you can be filled at, refuse trades that are dead on arrival, and bound your maximum loss to the margin you posted.

GuardRuleEffect on a violation
Slippage toleranceEvery open/limit intent carries a mandatory maxSlippageBps in [1, 500] — no implicit defaultexecuteOpen reverts SlippageExceeded; intent stays pending and escrow is reclaimable with cancelIntent
Liquidatable-at-openRefuse a position already at/under 10% maintenance margin at the fill markReverts LiquidatableAtOpen; no open fee is taken
Spread floorA fill is never better than midTotal spread floored at 0 bps
Spread capThe spread can never exceed 5%Total spread capped at 500 bps
Isolated marginEach position's loss is bounded to its own marginOne position can never drain another
Max-profit reserveVault locks margin x 9 per positionGuarantees the capped 900%-of-margin payout is funded
spreadBps = clamp( 2 + priceImpact + skew , 0 , 500 )   // >= 0 (never beats mid), <= 500 (5% cap)
execPrice = mid * (1 +/- spreadBps/1e4)                  // applied worse-for-trade

executeOpen reverts SlippageExceeded    if the spread pushes entry past your maxSlippageBps (1..500)
executeOpen reverts LiquidatableAtOpen  if the new position is already <= 10% maintenance at the fill
// on either revert: the intent is NOT consumed and no open fee is charged

isolated margin -> max loss = your margin
reserved        = margin x 9            // locked in the vault to fund the 900% max-profit cap

The slippage guard is not cosmetic. At 100x leverage a 1% entry spread is a 100% loss before the position even exists, so the liquidatable-at-open check refuses to mint a position that would be dead on arrival, and no fee is taken. In the UI you pick a Max slippage — 0.10%, 0.30%, 0.50%, or 1.00% — and the engine holds you to it.

Permissionless liquidation

Liquidation is open to anyone. A position becomes liquidatable when its equity falls to or below the 10% maintenance margin — roughly a 90% loss including accrued funding. Any caller can close it by submitting a signed oracle price report; the contract re-checks the condition against that verified price, so a liquidation only settles if the position is genuinely underwater. The liquidator is paid 5% of collateral for keeping the book solvent, and no adverse spread is applied on the liquidation mark. Because margin is isolated, only the liquidated position is affected — never your other positions or unrelated traders.

Governance and upgrades

Levant is one EIP-2535 Diamond address with many facets over append-only Diamond-Storage. Upgrades are performed with diamondCut, which preserves the vault, LP shares, and every open position — storage fields are only ever appended, never reordered. The owner is the most powerful account in the system: it can diamondCut any facet, add or remove price signers, set the signer threshold, and rewrite every market risk parameter. That authority is real, so treat it as part of your risk assessment.

Ownership uses the two-step ERC-173 pattern (OwnershipFacet). transferOwnership only nominates a new owner; nothing changes until that nominee calls acceptOwnership, which proves the address is controllable and cannot be a typo, an exchange address, or a contract that can never call back. cancelOwnershipTransfer withdraws a pending nomination. There is deliberately no `renounceOwnership` — an ownerless diamond could never be cut again, so a future bug would be permanent.

Warning
Ownership is still on the deployer's hot key. The two-step transfer facility is deployed and ready, but the handover to a hardware wallet or multisig has not yet been done. Until it is, one key can upgrade the contracts and change every risk parameter.

The keeper is a single point of failure for liveness

The keeper is off-chain and non-custodial, but it is currently the only one. While it is down, nothing fills, nothing closes, and nothing liquidates. Open positions are frozen, not safe — the mark can keep moving against you while no keeper is available to execute your stop-loss or close. This is a liveness risk you carry on top of market risk, and it is one of the reasons this is a testnet document.

Verify it yourself — don't trust us

Every claim on this page is checkable on-chain. Read the live state of the Diamond directly rather than trusting this doc or any UI. Everything below is public and read-only.

WhatAddress
Diamond (all user actions)0xE07098bc29Ebe50E6203606bc2eA0e1E189cb7e2
Owner (deployer key, today)0x520F136b72Cc7A24FDB1C073189704442e9945F6
Price signer / keeper0x5040b3387F450Af658f8E7C93ca3F0D44b07E7e4
Vault (lvUSDG, ERC-4626)0xC90D98cFeE95F481aDE30d95AD88f01B6C65ad5C
USDG (mock collateral, open mint)0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
# Robinhood Chain mainnet, chainId 4663
# explorer: https://robinhoodchain.blockscout.com
DIAMOND=0xE07098bc29Ebe50E6203606bc2eA0e1E189cb7e2

cast call $DIAMOND "owner()(address)"          # -> 0x520F...945F6 (deployer key, today)
cast call $DIAMOND "priceSignerCount()(uint8)" # -> 1   (1-of-1 oracle today)
cast call $DIAMOND "signerThreshold()(uint8)"  # -> 1
cast call $DIAMOND "maxPriceAge()(uint64)"     # -> 60  (seconds)
cast call $DIAMOND "isPriceSigner(address)(bool)" \
  0x5040b3387F450Af658f8E7C93ca3F0D44b07E7e4   # -> true

Reporting a vulnerability

If you find a security issue, please disclose it responsibly and privately. Do not open a public issue, post it on-chain, or exploit it against live positions. Report it privately to the maintainers — for example via a private security advisory on the project's source repository — and give them time to ship a fix. A diamondCut can patch a single facet without disturbing the vault, LP shares, or open positions, so a well-scoped report can usually be remediated quickly.

  • Include the chain (chainId 4663), the affected contract address, and any transaction hashes.
  • Describe the impact and give a concrete reproduction — a failing test or a script is ideal.
  • This is unaudited testnet software with no bug-bounty program and no real funds at stake, so there is no reward on offer and no need to demonstrate an exploit against real value. A clear write-up is enough.
  • Please do not publicly disclose until a fix has been deployed and verified on-chain.

Roadmap to a harder posture

  • m-of-n oracle — run multiple signers on independent machines and raise the threshold, so no single key can price the exchange.
  • Move ownership off the hot key — complete the two-step transfer to a hardware wallet or multisig.
  • Chainlink Data Streams — the engine already exposes the same getVerifiedPrice selector, so the signed oracle can be swapped for a DON-backed feed via diamondCut wherever credentials are available.
  • Independent audit — before any deployment that holds real value.
Tip
Bottom line: the on-chain guards genuinely bound your loss to the margin you post, but the operator-trust and unaudited-code risks are real and unmitigated today. Treat Levant as experimental testnet software, and never commit anything you are not prepared to lose entirely. None of this is investment advice. Leverage can lose your full margin, and access to leveraged derivatives is restricted in some jurisdictions — see the Risk Disclaimer.