Files
zeekayandHanzo Dev 84587ddb0f Resolve the market before a swap can name a halt
SettleSwap took the caller's PoolKey and never asked whether that pool
existed, so key.ID() fed the halt slot, the volume shard and the fill
event alike. A fabricated key names a slot nobody set, and the swap
settles while the real market is halted.

Resolving once above the phase switch and passing the resolved poolID
down means no phase -- including one added later -- can key on anything
the registry has not admitted. checkHalt no longer accepts a key, so it
cannot derive one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 02:41:14 -07:00

741 lines
32 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package dex implements Uniswap v4-style DEX precompiles for Lux EVMs.
// This provides native singleton DEX functionality with flash accounting,
// hooks, and native token support for HFT across all Lux chains.
package dex
import (
"encoding/binary"
"errors"
"math/big"
"github.com/luxfi/crypto"
"github.com/luxfi/geth/common"
)
// Precompile addresses for LX components
// LP-aligned format: 0x0000000000000000000000000000000000LPNUM
// See LP-9015 for canonical specification
const (
// ─────────────────────────────────────────────────────────────────────────
// CANONICAL V4 DEX PRECOMPILE SURFACE — the 0x999x family, receipt-
// settlement, specified by LP-9999's address table.
//
// ONE money path, three read views, one position adapter. Each is its OWN
// modules.Module at its OWN address (orthogonal: a new primitive per slot,
// never bolted onto another). The split is by CONCERN, not by token:
//
// 0x9999 PoolManager — the SOLE value-moving module. swap settles a
// certified D-Chain fill receipt; initialize registers
// a market (C-authoritative); modifyLiquidity locks/
// unlocks maker reserves; deposit/withdraw fund/drain
// the vault; halt + governance live here.
// 0x9998 Quoter — read-only swap-quote VIEW (advisory, NON-authoritative;
// a real swap still needs a receipt). Reads the local D
// book off-consensus or the C-side registry projection.
// STRUCTURALLY INCAPABLE OF WRITING (see quoter_module.go).
// 0x9997 StateView — read-only pool/book/market/position/account VIEW.
// 0x9996 PositionManager— CLOB position adapter (NOT an ERC-721 NFT). Holds
// NO custody of its own: every lifecycle op routes into
// 0x9999 modifyLiquidity (+/- delta). Reads enumerate the
// 0x9999 ownerOrders index.
//
// Reserved-only (constants, deliberately NOT implemented this pass):
// 0x9995 Permit / 0x9994 FHE / 0x9993 Admin.
//
// Invariant: only 0x9999 moves value; 9998/9997 never write; 9996 composes
// 0x9999. No duplicate money paths; one consumedReceipt + one halt namespace
// (dex.precompile.v1.9999.*). DEXPoolManagerAddress == LXSettleAddress.
// ─────────────────────────────────────────────────────────────────────────
DEXPoolManagerAddress = "0x0000000000000000000000000000000000009999" // 0x9999 V4 PoolManager — THE money path (== LXSettleAddress)
DEXQuoterAddress = "0x0000000000000000000000000000000000009998" // 0x9998 Quoter — read-only swap-quote VIEW (advisory)
DEXStateViewAddress = "0x0000000000000000000000000000000000009997" // 0x9997 StateView — read-only pool/book/position VIEW
DEXPositionManagerAddress = "0x0000000000000000000000000000000000009996" // 0x9996 PositionManager — CLOB position adapter (composes 0x9999)
DEXPermitAddress = "0x0000000000000000000000000000000000009995" // 0x9995 Permit (RESERVED — constant only, not implemented)
DEXFHEAddress = "0x0000000000000000000000000000000000009994" // 0x9994 FHE (RESERVED — constant only, not implemented)
DEXAdminAddress = "0x0000000000000000000000000000000000009993" // 0x9993 Admin (RESERVED — constant only, not implemented)
// Core DEX (LP-901x) — RETIRED address space. 0x9010 was removed; 0x9999 is the
// sole DEX precompile. Nothing dispatches at these addresses, so they are kept so
// the values are not reused. The canonical surface is the LP-9999 family above.
//
// LXPoolAddress is not inert, though: it derives poolManagerAddr, the storage
// namespace of the undispatched PoolManager lineage, and lxPoolAddr, the log
// address that lineage emits under. No dispatched selector reads or writes it
// (TestBlueBNoDispatchedPathTouchesTheRetiredNamespace), which is what makes
// leaving it at 0x9010 rather than rebasing it onto 0x9999 orphan nothing.
LXPoolAddress = "0x0000000000000000000000000000000000009010" // 0x9010 LXPool — deprecated by LP-9999 (not dispatched)
LXOracleAddress = "0x0000000000000000000000000000000000009011" // LP-9011 LXOracle (legacy)
LXRouterAddress = "0x0000000000000000000000000000000000009012" // LP-9012 LXRouter (legacy)
LXHooksAddress = "0x0000000000000000000000000000000000009013" // LP-9013 LXHooks (legacy)
LXFlashAddress = "0x0000000000000000000000000000000000009014" // LP-9014 LXFlash (legacy)
// Orderbook (LP-902x)
LXBookAddress = "0x0000000000000000000000000000000000009020" // LP-9020 LXBook (legacy CLOB orderbook engine)
// Custody (LP-903x)
LXVaultAddress = "0x0000000000000000000000000000000000009030" // LP-9030 LXVault (custody + margin)
// Pricing (LP-904x)
LXPriceAddress = "0x0000000000000000000000000000000000009040" // LP-9040 LXPrice (derived/computed pricing)
// Lending (LP-905x–906x)
LXLendAddress = "0x0000000000000000000000000000000000009050" // LP-9050 LXLend (lending pool)
LXRepayerAddress = "0x0000000000000000000000000000000000009060" // LP-9060 LXRepayer (self-repaying loans)
// Liquidation + Transmutation (LP-907x–908x)
LXLiquidatorAddress = "0x0000000000000000000000000000000000009070" // LP-9070 LXLiquidator (liquidation engine)
LXTransmuterAddress = "0x0000000000000000000000000000000000009080" // LP-9080 LXTransmuter (debt → collateral conversion)
// Settlement (LP-909x)
// LXSettlementAddress is defined in fill_attestation.go // LP-9090 LXSettlement (settlement attestation)
LiquidFXAddress = LXTransmuterAddress
// Bridge Precompiles (LP-6xxx)
TeleportAddress = "0x0000000000000000000000000000000000006010" // LP-6010 Teleport (cross-chain)
)
// Gas costs optimized for HFT operations
const (
// Core DEX operations
GasPoolCreate uint64 = 50_000 // Create new pool
GasSwap uint64 = 50_000 // Single swap
GasAddLiquidity uint64 = 20_000 // Add liquidity
GasRemoveLiq uint64 = 20_000 // Remove liquidity
GasFlashLoan uint64 = 5_000 // Flash loan base
GasHookCall uint64 = 3_000 // Hook invocation
GasBalanceUpdate uint64 = 500 // Balance delta update
GasSettlement uint64 = 8_000 // Final settlement
GasPoolLookup uint64 = 100 // Pool state lookup
GasNativeTransfer uint64 = 2_100 // Native LUX transfer
GasAdmin uint64 = 2_100 // Admin operations (pause/freeze)
// Lending operations
GasSupply uint64 = 15_000 // Supply collateral
GasBorrow uint64 = 20_000 // Borrow against collateral
GasRepay uint64 = 15_000 // Repay debt
GasWithdraw uint64 = 15_000 // Withdraw collateral
GasLiquidate uint64 = 50_000 // Liquidate position
// Perpetual operations
GasOpenPosition uint64 = 25_000 // Open perp position
GasClosePosition uint64 = 25_000 // Close perp position
GasModifyMargin uint64 = 10_000 // Add/remove margin
GasSettleFunding uint64 = 10_000 // Settle funding rate
// Liquid operations (self-repaying loans)
GasDeposit uint64 = 20_000 // Deposit yield-bearing collateral
GasMint uint64 = 25_000 // Mint liquid tokens (L*)
GasBurn uint64 = 20_000 // Burn liquid tokens
GasRepayDebt uint64 = 15_000 // Manual debt repayment
GasHarvest uint64 = 30_000 // Harvest yield and repay debt
GasTransmute uint64 = 25_000 // Convert liquid token to underlying
// Teleport operations
GasTeleportInit uint64 = 50_000 // Initiate cross-chain transfer
GasTeleportComplete uint64 = 40_000 // Complete cross-chain transfer
)
// Pool fee tiers (basis points)
const (
Fee001 uint24 = 100 // 0.01% - stablecoins
Fee005 uint24 = 500 // 0.05% - stable pairs
Fee030 uint24 = 3000 // 0.30% - standard
Fee100 uint24 = 10000 // 1.00% - exotic pairs
FeeMax uint24 = 100000 // 10% max fee
)
// Tick spacing for different fee tiers
const (
TickSpacing001 int24 = 1
TickSpacing005 int24 = 10
TickSpacing030 int24 = 60
TickSpacing100 int24 = 200
)
// Hook flags (bitmap for hook capabilities).
// V4: flags are encoded in the TRAILING (least significant) bits of the hook address.
// Bit positions match Uniswap V4 Hooks.sol exactly.
type HookFlags uint16
const (
HookAfterRemoveLiquidityReturnsDelta HookFlags = 1 << 0
HookAfterAddLiquidityReturnsDelta HookFlags = 1 << 1
HookAfterSwapReturnsDelta HookFlags = 1 << 2
HookBeforeSwapReturnsDelta HookFlags = 1 << 3
HookAfterDonate HookFlags = 1 << 4
HookBeforeDonate HookFlags = 1 << 5
HookAfterSwap HookFlags = 1 << 6
HookBeforeSwap HookFlags = 1 << 7
HookAfterRemoveLiquidity HookFlags = 1 << 8
HookBeforeRemoveLiquidity HookFlags = 1 << 9
HookAfterAddLiquidity HookFlags = 1 << 10
HookBeforeAddLiquidity HookFlags = 1 << 11
HookAfterInitialize HookFlags = 1 << 12
HookBeforeInitialize HookFlags = 1 << 13
HookAllMask HookFlags = (1 << 14) - 1
)
// Currency represents a token (native or ERC20)
// Address(0) represents native LUX
type Currency struct {
Address common.Address
}
// NativeCurrency represents native LUX (no wrapping needed)
var NativeCurrency = Currency{Address: common.Address{}}
// IsNative returns true if this currency is native LUX
func (c Currency) IsNative() bool {
return c.Address == common.Address{}
}
// ToBytes serializes currency for storage
func (c Currency) ToBytes() []byte {
return c.Address.Bytes()
}
// CurrencyFromBytes deserializes currency from storage
func CurrencyFromBytes(data []byte) Currency {
return Currency{Address: common.BytesToAddress(data)}
}
// PoolKey uniquely identifies a pool
// Sorted by currency address (currency0 < currency1)
type PoolKey struct {
Currency0 Currency // Lower address token
Currency1 Currency // Higher address token
Fee uint24 // Fee in basis points
TickSpacing int24 // Tick spacing for concentrated liquidity
Hooks common.Address // Hook contract address (zero = no hooks)
}
// ID computes the unique pool identifier.
// V4: keccak256(abi.encode(poolKey)) where poolKey is 5 ABI slots (160 bytes).
func (pk PoolKey) ID() [32]byte {
data := EncodePoolKeyABI(pk)
var id [32]byte
copy(id[:], crypto.Keccak256(data))
return id
}
// ToBytes serializes pool key for storage
func (pk PoolKey) ToBytes() []byte {
data := make([]byte, 20+20+3+3+20) // 66 bytes
copy(data[0:20], pk.Currency0.ToBytes())
copy(data[20:40], pk.Currency1.ToBytes())
binary.BigEndian.PutUint32(data[40:44], uint32(pk.Fee))
// Shift to use only 3 bytes
copy(data[40:43], data[41:44])
binary.BigEndian.PutUint32(data[43:47], uint32(pk.TickSpacing))
copy(data[43:46], data[44:47])
copy(data[46:66], pk.Hooks.Bytes())
return data[:66]
}
// PoolKeyFromBytes deserializes pool key from storage
func PoolKeyFromBytes(data []byte) (PoolKey, error) {
if len(data) < 66 {
return PoolKey{}, errors.New("invalid pool key data length")
}
pk := PoolKey{}
pk.Currency0 = CurrencyFromBytes(data[0:20])
pk.Currency1 = CurrencyFromBytes(data[20:40])
var feeBytes [4]byte
copy(feeBytes[1:], data[40:43])
pk.Fee = uint24(binary.BigEndian.Uint32(feeBytes[:]))
var tickBytes [4]byte
copy(tickBytes[1:], data[43:46])
pk.TickSpacing = int24(binary.BigEndian.Uint32(tickBytes[:]))
pk.Hooks = common.BytesToAddress(data[46:66])
return pk, nil
}
// BalanceDelta represents the net token changes during a transaction
// Uses signed 128-bit integers for amount0 and amount1
// Positive = owed to the pool, Negative = owed to the user
type BalanceDelta struct {
Amount0 *big.Int // Currency0 delta (positive = user owes pool)
Amount1 *big.Int // Currency1 delta (positive = user owes pool)
}
// NewBalanceDelta creates a new balance delta
func NewBalanceDelta(amount0, amount1 *big.Int) BalanceDelta {
return BalanceDelta{
Amount0: new(big.Int).Set(amount0),
Amount1: new(big.Int).Set(amount1),
}
}
// ZeroBalanceDelta returns a zero balance delta
func ZeroBalanceDelta() BalanceDelta {
return BalanceDelta{
Amount0: big.NewInt(0),
Amount1: big.NewInt(0),
}
}
// Add combines two balance deltas
func (bd BalanceDelta) Add(other BalanceDelta) BalanceDelta {
return BalanceDelta{
Amount0: new(big.Int).Add(bd.Amount0, other.Amount0),
Amount1: new(big.Int).Add(bd.Amount1, other.Amount1),
}
}
// Sub subtracts another balance delta
func (bd BalanceDelta) Sub(other BalanceDelta) BalanceDelta {
return BalanceDelta{
Amount0: new(big.Int).Sub(bd.Amount0, other.Amount0),
Amount1: new(big.Int).Sub(bd.Amount1, other.Amount1),
}
}
// Negate inverts the balance delta signs
func (bd BalanceDelta) Negate() BalanceDelta {
return BalanceDelta{
Amount0: new(big.Int).Neg(bd.Amount0),
Amount1: new(big.Int).Neg(bd.Amount1),
}
}
// IsZero returns true if both amounts are zero
func (bd BalanceDelta) IsZero() bool {
return bd.Amount0.Sign() == 0 && bd.Amount1.Sign() == 0
}
// Pool represents the state of a liquidity pool
type Pool struct {
SqrtPriceX96 *big.Int // sqrt(price) * 2^96 (Q64.96)
Tick int24 // Current tick
Liquidity *big.Int // Total liquidity (L)
FeeGrowth0X128 *big.Int // Fee growth for currency0 (Q128.128)
FeeGrowth1X128 *big.Int // Fee growth for currency1 (Q128.128)
ProtocolFees0 *big.Int // Accumulated protocol fees currency0
ProtocolFees1 *big.Int // Accumulated protocol fees currency1
}
// IsInitialized returns true if the pool has been initialized
func (p *Pool) IsInitialized() bool {
return p.SqrtPriceX96 != nil && p.SqrtPriceX96.Sign() > 0
}
// NewPool creates a new uninitialized pool
func NewPool() *Pool {
return &Pool{
SqrtPriceX96: big.NewInt(0),
Tick: 0,
Liquidity: big.NewInt(0),
FeeGrowth0X128: big.NewInt(0),
FeeGrowth1X128: big.NewInt(0),
ProtocolFees0: big.NewInt(0),
ProtocolFees1: big.NewInt(0),
}
}
// Position represents a liquidity position
type Position struct {
Owner common.Address
TickLower int24
TickUpper int24
Liquidity *big.Int
FeeGrowthInside0LastX128 *big.Int
FeeGrowthInside1LastX128 *big.Int
TokensOwed0 *big.Int
TokensOwed1 *big.Int
}
// PositionKey computes the unique position identifier.
// V4: keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt))
// encodePacked: 20 bytes (address) + 3 bytes (int24) + 3 bytes (int24) + 32 bytes (bytes32) = 58 bytes.
func PositionKey(owner common.Address, tickLower, tickUpper int24, salt [32]byte) [32]byte {
data := make([]byte, 58) // 20 + 3 + 3 + 32
copy(data[0:20], owner.Bytes())
// tickLower as int24 (3 bytes, big-endian)
data[20] = byte(tickLower >> 16)
data[21] = byte(tickLower >> 8)
data[22] = byte(tickLower)
// tickUpper as int24 (3 bytes, big-endian)
data[23] = byte(tickUpper >> 16)
data[24] = byte(tickUpper >> 8)
data[25] = byte(tickUpper)
copy(data[26:58], salt[:])
var key [32]byte
copy(key[:], crypto.Keccak256(data))
return key
}
// SwapParams contains parameters for a swap.
// V4 convention: amountSpecified < 0 means exact input, > 0 means exact output.
type SwapParams struct {
ZeroForOne bool // true = swap currency0 for currency1
AmountSpecified *big.Int // Negative = exact input, Positive = exact output (V4 convention)
SqrtPriceLimitX96 *big.Int // Price limit (sqrt(price) * 2^96)
}
// ModifyLiquidityParams contains parameters for adding/removing liquidity
type ModifyLiquidityParams struct {
TickLower int24
TickUpper int24
LiquidityDelta *big.Int // Positive = add, Negative = remove
Salt [32]byte // Position salt for uniqueness
}
// FlashParams contains parameters for flash loans
type FlashParams struct {
Amount0 *big.Int // Amount of currency0 to borrow
Amount1 *big.Int // Amount of currency1 to borrow
Recipient common.Address // Recipient of borrowed tokens
Data []byte // Callback data
}
// Errors - Core DEX
var (
ErrPoolNotInitialized = errors.New("pool not initialized")
ErrPoolAlreadyInitialized = errors.New("pool already initialized")
ErrPoolExists = errors.New("pool already exists")
ErrPoolNotFound = errors.New("pool not found")
ErrInvalidTickRange = errors.New("invalid tick range")
ErrInsufficientLiquidity = errors.New("insufficient liquidity")
ErrPriceLimitReached = errors.New("price limit reached")
// The three fields a PoolKey or a liquidity range declares narrower than the
// 256-bit word carrying it. Each is refused where the bytes become a value,
// so no caller downstream has to know which of its arguments were bounded.
ErrInvalidFee = errors.New("invalid fee")
ErrInvalidTickSpacing = errors.New("invalid tick spacing")
ErrInvalidTick = errors.New("invalid tick")
ErrCurrencyNotSorted = errors.New("currencies not sorted")
ErrUnauthorized = errors.New("unauthorized")
// ErrDEXNoProtocolFeeController is returned when the DEX precompile is
// configured without an explicit protocolFeeController address (or with
// the zero address). Activating the precompile without a real controller
// would leave PauseDEX / ResumeDEX / FreezePool callable by anyone, so
// configuration must explicitly populate the controller. luxd refuses to
// advance past the activation block until upgrade.json is fixed.
ErrDEXNoProtocolFeeController = errors.New("dex: protocolFeeController must be set in upgrade.json — refusing to activate")
// ErrDEXCompromisedController is returned when the configured
// protocolFeeController matches a known publicly-disclosed development
// key (e.g. Foundry/Anvil account #0). The matching private key is in
// every Foundry user's ~/.foundry — any random caller could pause /
// freeze the chain-wide AMM. Activation fails closed.
ErrDEXCompromisedController = errors.New("dex: protocolFeeController is a publicly-known dev key — refusing to activate")
ErrInvalidHookResponse = errors.New("invalid hook response")
ErrSettlementFailed = errors.New("settlement failed")
ErrInvalidSqrtPrice = errors.New("invalid sqrt price")
ErrTickOutOfRange = errors.New("tick out of range")
ErrNoLiquidity = errors.New("no liquidity in pool")
// ErrDChainUnavailable is returned by every operation of the precompile when
// the node-LOCAL D-Chain (dexvm) is not reachable. The LP-9010 precompile is
// a thin C-Chain ON-RAMP to the D-Chain the validators run — it is NOT a
// matcher and NOT a backend selector. V4 = CLOB, so the matcher lives ONLY on
// the D-Chain; 0x9010 SUBMITS to / VERIFIES receipts from it over the local
// loopback. When this node serves the 0x9010 DEX path the local D-Chain MUST
// be running; if it is absent the precompile reverts cleanly with this error
// rather than fabricating a fill or masking the absent chain as a missing
// pool. See dchain_client.go (the dchainUnavailable client).
ErrDChainUnavailable = errors.New("dex: local D-Chain unavailable (node must run the dexvm D-Chain to serve LP-9010)")
// ErrCustodyUnbound is returned by Deposit/Withdraw when the StateDB carries no
// originating-tx identity (not txIdentified, or a zero txHash). The txHash is the
// idempotency ref threaded into the clob_deposit/clob_withdraw frame and the
// D-Chain seen: dedup key; with a zero ref two genuinely-distinct custody ops
// collide on (zero-ref‖user‖asset‖amount) and a replay can mint or double-release
// (the vault-drain class). A committed EVM tx ALWAYS has a unique non-zero hash,
// so refusing here closes the zero-ref door (defense-in-depth; prerequisite for
// the ERC-20 rail) without touching any real on-chain custody op. Only the
// non-EVM / zero-hash test path is refused — the production poolStateAdapter
// supplies a real per-tx txHash.
ErrCustodyUnbound = errors.New("dex: custody refused — no originating-tx context (zero idempotency ref)")
// ErrCustodyReentrant is returned when a custody op (Deposit/Withdraw) is
// re-entered while another custody op is already in progress on the same call
// stack — the classic reentrancy a malicious ERC-20 mounts from inside its
// transferFrom/transfer sub-call to double-mint (land a second pull inside the
// observed-delta window). The GLOBAL non-reentrant guard on the custody
// entrypoint reverts the nested call before it can move value, so the vault and
// the D-Chain ledger stay in lockstep (minted == vault).
ErrCustodyReentrant = errors.New("dex: custody reentrancy refused — a deposit/withdraw is already in progress")
)
// Errors - Pause/Freeze Controls (ATS regulatory compliance)
var (
ErrDEXPaused = errors.New("DEX: paused")
ErrPoolPaused = errors.New("pool: paused")
ErrPoolFrozen = errors.New("pool: frozen")
ErrPoolNotPaused = errors.New("pool: not paused")
ErrDEXNotPaused = errors.New("DEX: not paused")
ErrFreezeIrreversible = errors.New("pool: freeze is irreversible without governance")
ErrAlreadyPaused = errors.New("already paused")
ErrAlreadyFrozen = errors.New("already frozen")
)
// Errors - Lending
var (
ErrInsufficientCollateral = errors.New("insufficient collateral")
ErrHealthFactorTooLow = errors.New("health factor below minimum")
ErrBorrowCapExceeded = errors.New("borrow cap exceeded")
ErrReserveNotActive = errors.New("reserve not active")
ErrPositionNotLiquidatable = errors.New("position not liquidatable")
ErrReserveAlreadyExists = errors.New("reserve already exists")
ErrInvalidCollateralFactor = errors.New("invalid collateral factor")
ErrReserveNotFound = errors.New("reserve not found")
ErrReserveFrozen = errors.New("reserve is frozen")
ErrInvalidAmount = errors.New("invalid amount")
ErrSupplyCapExceeded = errors.New("supply cap exceeded")
ErrInsufficientBalance = errors.New("insufficient balance")
ErrBorrowDisabled = errors.New("borrow disabled for this reserve")
ErrPositionHealthy = errors.New("position is healthy, cannot liquidate")
ErrLiquidationTooSmall = errors.New("liquidation amount too small")
ErrFlashLiquidationDisabled = errors.New("flash liquidation disabled")
ErrInvalidParameter = errors.New("invalid parameter")
)
// Errors - Perpetuals
var (
ErrMaxLeverageExceeded = errors.New("max leverage exceeded (1111x)")
ErrInsufficientMargin = errors.New("insufficient margin")
ErrPositionNotFound = errors.New("position not found")
ErrInvalidPositionSize = errors.New("invalid position size")
ErrMarkPriceUnavailable = errors.New("mark price unavailable")
ErrBankruptPosition = errors.New("position is bankrupt")
)
// Errors - Liquid (self-repaying loans)
var (
ErrMaxLTVExceeded = errors.New("max LTV exceeded (90%)")
ErrInvalidYieldToken = errors.New("invalid yield-bearing token")
ErrDebtCeiling = errors.New("debt ceiling reached")
ErrNoDebtToRepay = errors.New("no debt to repay")
ErrTransmuterEmpty = errors.New("transmuter has no underlying")
ErrLiquidTokenNotRegistered = errors.New("liquid token not registered")
)
// Errors - Teleport
var (
ErrInvalidChainID = errors.New("invalid chain ID")
ErrTeleportNotFinalized = errors.New("teleport not finalized")
ErrDuplicateTeleportID = errors.New("duplicate teleport ID")
ErrInvalidWarpSignature = errors.New("invalid warp signature")
)
// Constants for math
var (
Q96 = new(big.Int).Lsh(big.NewInt(1), 96)
Q128 = new(big.Int).Lsh(big.NewInt(1), 128)
MinTick int24 = -887272
MaxTick int24 = 887272
MinSqrtRatio = new(big.Int).SetUint64(4295128739)
MaxSqrtRatio, _ = new(big.Int).SetString("1461446703485210103287273052203988822378723970342", 10)
)
// uint24 type alias for fees
type uint24 = uint32
// int24 type alias for ticks
type int24 = int32
// =========================================================================
// Lending Types
// =========================================================================
// LendingReserve represents a lending market for a single asset
type LendingReserve struct {
Asset Currency // The asset
TotalSupply *big.Int // Total supplied (in underlying)
TotalBorrow *big.Int // Total borrowed
SupplyIndex *big.Int // Cumulative supply interest index (Q128)
BorrowIndex *big.Int // Cumulative borrow interest index (Q128)
LastUpdateBlock uint64 // Last block interest was accrued
CollateralFactor *big.Int // Max borrow % (18 decimals, 0.8e18 = 80%)
LiquidationBonus *big.Int // Liquidator bonus (18 decimals, 0.05e18 = 5%)
BorrowCap *big.Int // Maximum borrowable amount
ReserveFactor *big.Int // Protocol fee on interest (18 decimals)
IsActive bool // Whether reserve accepts deposits
}
// LendingPosition represents a user's lending position
type LendingPosition struct {
Owner common.Address
Asset common.Address
SupplyShares *big.Int // User's share of supply pool
BorrowAmount *big.Int // Current borrow amount with interest
BorrowIndex *big.Int // Index when borrow was last updated
LastUpdateBlock uint64 // Block when position was last updated
}
// =========================================================================
// Perpetual Types
// =========================================================================
// MaxLeverage is the maximum allowed leverage (1111x)
const MaxLeverage uint32 = 1111
// PerpMarket represents a perpetual futures market
type PerpMarket struct {
BaseAsset Currency // The underlying asset (e.g., ETH)
QuoteAsset Currency // The quote asset (e.g., USD)
MarkPrice *big.Int // Current mark price (Q96)
IndexPrice *big.Int // Oracle index price (Q96)
OpenInterestLong *big.Int // Total long open interest
OpenInterestShort *big.Int // Total short open interest
FundingRate *big.Int // Current funding rate (signed, per 8h)
LastFundingTime int64 // Unix timestamp of last funding
MaxLeverage uint32 // Maximum leverage (default 1111x)
MaintenanceMargin *big.Int // Maintenance margin ratio (18 decimals)
InsuranceFund *big.Int // Market's insurance fund balance
}
// PerpPosition represents a user's perpetual position
type PerpPosition struct {
Owner common.Address
Market [32]byte // Market ID
Size *big.Int // Position size (positive=long, negative=short)
EntryPrice *big.Int // Average entry price (Q96)
Margin *big.Int // Deposited margin
LastFundingIndex *big.Int // Funding index at last update
IsIsolated bool // Isolated vs cross margin
}
// FundingState tracks funding rate calculation state
type FundingState struct {
CumulativeFunding *big.Int // Cumulative funding per unit size
LastUpdateTime int64 // Last funding settlement time
PremiumEMA *big.Int // Exponential moving average of premium
TWAPWindow uint64 // TWAP window in seconds (default 8h)
}
// =========================================================================
// Liquid Types (self-repaying loans with 90% LTV)
// =========================================================================
// Liquid protocol parameters
const (
// MaxLTV is 90% - allows 90% of collateral to be minted as debt
MaxLTV = 9000 // 90.00% in basis points
// MinLTV is the minimum LTV to maintain position
MinLTV = 100 // 1.00% in basis points
// LTVPrecision for basis point calculations
LTVPrecision = 10000
)
// LiquidToken represents a liquid asset (e.g., LUSD, LETH, LBTC)
type LiquidToken struct {
Address common.Address // Liquid token address
UnderlyingAsset Currency // The underlying asset it tracks
TotalMinted *big.Int // Total liquid tokens minted
DebtCeiling *big.Int // Maximum mintable
MintFee uint24 // Fee on minting (basis points)
BurnFee uint24 // Fee on burning (basis points)
}
// YieldToken represents an approved yield-bearing collateral token
// (e.g., LP tokens from DEX pools, yield vault shares)
type YieldToken struct {
Address common.Address // Yield token address
UnderlyingAsset Currency // The underlying asset
YieldPerBlock *big.Int // Expected yield per block (for estimation)
IsActive bool // Whether deposits are accepted
TotalDeposited *big.Int // Total deposited in Liquid
}
// LiquidAccount represents a user's self-repaying loan position
type LiquidAccount struct {
Owner common.Address
YieldToken common.Address // The yield-bearing collateral token
Collateral *big.Int // Amount of yield token deposited
Debt *big.Int // Amount of liquid token debt owed
LastHarvestBlock uint64 // Last block yield was harvested
AccruedYield *big.Int // Unharvested yield (auto-repays debt)
}
// LiquidFXState manages the conversion of L* tokens back to underlying
type LiquidFXState struct {
LiquidToken common.Address // The liquid token (e.g., LUSD)
UnderlyingAsset Currency // The underlying (e.g., USDC)
ExchangeBuffer *big.Int // Underlying available for exchange
TotalStaked *big.Int // Total liquid tokens staked for transmutation
ExchangeRate *big.Int // Current exchange rate (Q96)
}
// =========================================================================
// Teleport Types
// =========================================================================
// Supported chains for Teleport
const (
ChainLux uint32 = 96369 // Lux mainnet
ChainHanzo uint32 = 36963 // Hanzo chain
ChainZoo uint32 = 200200 // Zoo chain
ChainETH uint32 = 1 // Ethereum mainnet
ChainArb uint32 = 42161 // Arbitrum One
ChainOP uint32 = 10 // Optimism
ChainBase uint32 = 8453 // Base
ChainPoly uint32 = 137 // Polygon
ChainBSC uint32 = 56 // BNB Chain
ChainAvax uint32 = 43114 // Avalanche C-Chain
)
// TeleportRequest represents a cross-chain transfer request
type TeleportRequest struct {
TeleportID [32]byte // Unique identifier
SourceChain uint32 // Source chain ID
DestChain uint32 // Destination chain ID
Sender common.Address // Sender on source chain
Recipient common.Address // Recipient on dest chain
Token common.Address // Token being teleported
Amount *big.Int // Amount to teleport
Timestamp int64 // Request timestamp
Status TeleportStatus // Current status
}
// TeleportStatus represents the state of a teleport
type TeleportStatus uint8
const (
TeleportPending TeleportStatus = iota
TeleportBurned // Burned on source chain
TeleportValidated // Validated by MPC/Warp
TeleportMinted // Minted on destination chain
TeleportFailed // Failed (can be retried)
)
// WarpMessage represents a cross-chain message via Lux Warp
type WarpMessage struct {
SourceChainID [32]byte // Source chain ID (Lux format)
Payload []byte // Message payload
Signatures []byte // BLS aggregate signature
BitSet []byte // Validator bitset
}
// OmnichainRoute represents a multi-hop cross-chain swap route
type OmnichainRoute struct {
Hops []RouteHop // Sequence of hops
TotalEstimate *big.Int // Estimated output amount
TotalGas uint64 // Estimated total gas
}
// RouteHop represents a single hop in a cross-chain route
type RouteHop struct {
ChainID uint32 // Chain for this hop
PoolID [32]byte // Pool to use on this chain
TokenIn common.Address // Input token
TokenOut common.Address // Output token
AmountIn *big.Int // Amount in
MinAmountOut *big.Int // Minimum output
}