feat(integrations): add Hummingbot Gateway connector for LX DEX

Add official Hummingbot Gateway connector enabling algorithmic trading on
LX DEX. The connector supports all four trading schemas:

- Router: DEX aggregation with optimal swap routing
- AMM: Traditional xy=k liquidity pool operations
- CLMM: Concentrated liquidity market maker positions
- CLOB: Central limit order book trading

Includes:
- Full connector implementation with WebSocket support
- Fastify route handlers for all endpoints
- Plugin package for easy Gateway integration
- Comprehensive API documentation
- Quick start and strategy guides
- Jest test suite
This commit is contained in:
2025-12-11 19:36:46 -08:00
parent afa78c21c7
commit 9e54603e26
25 changed files with 10800 additions and 0 deletions
@@ -0,0 +1,212 @@
---
title: Hummingbot Quick Start
description: Get started with Hummingbot and LX DEX in 5 minutes
---
# Hummingbot Quick Start
Get your first trading bot running on LX DEX in under 5 minutes.
## Prerequisites
- [Docker](https://docker.com) installed
- A Lux wallet with some LUX for gas
- Basic terminal/command line knowledge
## Step 1: Clone Hummingbot
```bash
git clone https://github.com/hummingbot/hummingbot.git
cd hummingbot
```
## Step 2: Install LX DEX Connector
```bash
# Install the LX DEX Gateway connector
cd gateway
npm install @lxdex/hummingbot-gateway-connector
```
## Step 3: Configure LX DEX
Create the configuration file:
```bash
cat > conf/lxdex.yml << 'EOF'
enabled: true
chain: lux
networks:
- testnet # Start with testnet!
trading:
slippagePct: 0.5
maxHops: 3
testnet:
chainId: 9393
apiEndpoint: https://api.testnet.dex.lux.network
wsEndpoint: wss://ws.testnet.dex.lux.network
EOF
```
## Step 4: Start Gateway
```bash
# Using Docker Compose
docker-compose up -d gateway
# Or manually
npm run start
```
Verify Gateway is running:
```bash
curl http://localhost:15888/lxdex/health
```
Expected response:
```json
{
"status": "healthy",
"network": "testnet",
"timestamp": 1702300000000
}
```
## Step 5: Start Hummingbot Client
```bash
docker-compose up -d hummingbot
docker attach hummingbot
```
## Step 6: Connect to LX DEX
In the Hummingbot client:
```
>>> connect lxdex
```
Enter your wallet private key when prompted.
## Step 7: Create a Simple Strategy
Let's create a basic market making strategy:
```
>>> create
```
Select options:
1. Strategy: `pure_market_making`
2. Exchange: `lxdex`
3. Trading pair: `LUX-USDC`
4. Bid spread: `0.5%`
5. Ask spread: `0.5%`
6. Order amount: `10`
## Step 8: Start Trading
```
>>> start
```
Watch your bot create orders! You can monitor with:
```
>>> status
>>> history
>>> open_orders
```
## Step 9: Stop When Done
```
>>> stop
>>> exit
```
## Quick Commands Reference
| Command | Description |
|---------|-------------|
| `connect lxdex` | Connect to LX DEX |
| `balance` | Check wallet balances |
| `ticker LUX-USDC` | Get current price |
| `create` | Create new strategy |
| `start` | Start strategy |
| `stop` | Stop strategy |
| `status` | Check strategy status |
| `history` | View trade history |
## Verify on Explorer
Check your transactions on the [Lux Explorer](https://testnet.explorer.lux.network):
1. Go to the explorer
2. Search for your wallet address
3. View your trading transactions
## Troubleshooting
### Gateway Connection Failed
```bash
# Check Gateway is running
curl http://localhost:15888/lxdex/health
# Check logs
docker logs gateway
```
### Insufficient Balance
Get testnet LUX from the [faucet](https://faucet.lux.network).
### Order Failed
Check slippage settings. For testnet with lower liquidity:
```yaml
trading:
slippagePct: 2.0 # Increase for testnet
```
## Next Steps
Now that you have a basic setup:
1. **[Strategy Guide](/docs/integrations/hummingbot-strategies)**: Learn advanced strategies
2. **[API Reference](/docs/integrations/hummingbot)**: Full endpoint documentation
3. **[Deploy to Mainnet](#mainnet-deployment)**: Go live with real funds
## Mainnet Deployment
When ready for mainnet:
1. Update configuration:
```yaml
networks:
- mainnet
mainnet:
chainId: 93
apiEndpoint: https://api.dex.lux.network
wsEndpoint: wss://ws.dex.lux.network
```
2. Fund your wallet with real LUX
3. Start with small amounts
4. Monitor closely for the first few hours
## Getting Help
- **Discord**: [Join our community](https://discord.gg/lux)
- **GitHub**: [Report issues](https://github.com/luxfi/dex/issues)
- **Hummingbot Discord**: [Hummingbot community](https://discord.gg/hummingbot)
@@ -0,0 +1,420 @@
---
title: Hummingbot Strategies
description: Trading strategies for LX DEX with Hummingbot
---
# Hummingbot Strategies for LX DEX
This guide covers popular trading strategies you can run on LX DEX using Hummingbot.
## Strategy Overview
| Strategy | Risk | Complexity | Capital Required |
|----------|------|------------|------------------|
| Pure Market Making | Medium | Low | Medium |
| Cross-Exchange MM | Low | Medium | High |
| Arbitrage | Low | Medium | Medium |
| CLMM Range Orders | Medium | High | Medium |
| Liquidity Mining | Low | Low | Low |
## Pure Market Making
The simplest strategy: place buy and sell orders around the current price.
### Configuration
```yaml
# conf/pure_mm_lxdex.yml
strategy: pure_market_making
exchange: lxdex
market: LUX-USDC
# Spread settings
bid_spread: 0.5% # Buy 0.5% below mid price
ask_spread: 0.5% # Sell 0.5% above mid price
# Order settings
order_amount: 100 # Order size in LUX
order_levels: 3 # Number of orders per side
order_level_spread: 0.2% # Spread between levels
# Risk management
inventory_target_base_pct: 50% # Target 50% LUX, 50% USDC
inventory_range_multiplier: 1.5
# Order refresh
order_refresh_time: 30s
filled_order_delay: 10s
# Advanced
hanging_orders_enabled: true
hanging_orders_cancel_pct: 5%
```
### Starting the Strategy
```bash
>>> config pure_mm_lxdex.yml
>>> start
```
### Optimal Parameters for LX DEX
Due to LX DEX's low latency, you can use tighter spreads:
| Market Condition | Bid Spread | Ask Spread | Refresh Time |
|------------------|------------|------------|--------------|
| High volatility | 1.0% | 1.0% | 15s |
| Normal | 0.3% | 0.3% | 30s |
| Low volatility | 0.1% | 0.1% | 60s |
## Cross-Exchange Market Making
Arbitrage between LX DEX and centralized exchanges.
### Configuration
```yaml
# conf/cross_exchange_mm.yml
strategy: cross_exchange_market_making
maker_exchange: lxdex
taker_exchange: binance
maker_market: LUX-USDC
taker_market: LUX-USDT
# Spread requirements
min_profitability: 0.2%
# Order settings
order_amount: 50
top_depth_tolerance: 0.1%
# Hedging
hedging_enabled: true
hedging_delay: 0.5s
```
### Risk Management
- Keep sufficient inventory on both exchanges
- Monitor funding costs for borrowed assets
- Set maximum position limits
## AMM Liquidity Provision
Provide liquidity to LX DEX AMM pools.
### Using the API Directly
```typescript
import { LXDex } from '@lxdex/hummingbot-gateway-connector';
const connector = LXDex.getInstance('mainnet');
// Check pool info first
const poolInfo = await connector.getPoolInfo({
tokenA: 'LUX',
tokenB: 'USDC',
});
const pool = poolInfo.pools[0];
console.log(`APY: ${pool.apy}%`);
console.log(`TVL: $${pool.tvlUSD}`);
// Add liquidity
const result = await connector.addLiquidity({
walletAddress: 'your-wallet',
tokenA: 'LUX',
tokenB: 'USDC',
amountA: '1000000000000000000000', // 1000 LUX
amountB: '12500000000', // 12,500 USDC
slippagePct: 0.5,
});
console.log(`LP tokens minted: ${result.liquidityMinted}`);
```
### Custom LP Strategy Script
```python
# scripts/lxdex_lp_strategy.py
from hummingbot.strategy.script_strategy_base import ScriptStrategyBase
class LXDexLPStrategy(ScriptStrategyBase):
markets = {"lxdex": {"LUX-USDC"}}
def __init__(self):
super().__init__()
self.target_pool_share = 0.01 # Target 1% of pool
self.rebalance_threshold = 0.1 # Rebalance if off by 10%
async def on_tick(self):
# Get current position
position = await self.connector.get_position_info({
"walletAddress": self.wallet_address,
})
# Get pool info
pool = await self.connector.get_pool_info({
"tokenA": "LUX",
"tokenB": "USDC",
})
current_share = float(position.share_percent) / 100
# Rebalance if needed
if abs(current_share - self.target_pool_share) > self.rebalance_threshold:
await self.rebalance(pool, position)
```
## CLMM Range Orders
Concentrated liquidity strategies for maximum capital efficiency.
### Tight Range Strategy
Best for stable pairs or when you have a strong price view:
```typescript
const connector = LXDex.getInstance('mainnet');
// Get current pool state
const poolInfo = await connector.getCLMMPoolInfo({
tokenA: 'USDC',
tokenB: 'USDT',
fee: 500, // 0.05% fee tier for stables
});
const pool = poolInfo.pools[0];
const currentTick = pool.currentTick;
// Set tight range: +/- 0.1%
// Each tick is ~0.01% price change
const tickLower = currentTick - 100;
const tickUpper = currentTick + 100;
// Open position
const result = await connector.openPosition({
walletAddress: 'your-wallet',
tokenA: 'USDC',
tokenB: 'USDT',
fee: 500,
tickLower,
tickUpper,
amountA: '10000000000', // 10,000 USDC
amountB: '10000000000', // 10,000 USDT
});
console.log(`Position NFT ID: ${result.tokenId}`);
```
### Active Range Management
Monitor and adjust positions as price moves:
```python
# scripts/clmm_range_manager.py
import asyncio
from typing import List
class CLMMRangeManager:
def __init__(self, connector, wallet_address):
self.connector = connector
self.wallet = wallet_address
self.check_interval = 60 # Check every minute
async def monitor_positions(self):
while True:
positions = await self.connector.get_positions_owned({
"walletAddress": self.wallet,
})
for pos in positions.positions:
if not pos.in_range:
print(f"Position {pos.token_id} out of range!")
await self.handle_out_of_range(pos)
await asyncio.sleep(self.check_interval)
async def handle_out_of_range(self, position):
# Option 1: Close position and collect fees
await self.connector.close_position({
"walletAddress": self.wallet,
"tokenId": position.token_id,
})
# Option 2: Just collect fees and keep position
# await self.connector.collect_fees({
# "walletAddress": self.wallet,
# "tokenId": position.token_id,
# })
```
## Arbitrage Strategies
### DEX-CEX Arbitrage
```python
# scripts/dex_cex_arb.py
class DexCexArbitrage:
def __init__(self, lxdex_connector, cex_connector):
self.lxdex = lxdex_connector
self.cex = cex_connector
self.min_profit_bps = 10 # Minimum 0.1% profit
async def find_opportunities(self, pair: str):
# Get LX DEX price
lxdex_quote = await self.lxdex.get_quote({
"baseToken": pair.split("/")[0],
"quoteToken": pair.split("/")[1],
"amount": "1000000000000000000", # 1 token
"side": "SELL",
})
# Get CEX price
cex_price = await self.cex.get_ticker(pair)
lxdex_price = float(lxdex_quote.price)
cex_bid = float(cex_price.bid)
cex_ask = float(cex_price.ask)
# Check for arbitrage
if lxdex_price > cex_ask * (1 + self.min_profit_bps / 10000):
return {
"direction": "buy_cex_sell_lxdex",
"profit_bps": (lxdex_price / cex_ask - 1) * 10000,
}
elif cex_bid > lxdex_price * (1 + self.min_profit_bps / 10000):
return {
"direction": "buy_lxdex_sell_cex",
"profit_bps": (cex_bid / lxdex_price - 1) * 10000,
}
return None
```
### Cross-AMM Arbitrage
Find price discrepancies between AMM pools:
```typescript
async function findAmmArbitrage(connector: LXDex, tokenPath: string[]) {
const quotes = [];
// Get quote for direct path
const directQuote = await connector.getQuote({
baseToken: tokenPath[0],
quoteToken: tokenPath[tokenPath.length - 1],
amount: '1000000000000000000',
side: 'SELL',
maxHops: 1,
});
// Get quote through intermediate tokens
const multiHopQuote = await connector.getQuote({
baseToken: tokenPath[0],
quoteToken: tokenPath[tokenPath.length - 1],
amount: '1000000000000000000',
side: 'SELL',
maxHops: 3,
});
const directOutput = BigInt(directQuote.amountOut);
const multiHopOutput = BigInt(multiHopQuote.amountOut);
if (multiHopOutput > directOutput * 1001n / 1000n) {
console.log('Arbitrage opportunity found!');
console.log(`Direct: ${directOutput}`);
console.log(`Multi-hop: ${multiHopOutput}`);
console.log(`Route: ${multiHopQuote.route.path.join(' → ')}`);
}
}
```
## Risk Management
### Position Limits
Always set maximum position sizes:
```yaml
# In your strategy config
max_order_size: 1000 # Max single order
max_inventory: 10000 # Max total position
daily_volume_limit: 50000 # Max daily volume
```
### Stop Loss Configuration
```yaml
# Kill switch settings
kill_switch_enabled: true
kill_switch_rate: -5% # Stop if down 5%
```
### Inventory Management
```yaml
# Inventory skew settings
inventory_skew_enabled: true
inventory_target_base_pct: 50%
inventory_range_multiplier: 2.0
```
## Monitoring and Alerts
### Set Up Telegram Alerts
```yaml
# conf/telegram.yml
telegram:
enabled: true
token: "your-bot-token"
chat_id: "your-chat-id"
alerts:
- type: fill
threshold: 100 # Alert on fills > 100
- type: inventory
threshold: 80% # Alert when inventory > 80% on one side
- type: pnl
threshold: -2% # Alert on -2% PnL
```
### Performance Metrics
Track key metrics:
```bash
>>> pnl
>>> trades
>>> history --days 7
```
## Best Practices
### 1. Start Small
Begin with small amounts and increase gradually.
### 2. Test on Testnet
Always test new strategies on testnet first.
### 3. Monitor Regularly
Check your bot at least every few hours initially.
### 4. Diversify
Don't put all capital in one strategy or pair.
### 5. Keep Reserves
Maintain emergency funds outside the bot.
### 6. Document Everything
Keep a trading journal of your strategies and results.
## Getting Help
- **[Hummingbot Documentation](https://docs.hummingbot.org)**
- **[LX DEX Discord](https://discord.gg/lux)**
- **[Strategy Examples](https://github.com/hummingbot/hummingbot/tree/master/scripts)**
@@ -0,0 +1,461 @@
---
title: Hummingbot Integration
description: Official LX DEX connector for Hummingbot Gateway
---
# Hummingbot Integration
[Hummingbot](https://hummingbot.org) is the most popular open-source trading bot software, used by thousands of traders and market makers worldwide. LX DEX provides an official Gateway connector enabling automated trading on our ultra-low latency DEX.
## Features
Our Hummingbot Gateway connector supports all four LX DEX trading schemas:
| Schema | Description | Use Cases |
|--------|-------------|-----------|
| **Router** | DEX aggregation | Optimal swap execution, arbitrage |
| **AMM** | Liquidity pools | Market making, LP strategies |
| **CLMM** | Concentrated liquidity | Range orders, active LP management |
| **CLOB** | Order book | Limit orders, market making |
## Prerequisites
- Node.js 18 or higher
- Hummingbot Gateway v2.8.0 or higher
- A Lux wallet with LUX for gas fees
## Installation
### Option 1: Install via npm
```bash
cd hummingbot-gateway
npm install @lxdex/hummingbot-gateway-connector
```
### Option 2: Clone and Build
```bash
git clone https://github.com/luxfi/dex.git
cd dex/hummingbot-gateway/src/connectors/lxdex
npm install
npm run build
```
### Option 3: Use the Plugin
```bash
npm install hummingbot-gateway-lxdex-plugin
```
## Configuration
### 1. Add Configuration File
Create `conf/lxdex.yml` in your Gateway directory:
```yaml
# LX DEX Configuration
enabled: true
chain: lux
networks:
- mainnet
- testnet
# Trading parameters
trading:
slippagePct: 0.5
maxHops: 3
quoteExpirationMs: 30000
# Network endpoints
mainnet:
chainId: 93
apiEndpoint: https://api.dex.lux.network
wsEndpoint: wss://ws.dex.lux.network
grpcEndpoint: grpc.dex.lux.network:443
testnet:
chainId: 9393
apiEndpoint: https://api.testnet.dex.lux.network
wsEndpoint: wss://ws.testnet.dex.lux.network
grpcEndpoint: grpc.testnet.dex.lux.network:443
```
### 2. Register the Connector
In your Gateway startup:
```typescript
import { FastifyInstance } from 'fastify';
import { lxdexRoutes } from '@lxdex/hummingbot-gateway-connector';
async function registerConnectors(fastify: FastifyInstance) {
await fastify.register(lxdexRoutes);
}
```
### 3. Start Gateway
```bash
# Start with Docker
docker-compose up -d gateway
# Or start directly
npm run start
```
## API Endpoints
### Router Schema (Swaps)
#### GET /lxdex/router/quote-swap
Get an optimal swap quote with routing.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| baseToken | string | Yes | Input token symbol or address |
| quoteToken | string | Yes | Output token symbol or address |
| amount | string | Yes | Amount in smallest units |
| side | string | Yes | `BUY` or `SELL` |
| slippagePct | number | No | Slippage tolerance (default: 0.5) |
| maxHops | number | No | Maximum routing hops (default: 3) |
**Example:**
```bash
curl "http://localhost:15888/lxdex/router/quote-swap?\
baseToken=LUX&\
quoteToken=USDC&\
amount=1000000000000000000&\
side=SELL"
```
**Response:**
```json
{
"quoteId": "q-abc123def456",
"tokenIn": {
"symbol": "LUX",
"address": "0x...",
"decimals": 18
},
"tokenOut": {
"symbol": "USDC",
"address": "0x...",
"decimals": 6
},
"amountIn": "1000000000000000000",
"amountOut": "12500000",
"price": "12.50",
"priceImpactPct": "0.05",
"minAmountOut": "12437500",
"route": {
"path": ["LUX", "USDC"],
"pools": ["0x..."],
"expectedOutput": "12500000",
"priceImpact": "0.05",
"fee": "0.003"
},
"estimatedGas": "150000",
"expiresAt": 1702300000000
}
```
#### POST /lxdex/router/execute-swap
Execute a swap directly.
**Body:**
```json
{
"walletAddress": "0x...",
"baseToken": "LUX",
"quoteToken": "USDC",
"amount": "1000000000000000000",
"side": "SELL",
"slippagePct": 0.5
}
```
#### POST /lxdex/router/execute-quote
Execute a previously fetched quote.
**Body:**
```json
{
"walletAddress": "0x...",
"quoteId": "q-abc123def456"
}
```
### AMM Schema (Liquidity Pools)
#### GET /lxdex/amm/pool-info
Get pool information including reserves and pricing.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| tokenA | string | Yes | First token |
| tokenB | string | Yes | Second token |
**Response:**
```json
{
"pools": [{
"address": "0x...",
"tokenA": { "symbol": "LUX", "address": "0x...", "decimals": 18 },
"tokenB": { "symbol": "USDC", "address": "0x...", "decimals": 6 },
"reserveA": "1000000000000000000000",
"reserveB": "12500000000",
"totalLiquidity": "3535533905932738",
"fee": "0.003",
"apy": "24.5",
"volume24h": "1500000"
}]
}
```
#### POST /lxdex/amm/add-liquidity
Add liquidity to a pool.
**Body:**
```json
{
"walletAddress": "0x...",
"tokenA": "LUX",
"tokenB": "USDC",
"amountA": "1000000000000000000",
"amountB": "12500000",
"slippagePct": 0.5
}
```
#### POST /lxdex/amm/remove-liquidity
Remove liquidity from a pool.
**Body:**
```json
{
"walletAddress": "0x...",
"poolAddress": "0x...",
"liquidity": "1000000000000000000",
"slippagePct": 0.5
}
```
### CLMM Schema (Concentrated Liquidity)
#### GET /lxdex/clmm/pool-info
Get CLMM pool information.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| tokenA | string | Yes | First token |
| tokenB | string | Yes | Second token |
| fee | number | No | Fee tier in basis points |
#### POST /lxdex/clmm/open-position
Open a concentrated liquidity position.
**Body:**
```json
{
"walletAddress": "0x...",
"tokenA": "LUX",
"tokenB": "USDC",
"fee": 3000,
"tickLower": -887220,
"tickUpper": 887220,
"amountA": "1000000000000000000",
"amountB": "12500000",
"slippagePct": 0.5
}
```
#### POST /lxdex/clmm/close-position
Close a position and withdraw all liquidity.
**Body:**
```json
{
"walletAddress": "0x...",
"tokenId": "12345",
"slippagePct": 0.5
}
```
#### POST /lxdex/clmm/collect-fees
Collect accumulated fees from a position.
**Body:**
```json
{
"walletAddress": "0x...",
"tokenId": "12345"
}
```
### Order Book Schema
#### POST /lxdex/order
Place a new order.
**Body:**
```json
{
"walletAddress": "0x...",
"symbol": "LUX/USDC",
"side": "BUY",
"type": "LIMIT",
"price": "12.00",
"size": "100",
"timeInForce": "GTC"
}
```
#### DELETE /lxdex/order/:orderId
Cancel an order.
#### GET /lxdex/orders
Get orders for a wallet.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| walletAddress | string | Yes | Wallet address |
| symbol | string | No | Filter by symbol |
| status | string | No | Comma-separated statuses |
| limit | number | No | Maximum results |
## WebSocket Subscriptions
Connect to real-time data feeds:
```typescript
import { LXDex } from '@lxdex/hummingbot-gateway-connector';
const connector = LXDex.getInstance('mainnet');
await connector.connectWebSocket();
// Order book updates
connector.on('orderbook', (data) => {
console.log('Order book:', data);
});
await connector.subscribeOrderBook('LUX/USDC');
// Trade updates
connector.on('trade', (data) => {
console.log('Trade:', data);
});
await connector.subscribeTrades('LUX/USDC');
// Order status updates
connector.on('order', (data) => {
console.log('Order update:', data);
});
await connector.subscribeOrders('0xYourWallet');
```
## Error Handling
The connector provides detailed error responses:
| Error Code | Description |
|------------|-------------|
| `INSUFFICIENT_LIQUIDITY` | Not enough liquidity for trade |
| `SLIPPAGE_EXCEEDED` | Price moved beyond tolerance |
| `QUOTE_EXPIRED` | Quote has expired |
| `INVALID_TOKEN` | Token not found |
| `INSUFFICIENT_BALANCE` | Wallet balance too low |
| `ORDER_NOT_FOUND` | Order does not exist |
| `POSITION_NOT_FOUND` | Position does not exist |
```typescript
try {
const result = await connector.executeSwap(request);
} catch (error) {
if (error.code === 'SLIPPAGE_EXCEEDED') {
// Fetch new quote and retry
const newQuote = await connector.getQuote(request);
// ...
}
}
```
## Best Practices
### 1. Use Testnet First
Always test strategies on testnet before deploying to mainnet:
```typescript
const connector = LXDex.getInstance('testnet');
```
### 2. Handle Quote Expiration
Quotes expire after 30 seconds. Always check expiration:
```typescript
const quote = await connector.getQuote(request);
if (quote.expiresAt < Date.now()) {
// Fetch a new quote
}
```
### 3. Set Appropriate Slippage
For volatile pairs, increase slippage tolerance:
```typescript
const quote = await connector.getQuote({
...request,
slippagePct: 1.0 // 1% for volatile pairs
});
```
### 4. Monitor Position Health
For CLMM positions, check if in range:
```typescript
const positions = await connector.getPositionsOwned({ walletAddress });
for (const pos of positions.positions) {
if (!pos.inRange) {
console.warn(`Position ${pos.tokenId} is out of range`);
}
}
```
## Next Steps
- [Quick Start Guide](/docs/integrations/hummingbot-quickstart): Set up your first strategy
- [Strategy Examples](/docs/integrations/hummingbot-strategies): Market making and arbitrage strategies
- [API Reference](/docs/api): Full API documentation
+69
View File
@@ -0,0 +1,69 @@
---
title: Trading Bot Integrations
description: Connect LX DEX to popular trading bots and market making software
---
# Trading Bot Integrations
LX DEX provides official connectors for popular trading bots and market making software, enabling algorithmic trading, market making, and automated strategies on our ultra-low latency DEX.
## Available Integrations
### Hummingbot Gateway
The most popular open-source trading bot software. Our official Gateway connector supports:
- **Router Schema**: DEX aggregation with optimal swap routing
- **AMM Schema**: Liquidity pool operations for market making
- **CLMM Schema**: Concentrated liquidity positions (Uniswap V3-style)
- **Order Book**: Central limit order book trading
[Get Started with Hummingbot →](/docs/integrations/hummingbot)
## Why Use Trading Bots on LX DEX?
### Ultra-Low Latency
LX DEX's sub-100ns matching engine latency makes it ideal for:
- High-frequency trading strategies
- Arbitrage opportunities
- Fast market making
### Multiple Trading Venues
Our hybrid architecture supports multiple trading styles:
- AMM liquidity provision
- Concentrated liquidity market making
- Central limit order book trading
- Cross-venue arbitrage
### Real-Time Data
WebSocket subscriptions provide instant updates:
- Order book changes
- Trade executions
- Order status updates
- Position changes
## Integration Methods
| Method | Use Case | Latency |
|--------|----------|---------|
| REST API | Simple integrations | ~50ms |
| WebSocket | Real-time data | ~5ms |
| gRPC | High-frequency trading | ~1ms |
| FIX Protocol | Institutional trading | ~1ms |
## Getting Started
1. **Choose Your Bot**: Start with Hummingbot for a fully open-source solution
2. **Install the Connector**: Follow our installation guides
3. **Configure**: Set up API endpoints and trading parameters
4. **Test**: Use testnet before trading with real funds
5. **Deploy**: Run your strategies in production
## Support
- [Discord](https://discord.gg/lux): Join our trading community
- [GitHub Issues](https://github.com/luxfi/dex/issues): Report bugs or request features
- [Documentation](https://dex.lux.network/docs): Full API reference
+9
View File
@@ -0,0 +1,9 @@
{
"title": "Integrations",
"pages": [
"index",
"hummingbot",
"hummingbot-quickstart",
"hummingbot-strategies"
]
}
+2
View File
@@ -18,6 +18,8 @@
"bridge",
"...bridge",
"contracts",
"---Integrations---",
"integrations",
"---Infrastructure---",
"connectivity",
"data",
+100
View File
@@ -0,0 +1,100 @@
# LX DEX Hummingbot Gateway Plugin
Official Hummingbot Gateway plugin for [LX DEX](https://dex.lux.network).
## Installation
### Via npm
```bash
npm install hummingbot-gateway-lxdex-plugin
```
### Manual Installation
1. Clone the repository:
```bash
git clone https://github.com/luxfi/dex.git
cd dex/hummingbot-gateway/plugins/lxdex
```
2. Install dependencies and build:
```bash
npm install
npm run build
```
3. Link to your Gateway installation:
```bash
npm link
cd /path/to/hummingbot-gateway
npm link hummingbot-gateway-lxdex-plugin
```
## Configuration
Copy the configuration file to your Gateway config directory:
```bash
cp config/lxdex.yml /path/to/hummingbot-gateway/conf/
```
### Configuration Options
Edit `conf/lxdex.yml` to customize:
- **Network endpoints**: API, WebSocket, gRPC URLs
- **Trading parameters**: Slippage, max hops, quote expiration
- **Gas settings**: Gas limit, price source
- **Rate limits**: Requests per second
- **WebSocket**: Auto-reconnect, ping intervals
## Usage
### Register with Gateway
In your Gateway startup file:
```typescript
import { FastifyInstance } from 'fastify';
import { lxdexPlugin } from 'hummingbot-gateway-lxdex-plugin';
async function start(fastify: FastifyInstance) {
// Register LX DEX plugin
await fastify.register(lxdexPlugin);
await fastify.listen({ port: 15888 });
}
```
### Test Endpoints
```bash
# Health check
curl http://localhost:15888/lxdex/health
# Get swap quote
curl "http://localhost:15888/lxdex/router/quote-swap?baseToken=LUX&quoteToken=USDC&amount=1000000000000000000&side=SELL"
# Get trading pairs
curl http://localhost:15888/lxdex/pairs
```
## Supported Trading Operations
| Schema | Endpoint Prefix | Operations |
|--------|-----------------|------------|
| Router | `/lxdex/router` | Quote swap, Execute swap |
| AMM | `/lxdex/amm` | Pool info, Add/Remove liquidity |
| CLMM | `/lxdex/clmm` | Open/Close positions, Collect fees |
| CLOB | `/lxdex` | Place/Cancel orders, Order book |
## Links
- [Full Documentation](https://dex.lux.network/docs/integrations/hummingbot)
- [API Reference](https://dex.lux.network/docs/api)
- [Hummingbot Documentation](https://docs.hummingbot.org)
## License
Apache-2.0
@@ -0,0 +1,113 @@
# LX DEX Hummingbot Gateway Configuration
# https://dex.lux.network
# Enable/disable the connector
enabled: true
# Chain configuration
chain: lux
networks:
- mainnet
- testnet
# Trading types supported
tradingTypes:
- ROUTER # DEX aggregation swaps
- AMM # Liquidity pool operations
- CLMM # Concentrated liquidity
- CLOB # Central limit order book
# Network endpoints
mainnet:
chainId: 93
apiEndpoint: https://api.dex.lux.network
wsEndpoint: wss://ws.dex.lux.network
grpcEndpoint: grpc.dex.lux.network:443
explorerUrl: https://explorer.lux.network
testnet:
chainId: 9393
apiEndpoint: https://api.testnet.dex.lux.network
wsEndpoint: wss://ws.testnet.dex.lux.network
grpcEndpoint: grpc.testnet.dex.lux.network:443
explorerUrl: https://testnet.explorer.lux.network
# Default trading parameters
trading:
# Default slippage tolerance (percentage)
slippagePct: 0.5
# Maximum routing hops for swaps
maxHops: 3
# Quote expiration time (milliseconds)
quoteExpirationMs: 30000
# Gas settings
gas:
# Default gas limit for transactions
defaultGasLimit: "500000"
# Gas price source: "auto" or specific value in gwei
gasPriceSource: auto
# Maximum gas price willing to pay (gwei)
maxGasPrice: "100"
# Rate limiting
rateLimit:
# Requests per second
requestsPerSecond: 50
# Burst capacity
burstCapacity: 100
# WebSocket configuration
websocket:
# Enable WebSocket connections
enabled: true
# Reconnect on disconnect
autoReconnect: true
# Reconnection delay (milliseconds)
reconnectDelayMs: 1000
# Maximum reconnection attempts
maxReconnectAttempts: 10
# Ping interval (milliseconds)
pingIntervalMs: 30000
# Fee tiers for CLMM pools (basis points)
clmmFeeTiers:
- 100 # 0.01%
- 500 # 0.05%
- 3000 # 0.30%
- 10000 # 1.00%
# Order book configuration
orderbook:
# Default depth for order book snapshots
defaultDepth: 20
# Maximum depth
maxDepth: 500
# Aggregation levels
aggregationLevels:
- 0.01
- 0.1
- 1.0
- 10.0
# Logging
logging:
# Log level: debug, info, warn, error
level: info
# Log API requests
logRequests: false
# Log WebSocket messages
logWebSocket: false
+39
View File
@@ -0,0 +1,39 @@
/**
* Hummingbot Gateway Plugin for LX DEX
*
* This plugin registers the LX DEX connector with Hummingbot Gateway.
*/
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { lxdexRoutes } from '@lxdex/hummingbot-gateway-connector';
export interface LXDexPluginOptions extends FastifyPluginOptions {
/** Custom prefix for routes (default: none) */
prefix?: string;
}
/**
* Register the LX DEX plugin with Hummingbot Gateway
*/
export async function lxdexPlugin(
fastify: FastifyInstance,
options: LXDexPluginOptions
): Promise<void> {
// Register the LX DEX routes
await fastify.register(lxdexRoutes, {
prefix: options.prefix,
});
fastify.log.info('LX DEX plugin registered successfully');
}
export default lxdexPlugin;
// Re-export connector components for direct use
export {
LXDex,
LXDexConfig,
getLXDexConfig,
LX_DEX_NETWORKS,
lxdexRoutes,
} from '@lxdex/hummingbot-gateway-connector';
@@ -0,0 +1,38 @@
{
"name": "hummingbot-gateway-lxdex-plugin",
"version": "1.0.0",
"description": "Hummingbot Gateway plugin for LX DEX - Ultra-low latency DEX with CLOB, AMM, and CLMM support",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"config"
],
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"prepublishOnly": "npm run build"
},
"keywords": [
"hummingbot",
"gateway",
"plugin",
"lxdex",
"lux",
"dex",
"trading"
],
"author": "Lux Partners Limited",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/luxfi/dex.git",
"directory": "hummingbot-gateway/plugins/lxdex"
},
"dependencies": {
"@lxdex/hummingbot-gateway-connector": "^1.0.0"
},
"peerDependencies": {
"fastify": "^4.0.0"
}
}
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"moduleResolution": "node"
},
"include": ["./**/*.ts"],
"exclude": ["node_modules", "dist"]
}
@@ -0,0 +1,324 @@
# LX DEX Hummingbot Gateway Connector
Official Hummingbot Gateway connector for [LX DEX](https://dex.lux.network), the ultra-low latency decentralized exchange on Lux Network.
## Features
- **Ultra-Low Latency**: Sub-100ns matching engine latency
- **Multiple Trading Schemas**:
- **Router**: DEX aggregation with optimal swap routing
- **AMM**: Traditional xy=k constant product liquidity pools
- **CLMM**: Concentrated liquidity market maker (Uniswap V3-style)
- **CLOB**: Central limit order book with on-chain settlement
- **Real-Time Updates**: WebSocket subscriptions for order book, trades, and order status
- **Cross-Chain**: Native support for Lux C-Chain and bridged assets
## Installation
```bash
# Install dependencies
npm install @lxdex/hummingbot-gateway-connector
# Or add to your Gateway installation
cd hummingbot-gateway
npm install
```
## Quick Start
### Register the Connector
```typescript
import { FastifyInstance } from 'fastify';
import { lxdexRoutes } from '@lxdex/hummingbot-gateway-connector';
async function registerConnectors(fastify: FastifyInstance) {
await fastify.register(lxdexRoutes);
}
```
### Use the Connector Directly
```typescript
import { LXDex } from '@lxdex/hummingbot-gateway-connector';
// Get connector instance (singleton per network)
const connector = LXDex.getInstance('mainnet');
// Get a swap quote
const quote = await connector.getQuote({
baseToken: 'LUX',
quoteToken: 'USDC',
amount: '1000000000000000000', // 1 LUX in wei
side: 'SELL',
});
console.log(`Quote: ${quote.amountOut} USDC`);
console.log(`Price Impact: ${quote.priceImpactPct}%`);
```
## API Endpoints
### Router Schema (Swaps)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/lxdex/router/quote-swap` | Get optimal swap quote |
| POST | `/lxdex/router/execute-swap` | Execute swap directly |
| POST | `/lxdex/router/execute-quote` | Execute pre-fetched quote |
### AMM Schema (Liquidity Pools)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/lxdex/amm/pool-info` | Get pool reserves and pricing |
| GET | `/lxdex/amm/position-info` | Get LP position details |
| POST | `/lxdex/amm/quote-liquidity` | Calculate liquidity provision |
| POST | `/lxdex/amm/add-liquidity` | Add liquidity to pool |
| POST | `/lxdex/amm/remove-liquidity` | Remove liquidity from pool |
### CLMM Schema (Concentrated Liquidity)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/lxdex/clmm/pool-info` | Get CLMM pool information |
| GET | `/lxdex/clmm/positions-owned` | Get user's CLMM positions |
| POST | `/lxdex/clmm/quote-position` | Calculate position amounts |
| POST | `/lxdex/clmm/open-position` | Open new position |
| POST | `/lxdex/clmm/close-position` | Close existing position |
| POST | `/lxdex/clmm/collect-fees` | Collect accumulated fees |
### Order Book Schema
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/lxdex/health` | Health check |
| POST | `/lxdex/orderbook` | Get order book snapshot |
| POST | `/lxdex/order` | Place new order |
| DELETE | `/lxdex/order/:orderId` | Cancel order |
| GET | `/lxdex/orders` | Get user orders |
| GET | `/lxdex/pairs` | Get trading pairs |
| GET | `/lxdex/token/:token` | Get token info |
## Example Usage
### Get Swap Quote
```bash
curl "http://localhost:15888/lxdex/router/quote-swap?\
baseToken=LUX&\
quoteToken=USDC&\
amount=1000000000000000000&\
side=SELL"
```
Response:
```json
{
"quoteId": "q-abc123",
"tokenIn": { "symbol": "LUX", "address": "0x...", "decimals": 18 },
"tokenOut": { "symbol": "USDC", "address": "0x...", "decimals": 6 },
"amountIn": "1000000000000000000",
"amountOut": "12500000",
"price": "12.50",
"priceImpactPct": "0.05",
"minAmountOut": "12375000",
"route": {
"path": ["LUX", "USDC"],
"pools": ["0x..."],
"expectedOutput": "12500000",
"priceImpact": "0.05",
"fee": "0.003"
},
"estimatedGas": "150000",
"expiresAt": 1702300000000
}
```
### Execute Swap
```bash
curl -X POST http://localhost:15888/lxdex/router/execute-swap \
-H "Content-Type: application/json" \
-d '{
"walletAddress": "0x...",
"baseToken": "LUX",
"quoteToken": "USDC",
"amount": "1000000000000000000",
"side": "SELL",
"slippagePct": 0.5
}'
```
### Add Liquidity to AMM Pool
```bash
curl -X POST http://localhost:15888/lxdex/amm/add-liquidity \
-H "Content-Type: application/json" \
-d '{
"walletAddress": "0x...",
"tokenA": "LUX",
"tokenB": "USDC",
"amountA": "1000000000000000000",
"amountB": "12500000",
"slippagePct": 0.5
}'
```
### Open CLMM Position
```bash
curl -X POST http://localhost:15888/lxdex/clmm/open-position \
-H "Content-Type: application/json" \
-d '{
"walletAddress": "0x...",
"tokenA": "LUX",
"tokenB": "USDC",
"fee": 3000,
"tickLower": -887220,
"tickUpper": 887220,
"amountA": "1000000000000000000",
"amountB": "12500000"
}'
```
### Place Limit Order
```bash
curl -X POST http://localhost:15888/lxdex/order \
-H "Content-Type: application/json" \
-d '{
"walletAddress": "0x...",
"symbol": "LUX/USDC",
"side": "BUY",
"type": "LIMIT",
"price": "12.00",
"size": "100"
}'
```
## WebSocket Subscriptions
Connect to the WebSocket endpoint for real-time updates:
```typescript
import { LXDex } from '@lxdex/hummingbot-gateway-connector';
const connector = LXDex.getInstance('mainnet');
await connector.connectWebSocket();
// Subscribe to order book updates
connector.on('orderbook', (data) => {
console.log('Order book update:', data);
});
await connector.subscribeOrderBook('LUX/USDC');
// Subscribe to trades
connector.on('trade', (data) => {
console.log('Trade:', data);
});
await connector.subscribeTrades('LUX/USDC');
// Subscribe to order updates
connector.on('order', (data) => {
console.log('Order update:', data);
});
await connector.subscribeOrders('0xYourWalletAddress');
```
## Configuration
The connector can be configured via environment variables or configuration file:
```typescript
// Environment variables
LX_DEX_API_ENDPOINT=https://api.dex.lux.network
LX_DEX_WS_ENDPOINT=wss://ws.dex.lux.network
LX_DEX_GRPC_ENDPOINT=grpc.dex.lux.network:443
// Or programmatically
import { getLXDexConfig } from '@lxdex/hummingbot-gateway-connector';
const config = getLXDexConfig('mainnet', {
slippagePct: 0.5,
rateLimitPerSecond: 100,
});
```
### Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiEndpoint` | string | Auto | LX DEX API endpoint |
| `wsEndpoint` | string | Auto | WebSocket endpoint |
| `grpcEndpoint` | string | Auto | gRPC endpoint |
| `slippagePct` | number | 0.5 | Default slippage tolerance |
| `defaultGasPrice` | string | Auto | Default gas price in gwei |
| `defaultGasLimit` | string | "500000" | Default gas limit |
| `rateLimitPerSecond` | number | 50 | API rate limit |
| `maxHops` | number | 3 | Maximum routing hops |
## Networks
| Network | Chain ID | API Endpoint |
|---------|----------|--------------|
| mainnet | 93 | https://api.dex.lux.network |
| testnet | 9393 | https://api.testnet.dex.lux.network |
## Error Handling
The connector provides detailed error responses:
```typescript
try {
const result = await connector.executeSwap(request);
} catch (error) {
if (error.code === 'INSUFFICIENT_LIQUIDITY') {
console.log('Not enough liquidity for this trade');
} else if (error.code === 'SLIPPAGE_EXCEEDED') {
console.log('Price moved too much');
} else if (error.code === 'QUOTE_EXPIRED') {
console.log('Quote has expired, fetch a new one');
}
}
```
## Testing
```bash
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch
```
## Development
```bash
# Install dependencies
npm install
# Build
npm run build
# Lint
npm run lint
# Lint and fix
npm run lint:fix
```
## License
Apache-2.0
## Links
- [LX DEX Documentation](https://dex.lux.network)
- [Hummingbot Gateway](https://github.com/hummingbot/gateway)
- [Lux Network](https://lux.network)
- [GitHub Issues](https://github.com/luxfi/dex/issues)
@@ -0,0 +1,481 @@
/**
* LX DEX Connector Tests
*/
import { LXDex } from '../lxdex';
import { getLXDexConfig } from '../lxdex.config';
describe('LXDex Connector', () => {
let connector: LXDex;
beforeEach(() => {
// Clear singleton instances between tests
(LXDex as unknown as { _instances: Map<string, LXDex> })._instances?.clear();
connector = LXDex.getInstance('testnet');
});
afterEach(async () => {
await connector.close();
});
describe('Singleton Pattern', () => {
it('should return the same instance for the same network', () => {
const instance1 = LXDex.getInstance('mainnet');
const instance2 = LXDex.getInstance('mainnet');
expect(instance1).toBe(instance2);
});
it('should return different instances for different networks', () => {
const mainnet = LXDex.getInstance('mainnet');
const testnet = LXDex.getInstance('testnet');
expect(mainnet).not.toBe(testnet);
});
});
describe('Configuration', () => {
it('should load mainnet config by default', () => {
const config = getLXDexConfig('mainnet');
expect(config.chain).toBe('lux');
expect(config.apiEndpoint).toContain('api.dex.lux.network');
});
it('should load testnet config', () => {
const config = getLXDexConfig('testnet');
expect(config.chain).toBe('lux');
expect(config.apiEndpoint).toContain('testnet');
});
it('should have required trading types', () => {
const config = getLXDexConfig('mainnet');
expect(config.tradingTypes).toContain('ROUTER');
expect(config.tradingTypes).toContain('AMM');
expect(config.tradingTypes).toContain('CLMM');
expect(config.tradingTypes).toContain('CLOB');
});
});
describe('Network', () => {
it('should return correct network', () => {
expect(connector.getNetwork()).toBe('testnet');
});
it('should report ready status', async () => {
// Mock implementation would be needed for actual API tests
const ready = await connector.ready();
expect(typeof ready).toBe('boolean');
});
});
describe('Token Operations', () => {
it('should get token by symbol', async () => {
const token = await connector.getToken('LUX');
// In real implementation, this would return token info
expect(token).toBeDefined();
});
it('should return null for unknown token', async () => {
const token = await connector.getToken('UNKNOWN_TOKEN_XYZ');
expect(token).toBeNull();
});
});
describe('Trading Pairs', () => {
it('should get trading pairs', async () => {
const pairs = await connector.getTradingPairs();
expect(Array.isArray(pairs)).toBe(true);
});
});
});
describe('Router Schema', () => {
let connector: LXDex;
beforeEach(() => {
(LXDex as unknown as { _instances: Map<string, LXDex> })._instances?.clear();
connector = LXDex.getInstance('testnet');
});
afterEach(async () => {
await connector.close();
});
describe('Quote Swap', () => {
it('should get a swap quote', async () => {
const quote = await connector.getQuote({
baseToken: 'LUX',
quoteToken: 'USDC',
amount: '1000000000000000000', // 1 LUX
side: 'SELL',
});
expect(quote).toHaveProperty('quoteId');
expect(quote).toHaveProperty('amountIn');
expect(quote).toHaveProperty('amountOut');
expect(quote).toHaveProperty('price');
expect(quote).toHaveProperty('route');
});
it('should include price impact', async () => {
const quote = await connector.getQuote({
baseToken: 'LUX',
quoteToken: 'USDC',
amount: '1000000000000000000000', // 1000 LUX
side: 'SELL',
});
expect(quote).toHaveProperty('priceImpactPct');
});
it('should respect slippage setting', async () => {
const quote = await connector.getQuote({
baseToken: 'LUX',
quoteToken: 'USDC',
amount: '1000000000000000000',
side: 'SELL',
slippagePct: 1.0,
});
expect(quote).toHaveProperty('minAmountOut');
});
});
describe('Execute Swap', () => {
it('should execute a swap', async () => {
const result = await connector.executeSwap({
walletAddress: '0x1234567890123456789012345678901234567890',
baseToken: 'LUX',
quoteToken: 'USDC',
amount: '1000000000000000000',
side: 'SELL',
});
expect(result).toHaveProperty('txHash');
expect(result).toHaveProperty('status');
});
});
});
describe('AMM Schema', () => {
let connector: LXDex;
beforeEach(() => {
(LXDex as unknown as { _instances: Map<string, LXDex> })._instances?.clear();
connector = LXDex.getInstance('testnet');
});
afterEach(async () => {
await connector.close();
});
describe('Pool Info', () => {
it('should get pool information', async () => {
const poolInfo = await connector.getPoolInfo({
tokenA: 'LUX',
tokenB: 'USDC',
});
expect(poolInfo).toHaveProperty('pools');
expect(Array.isArray(poolInfo.pools)).toBe(true);
});
it('should include reserves and liquidity', async () => {
const poolInfo = await connector.getPoolInfo({
tokenA: 'LUX',
tokenB: 'USDC',
});
if (poolInfo.pools.length > 0) {
const pool = poolInfo.pools[0];
expect(pool).toHaveProperty('reserveA');
expect(pool).toHaveProperty('reserveB');
expect(pool).toHaveProperty('totalLiquidity');
}
});
});
describe('Position Info', () => {
it('should get LP positions', async () => {
const positions = await connector.getPositionInfo({
walletAddress: '0x1234567890123456789012345678901234567890',
});
expect(positions).toHaveProperty('positions');
expect(positions).toHaveProperty('totalValueUSD');
});
});
describe('Add Liquidity', () => {
it('should add liquidity to pool', async () => {
const result = await connector.addLiquidity({
walletAddress: '0x1234567890123456789012345678901234567890',
tokenA: 'LUX',
tokenB: 'USDC',
amountA: '1000000000000000000',
amountB: '1000000',
});
expect(result).toHaveProperty('txHash');
expect(result).toHaveProperty('liquidityMinted');
});
});
describe('Remove Liquidity', () => {
it('should remove liquidity from pool', async () => {
const result = await connector.removeLiquidity({
walletAddress: '0x1234567890123456789012345678901234567890',
poolAddress: '0x0987654321098765432109876543210987654321',
liquidity: '1000000000000000000',
});
expect(result).toHaveProperty('txHash');
expect(result).toHaveProperty('amountA');
expect(result).toHaveProperty('amountB');
});
});
});
describe('CLMM Schema', () => {
let connector: LXDex;
beforeEach(() => {
(LXDex as unknown as { _instances: Map<string, LXDex> })._instances?.clear();
connector = LXDex.getInstance('testnet');
});
afterEach(async () => {
await connector.close();
});
describe('CLMM Pool Info', () => {
it('should get CLMM pool information', async () => {
const poolInfo = await connector.getCLMMPoolInfo({
tokenA: 'LUX',
tokenB: 'USDC',
});
expect(poolInfo).toHaveProperty('pools');
});
it('should include tick and price data', async () => {
const poolInfo = await connector.getCLMMPoolInfo({
tokenA: 'LUX',
tokenB: 'USDC',
fee: 3000, // 0.3%
});
if (poolInfo.pools.length > 0) {
const pool = poolInfo.pools[0];
expect(pool).toHaveProperty('currentTick');
expect(pool).toHaveProperty('currentPrice');
expect(pool).toHaveProperty('tickSpacing');
}
});
});
describe('Positions Owned', () => {
it('should get CLMM positions', async () => {
const positions = await connector.getPositionsOwned({
walletAddress: '0x1234567890123456789012345678901234567890',
});
expect(positions).toHaveProperty('positions');
expect(positions).toHaveProperty('totalValueUSD');
});
});
describe('Quote Position', () => {
it('should quote a position', async () => {
const quote = await connector.quotePosition({
tokenA: 'LUX',
tokenB: 'USDC',
fee: 3000,
tickLower: -887220,
tickUpper: 887220,
amountA: '1000000000000000000',
});
expect(quote).toHaveProperty('estimatedAmountA');
expect(quote).toHaveProperty('estimatedAmountB');
expect(quote).toHaveProperty('estimatedLiquidity');
expect(quote).toHaveProperty('priceRange');
expect(quote).toHaveProperty('inRange');
});
});
describe('Open Position', () => {
it('should open a CLMM position', async () => {
const result = await connector.openPosition({
walletAddress: '0x1234567890123456789012345678901234567890',
tokenA: 'LUX',
tokenB: 'USDC',
fee: 3000,
tickLower: -887220,
tickUpper: 887220,
amountA: '1000000000000000000',
amountB: '1000000',
});
expect(result).toHaveProperty('txHash');
expect(result).toHaveProperty('tokenId');
expect(result).toHaveProperty('liquidity');
});
});
describe('Close Position', () => {
it('should close a CLMM position', async () => {
const result = await connector.closePosition({
walletAddress: '0x1234567890123456789012345678901234567890',
tokenId: '12345',
});
expect(result).toHaveProperty('txHash');
expect(result).toHaveProperty('amountA');
expect(result).toHaveProperty('amountB');
expect(result).toHaveProperty('feesCollectedA');
expect(result).toHaveProperty('feesCollectedB');
});
});
describe('Collect Fees', () => {
it('should collect fees from a position', async () => {
const result = await connector.collectFees({
walletAddress: '0x1234567890123456789012345678901234567890',
tokenId: '12345',
});
expect(result).toHaveProperty('txHash');
expect(result).toHaveProperty('feesCollectedA');
expect(result).toHaveProperty('feesCollectedB');
});
});
});
describe('Order Book Schema', () => {
let connector: LXDex;
beforeEach(() => {
(LXDex as unknown as { _instances: Map<string, LXDex> })._instances?.clear();
connector = LXDex.getInstance('testnet');
});
afterEach(async () => {
await connector.close();
});
describe('Order Book', () => {
it('should get order book', async () => {
const orderBook = await connector.getOrderBook({
symbol: 'LUX/USDC',
depth: 20,
});
expect(orderBook).toHaveProperty('symbol');
expect(orderBook).toHaveProperty('bids');
expect(orderBook).toHaveProperty('asks');
expect(orderBook).toHaveProperty('timestamp');
});
});
describe('Place Order', () => {
it('should place a limit order', async () => {
const result = await connector.placeOrder({
walletAddress: '0x1234567890123456789012345678901234567890',
symbol: 'LUX/USDC',
side: 'BUY',
type: 'LIMIT',
price: '10.5',
size: '100',
});
expect(result).toHaveProperty('order');
expect(result.order).toHaveProperty('orderId');
expect(result.order).toHaveProperty('status');
});
it('should place a market order', async () => {
const result = await connector.placeOrder({
walletAddress: '0x1234567890123456789012345678901234567890',
symbol: 'LUX/USDC',
side: 'BUY',
type: 'MARKET',
size: '100',
});
expect(result).toHaveProperty('order');
});
});
describe('Cancel Order', () => {
it('should cancel an order', async () => {
const result = await connector.cancelOrder({
walletAddress: '0x1234567890123456789012345678901234567890',
orderId: 'order-123',
});
expect(result).toHaveProperty('orderId');
expect(result).toHaveProperty('status');
});
});
describe('Get Orders', () => {
it('should get orders for wallet', async () => {
const result = await connector.getOrders({
walletAddress: '0x1234567890123456789012345678901234567890',
});
expect(result).toHaveProperty('orders');
expect(result).toHaveProperty('total');
});
it('should filter by status', async () => {
const result = await connector.getOrders({
walletAddress: '0x1234567890123456789012345678901234567890',
status: ['open', 'partial'],
});
expect(result).toHaveProperty('orders');
});
});
});
describe('WebSocket', () => {
let connector: LXDex;
beforeEach(() => {
(LXDex as unknown as { _instances: Map<string, LXDex> })._instances?.clear();
connector = LXDex.getInstance('testnet');
});
afterEach(async () => {
await connector.close();
});
it('should connect to WebSocket', async () => {
await expect(connector.connectWebSocket()).resolves.not.toThrow();
});
it('should subscribe to order book updates', async () => {
await connector.connectWebSocket();
const updates: unknown[] = [];
connector.on('orderbook', (data) => {
updates.push(data);
});
await connector.subscribeOrderBook('LUX/USDC');
// In real test, would wait for updates
expect(connector.listenerCount('orderbook')).toBeGreaterThan(0);
});
it('should subscribe to trade updates', async () => {
await connector.connectWebSocket();
connector.on('trade', (data) => {
expect(data).toHaveProperty('price');
expect(data).toHaveProperty('size');
});
await connector.subscribeTrades('LUX/USDC');
});
});
@@ -0,0 +1,232 @@
/**
* LX DEX AMM Schema Routes
*
* Implements the AMM schema endpoints for liquidity pool operations.
* - GET /pool-info: Get pool reserves and pricing
* - GET /position-info: Get current liquidity position details
* - POST /quote-liquidity: Calculate liquidity provision amounts
* - POST /add-liquidity: Add liquidity to pool
* - POST /remove-liquidity: Remove liquidity from pool
*/
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { LXDex } from '../lxdex';
import {
LXDexPoolInfoRequest,
LXDexPositionInfoRequest,
LXDexAddLiquidityRequest,
LXDexRemoveLiquidityRequest,
} from '../schemas';
export async function routes(
fastify: FastifyInstance,
_options: FastifyPluginOptions
): Promise<void> {
/**
* GET /pool-info
*
* Get information about a liquidity pool.
* Returns reserves, liquidity, fees, and pricing.
*/
fastify.get<{
Querystring: LXDexPoolInfoRequest;
}>(
'/pool-info',
{
schema: {
querystring: {
type: 'object',
required: ['tokenA', 'tokenB'],
properties: {
network: { type: 'string' },
tokenA: { type: 'string' },
tokenB: { type: 'string' },
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.query.network || 'mainnet');
const result = await connector.getPoolInfo(request.query);
return reply.send(result);
}
);
/**
* GET /position-info
*
* Get current LP position details for a wallet.
*/
fastify.get<{
Querystring: LXDexPositionInfoRequest;
}>(
'/position-info',
{
schema: {
querystring: {
type: 'object',
required: ['walletAddress'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
poolAddress: { type: 'string' },
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.query.network || 'mainnet');
const result = await connector.getPositionInfo(request.query);
return reply.send(result);
}
);
/**
* POST /quote-liquidity
*
* Calculate optimal amounts for adding liquidity.
* Returns estimated LP tokens and pool share.
*/
fastify.post<{
Body: {
network?: string;
tokenA: string;
tokenB: string;
amountA?: string;
amountB?: string;
};
}>(
'/quote-liquidity',
{
schema: {
body: {
type: 'object',
required: ['tokenA', 'tokenB'],
properties: {
network: { type: 'string' },
tokenA: { type: 'string' },
tokenB: { type: 'string' },
amountA: { type: 'string' },
amountB: { type: 'string' },
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
// Get pool info to calculate quote
const poolInfo = await connector.getPoolInfo({
tokenA: request.body.tokenA,
tokenB: request.body.tokenB,
});
if (!poolInfo.pools.length) {
return reply.status(404).send({ error: 'Pool not found' });
}
const pool = poolInfo.pools[0];
const reserveA = BigInt(pool.reserveA);
const reserveB = BigInt(pool.reserveB);
const totalLiquidity = BigInt(pool.totalLiquidity);
let amountA: bigint;
let amountB: bigint;
let liquidityEstimate: bigint;
if (request.body.amountA) {
amountA = BigInt(request.body.amountA);
amountB = (amountA * reserveB) / reserveA;
liquidityEstimate = (amountA * totalLiquidity) / reserveA;
} else if (request.body.amountB) {
amountB = BigInt(request.body.amountB);
amountA = (amountB * reserveA) / reserveB;
liquidityEstimate = (amountB * totalLiquidity) / reserveB;
} else {
return reply.status(400).send({
error: 'Either amountA or amountB must be provided',
});
}
const sharePercent =
Number((liquidityEstimate * 10000n) / (totalLiquidity + liquidityEstimate)) / 100;
return reply.send({
amountA: amountA.toString(),
amountB: amountB.toString(),
estimatedLiquidity: liquidityEstimate.toString(),
sharePercent: sharePercent.toFixed(4),
pool: {
address: pool.address,
fee: pool.fee,
},
});
}
);
/**
* POST /add-liquidity
*
* Add liquidity to a pool.
*/
fastify.post<{
Body: LXDexAddLiquidityRequest;
}>(
'/add-liquidity',
{
schema: {
body: {
type: 'object',
required: ['walletAddress', 'tokenA', 'tokenB', 'amountA', 'amountB'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
tokenA: { type: 'string' },
tokenB: { type: 'string' },
amountA: { type: 'string' },
amountB: { type: 'string' },
slippagePct: { type: 'number' },
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.addLiquidity(request.body);
return reply.send(result);
}
);
/**
* POST /remove-liquidity
*
* Remove liquidity from a pool.
*/
fastify.post<{
Body: LXDexRemoveLiquidityRequest;
}>(
'/remove-liquidity',
{
schema: {
body: {
type: 'object',
required: ['walletAddress', 'poolAddress', 'liquidity'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
poolAddress: { type: 'string' },
liquidity: { type: 'string' },
slippagePct: { type: 'number' },
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.removeLiquidity(request.body);
return reply.send(result);
}
);
}
export default routes;
@@ -0,0 +1,337 @@
/**
* LX DEX CLMM Schema Routes
*
* Implements the CLMM (Concentrated Liquidity Market Maker) schema endpoints.
* - GET /pool-info: Get concentrated liquidity pool information
* - GET /positions-owned: Get user's concentrated liquidity positions
* - POST /quote-position: Calculate position amounts for a price range
* - POST /open-position: Open a new concentrated liquidity position
* - POST /close-position: Close an existing position
* - POST /collect-fees: Collect accumulated fees from a position
*/
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { LXDex } from '../lxdex';
import {
LXDexCLMMPoolInfoRequest,
LXDexPositionsOwnedRequest,
LXDexQuotePositionRequest,
LXDexOpenPositionRequest,
LXDexClosePositionRequest,
LXDexCollectFeesRequest,
} from '../schemas';
export async function routes(
fastify: FastifyInstance,
_options: FastifyPluginOptions
): Promise<void> {
/**
* GET /pool-info
*
* Get information about a concentrated liquidity pool.
* Returns current tick, price, liquidity, and fee tier.
*/
fastify.get<{
Querystring: LXDexCLMMPoolInfoRequest;
}>(
'/pool-info',
{
schema: {
querystring: {
type: 'object',
required: ['tokenA', 'tokenB'],
properties: {
network: { type: 'string' },
tokenA: { type: 'string' },
tokenB: { type: 'string' },
fee: { type: 'number' },
},
},
response: {
200: {
type: 'object',
properties: {
pools: {
type: 'array',
items: {
type: 'object',
properties: {
address: { type: 'string' },
tokenA: { type: 'object' },
tokenB: { type: 'object' },
fee: { type: 'number' },
tickSpacing: { type: 'number' },
currentTick: { type: 'number' },
currentPrice: { type: 'string' },
liquidity: { type: 'string' },
volume24h: { type: 'string' },
tvlUSD: { type: 'string' },
},
},
},
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.query.network || 'mainnet');
const result = await connector.getCLMMPoolInfo(request.query);
return reply.send(result);
}
);
/**
* GET /positions-owned
*
* Get all concentrated liquidity positions owned by a wallet.
*/
fastify.get<{
Querystring: LXDexPositionsOwnedRequest;
}>(
'/positions-owned',
{
schema: {
querystring: {
type: 'object',
required: ['walletAddress'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
},
},
response: {
200: {
type: 'object',
properties: {
positions: {
type: 'array',
items: {
type: 'object',
properties: {
tokenId: { type: 'string' },
poolAddress: { type: 'string' },
tokenA: { type: 'object' },
tokenB: { type: 'object' },
tickLower: { type: 'number' },
tickUpper: { type: 'number' },
liquidity: { type: 'string' },
amountA: { type: 'string' },
amountB: { type: 'string' },
unclaimedFeesA: { type: 'string' },
unclaimedFeesB: { type: 'string' },
inRange: { type: 'boolean' },
valueUSD: { type: 'string' },
},
},
},
totalValueUSD: { type: 'string' },
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.query.network || 'mainnet');
const result = await connector.getPositionsOwned(request.query);
return reply.send(result);
}
);
/**
* POST /quote-position
*
* Calculate the amounts needed for a position in a given price range.
*/
fastify.post<{
Body: LXDexQuotePositionRequest;
}>(
'/quote-position',
{
schema: {
body: {
type: 'object',
required: ['tokenA', 'tokenB', 'fee', 'tickLower', 'tickUpper'],
properties: {
network: { type: 'string' },
tokenA: { type: 'string' },
tokenB: { type: 'string' },
fee: { type: 'number' },
tickLower: { type: 'number' },
tickUpper: { type: 'number' },
amountA: { type: 'string' },
amountB: { type: 'string' },
},
},
response: {
200: {
type: 'object',
properties: {
estimatedAmountA: { type: 'string' },
estimatedAmountB: { type: 'string' },
estimatedLiquidity: { type: 'string' },
priceRange: {
type: 'object',
properties: {
lower: { type: 'string' },
upper: { type: 'string' },
current: { type: 'string' },
},
},
inRange: { type: 'boolean' },
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.quotePosition(request.body);
return reply.send(result);
}
);
/**
* POST /open-position
*
* Open a new concentrated liquidity position.
*/
fastify.post<{
Body: LXDexOpenPositionRequest;
}>(
'/open-position',
{
schema: {
body: {
type: 'object',
required: [
'walletAddress',
'tokenA',
'tokenB',
'fee',
'tickLower',
'tickUpper',
'amountA',
'amountB',
],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
tokenA: { type: 'string' },
tokenB: { type: 'string' },
fee: { type: 'number' },
tickLower: { type: 'number' },
tickUpper: { type: 'number' },
amountA: { type: 'string' },
amountB: { type: 'string' },
slippagePct: { type: 'number' },
},
},
response: {
200: {
type: 'object',
properties: {
txHash: { type: 'string' },
status: { type: 'string' },
tokenId: { type: 'string' },
liquidity: { type: 'string' },
amountA: { type: 'string' },
amountB: { type: 'string' },
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.openPosition(request.body);
return reply.send(result);
}
);
/**
* POST /close-position
*
* Close an existing concentrated liquidity position.
* This withdraws all liquidity and collects fees.
*/
fastify.post<{
Body: LXDexClosePositionRequest;
}>(
'/close-position',
{
schema: {
body: {
type: 'object',
required: ['walletAddress', 'tokenId'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
tokenId: { type: 'string' },
slippagePct: { type: 'number' },
},
},
response: {
200: {
type: 'object',
properties: {
txHash: { type: 'string' },
status: { type: 'string' },
amountA: { type: 'string' },
amountB: { type: 'string' },
feesCollectedA: { type: 'string' },
feesCollectedB: { type: 'string' },
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.closePosition(request.body);
return reply.send(result);
}
);
/**
* POST /collect-fees
*
* Collect accumulated trading fees from a position.
*/
fastify.post<{
Body: LXDexCollectFeesRequest;
}>(
'/collect-fees',
{
schema: {
body: {
type: 'object',
required: ['walletAddress', 'tokenId'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
tokenId: { type: 'string' },
},
},
response: {
200: {
type: 'object',
properties: {
txHash: { type: 'string' },
status: { type: 'string' },
feesCollectedA: { type: 'string' },
feesCollectedB: { type: 'string' },
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.collectFees(request.body);
return reply.send(result);
}
);
}
export default routes;
@@ -0,0 +1,28 @@
/**
* LX DEX Hummingbot Gateway Connector
*
* Ultra-low latency decentralized exchange connector supporting:
* - Router Schema: DEX aggregation and optimal swap routing
* - AMM Schema: Traditional xy=k liquidity pool operations
* - CLMM Schema: Concentrated liquidity market maker positions
* - Order Book: Central limit order book trading
*
* @packageDocumentation
*/
// Main connector class
export { LXDex } from './lxdex';
// Configuration
export { LXDexConfig, getLXDexConfig, LX_DEX_NETWORKS } from './lxdex.config';
// Route registration
export { lxdexRoutes } from './lxdex.routes';
// All schemas and types
export * from './schemas';
// Sub-route modules
export * as routerRoutes from './router-routes';
export * as ammRoutes from './amm-routes';
export * as clmmRoutes from './clmm-routes';
@@ -0,0 +1,30 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>'],
testMatch: ['**/__tests__/**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
collectCoverageFrom: [
'**/*.ts',
'!**/*.d.ts',
'!**/node_modules/**',
'!**/dist/**',
'!**/__tests__/**',
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 70,
functions: 80,
lines: 80,
statements: 80,
},
},
verbose: true,
testTimeout: 30000,
};
@@ -0,0 +1,126 @@
/**
* LX DEX Gateway Connector Configuration
*
* Configuration interface and loader for the LX DEX connector.
* Supports Router, AMM, CLMM, and CLOB trading types.
*/
export interface AvailableNetworks {
chain: string;
networks: string[];
}
export interface LXDexConfig {
// Network configuration
chain: string;
networks: string[];
availableNetworks: AvailableNetworks[];
// Trading parameters
tradingTypes: string[];
slippagePct: number;
maxHops: number;
// API configuration
apiEndpoint: string;
wsEndpoint: string;
grpcEndpoint: string;
// Rate limits
requestsPerSecond: number;
// Fees
defaultFeeRate: number;
// Timeouts (ms)
orderTimeout: number;
connectionTimeout: number;
}
/**
* Default configuration values
*/
export const LXDexConfigDefaults: LXDexConfig = {
chain: 'lux',
networks: ['mainnet', 'testnet'],
availableNetworks: [
{ chain: 'lux', networks: ['mainnet', 'testnet'] },
],
tradingTypes: ['ROUTER', 'AMM', 'CLMM', 'CLOB'],
slippagePct: 0.5,
maxHops: 4,
apiEndpoint: 'https://api.dex.lux.network',
wsEndpoint: 'wss://ws.dex.lux.network',
grpcEndpoint: 'grpc.dex.lux.network:443',
requestsPerSecond: 50,
defaultFeeRate: 0.003, // 0.3% (30 bps)
orderTimeout: 30000,
connectionTimeout: 10000,
};
/**
* Network-specific configurations
*/
export const LX_DEX_NETWORKS: Record<string, Partial<LXDexConfig>> = {
mainnet: {
apiEndpoint: 'https://api.dex.lux.network',
wsEndpoint: 'wss://ws.dex.lux.network',
grpcEndpoint: 'grpc.dex.lux.network:443',
},
testnet: {
apiEndpoint: 'https://api.testnet.dex.lux.network',
wsEndpoint: 'wss://ws.testnet.dex.lux.network',
grpcEndpoint: 'grpc.testnet.dex.lux.network:443',
},
};
/**
* Get configuration for a specific network
*/
export function getLXDexConfig(
network: string = 'mainnet',
overrides?: Partial<LXDexConfig>
): LXDexConfig {
const networkConfig = LX_DEX_NETWORKS[network] || LX_DEX_NETWORKS.mainnet;
return {
...LXDexConfigDefaults,
...networkConfig,
...overrides,
};
}
/**
* Get network endpoints for a specific network
*/
export function getNetworkEndpoints(network: string): {
api: string;
ws: string;
grpc: string;
} {
const config = getLXDexConfig(network);
return {
api: config.apiEndpoint,
ws: config.wsEndpoint,
grpc: config.grpcEndpoint,
};
}
/**
* Validate configuration
*/
export function validateConfig(config: LXDexConfig): boolean {
if (!config.apiEndpoint) {
throw new Error('LXDex config: apiEndpoint is required');
}
if (!config.wsEndpoint) {
throw new Error('LXDex config: wsEndpoint is required');
}
if (config.slippagePct < 0 || config.slippagePct > 100) {
throw new Error('LXDex config: slippagePct must be between 0 and 100');
}
if (config.maxHops < 1 || config.maxHops > 10) {
throw new Error('LXDex config: maxHops must be between 1 and 10');
}
return true;
}
@@ -0,0 +1,127 @@
/**
* LX DEX Gateway Connector Routes
*
* Fastify route registration for all LX DEX endpoints.
* Implements Router, AMM, CLMM, and Order Book schemas.
*/
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { LXDex } from './lxdex';
import * as routerRoutes from './router-routes';
import * as ammRoutes from './amm-routes';
import * as clmmRoutes from './clmm-routes';
export async function lxdexRoutes(
fastify: FastifyInstance,
_options: FastifyPluginOptions
): Promise<void> {
// Health check
fastify.get('/lxdex/health', async (_request, reply) => {
const connector = LXDex.getInstance('mainnet');
const ready = await connector.ready();
return reply.send({
status: ready ? 'healthy' : 'unhealthy',
network: connector.getNetwork(),
timestamp: Date.now(),
});
});
// Router Schema Routes (Swaps)
fastify.register(routerRoutes.routes, { prefix: '/lxdex/router' });
// AMM Schema Routes (Liquidity Pools)
fastify.register(ammRoutes.routes, { prefix: '/lxdex/amm' });
// CLMM Schema Routes (Concentrated Liquidity)
fastify.register(clmmRoutes.routes, { prefix: '/lxdex/clmm' });
// Order Book Routes
fastify.post('/lxdex/orderbook', async (request, reply) => {
const { network, symbol, depth } = request.body as {
network?: string;
symbol: string;
depth?: number;
};
const connector = LXDex.getInstance(network || 'mainnet');
const result = await connector.getOrderBook({ symbol, depth });
return reply.send(result);
});
// Place Order
fastify.post('/lxdex/order', async (request, reply) => {
const body = request.body as {
network?: string;
walletAddress: string;
symbol: string;
side: 'BUY' | 'SELL';
type: 'LIMIT' | 'MARKET' | 'STOP' | 'STOP_LIMIT';
price?: string;
size: string;
timeInForce?: 'GTC' | 'IOC' | 'FOK' | 'GTT';
clientOrderId?: string;
};
const connector = LXDex.getInstance(body.network || 'mainnet');
const result = await connector.placeOrder(body);
return reply.send(result);
});
// Cancel Order
fastify.delete('/lxdex/order/:orderId', async (request, reply) => {
const { orderId } = request.params as { orderId: string };
const { network, walletAddress } = request.query as {
network?: string;
walletAddress: string;
};
const connector = LXDex.getInstance(network || 'mainnet');
const result = await connector.cancelOrder({ walletAddress, orderId });
return reply.send(result);
});
// Get Orders
fastify.get('/lxdex/orders', async (request, reply) => {
const query = request.query as {
network?: string;
walletAddress: string;
symbol?: string;
status?: string;
limit?: string;
};
const connector = LXDex.getInstance(query.network || 'mainnet');
const result = await connector.getOrders({
walletAddress: query.walletAddress,
symbol: query.symbol,
status: query.status?.split(','),
limit: query.limit ? parseInt(query.limit) : undefined,
});
return reply.send(result);
});
// Trading Pairs
fastify.get('/lxdex/pairs', async (request, reply) => {
const { network } = request.query as { network?: string };
const connector = LXDex.getInstance(network || 'mainnet');
const pairs = await connector.getTradingPairs();
return reply.send({ pairs });
});
// Token Info
fastify.get('/lxdex/token/:token', async (request, reply) => {
const { token } = request.params as { token: string };
const { network } = request.query as { network?: string };
const connector = LXDex.getInstance(network || 'mainnet');
const tokenInfo = await connector.getToken(token);
if (!tokenInfo) {
return reply.status(404).send({ error: 'Token not found' });
}
return reply.send(tokenInfo);
});
}
export default lxdexRoutes;
@@ -0,0 +1,793 @@
/**
* LX DEX Gateway Connector
*
* Main connector class implementing Router, AMM, and CLMM schemas
* for the LX DEX decentralized exchange.
*
* Features:
* - Ultra-low latency order matching (<100ns)
* - Multiple orderbook backends (Pure Go, C++, GPU, FPGA)
* - Central Limit Order Book (CLOB) with AMM integration
* - Concentrated liquidity market making (CLMM)
* - Cross-chain bridge support via Warp messaging
*/
import axios, { AxiosInstance, AxiosError } from 'axios';
import WebSocket from 'ws';
import { EventEmitter } from 'events';
import {
LXDexConfig,
getLXDexConfig,
getNetworkEndpoints,
} from './lxdex.config';
import {
TokenInfo,
TradingPair,
LXDexQuoteSwapRequest,
LXDexQuoteSwapResponse,
LXDexExecuteSwapRequest,
LXDexExecuteQuoteRequest,
LXDexSwapResponse,
LXDexPoolInfoRequest,
LXDexPoolInfoResponse,
LXDexPositionInfoRequest,
LXDexPositionInfoResponse,
LXDexAddLiquidityRequest,
LXDexAddLiquidityResponse,
LXDexRemoveLiquidityRequest,
LXDexRemoveLiquidityResponse,
LXDexCLMMPoolInfoRequest,
LXDexCLMMPoolInfoResponse,
LXDexPositionsOwnedRequest,
LXDexPositionsOwnedResponse,
LXDexQuotePositionRequest,
LXDexQuotePositionResponse,
LXDexOpenPositionRequest,
LXDexOpenPositionResponse,
LXDexClosePositionRequest,
LXDexClosePositionResponse,
LXDexCollectFeesRequest,
LXDexCollectFeesResponse,
LXDexPlaceOrderRequest,
LXDexPlaceOrderResponse,
LXDexCancelOrderRequest,
LXDexCancelOrderResponse,
LXDexGetOrdersRequest,
LXDexGetOrdersResponse,
LXDexOrderBookRequest,
LXDexOrderBookResponse,
} from './schemas';
// Error types
export class LXDexError extends Error {
constructor(
message: string,
public code: string,
public details?: unknown
) {
super(message);
this.name = 'LXDexError';
}
}
export class InsufficientLiquidityError extends LXDexError {
constructor(message: string = 'Insufficient liquidity for this trade') {
super(message, 'INSUFFICIENT_LIQUIDITY');
}
}
export class SlippageExceededError extends LXDexError {
constructor(message: string = 'Price moved beyond slippage tolerance') {
super(message, 'SLIPPAGE_EXCEEDED');
}
}
export class OrderNotFoundError extends LXDexError {
constructor(orderId: string) {
super(`Order not found: ${orderId}`, 'ORDER_NOT_FOUND');
}
}
/**
* LX DEX Connector
*
* Singleton class managing connections to LX DEX.
* Supports Router (swaps), AMM (liquidity), CLMM (concentrated liquidity),
* and Order Book (limit orders) trading types.
*/
export class LXDex extends EventEmitter {
private static _instances: Map<string, LXDex> = new Map();
private readonly config: LXDexConfig;
private readonly network: string;
private readonly httpClient: AxiosInstance;
private wsClient: WebSocket | null = null;
private wsConnected: boolean = false;
private tokenCache: Map<string, TokenInfo> = new Map();
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
private constructor(network: string, config: LXDexConfig) {
super();
this.network = network;
this.config = config;
const endpoints = getNetworkEndpoints(network);
this.httpClient = axios.create({
baseURL: endpoints.api,
timeout: config.connectionTimeout,
headers: {
'Content-Type': 'application/json',
'X-Client': 'hummingbot-gateway',
'X-Client-Version': '1.0.0',
},
});
// Add request interceptor for logging
this.httpClient.interceptors.request.use((request) => {
console.log(`[LXDex] ${request.method?.toUpperCase()} ${request.url}`);
return request;
});
// Add response interceptor for error handling
this.httpClient.interceptors.response.use(
(response) => response,
(error: AxiosError) => this.handleError(error)
);
}
/**
* Get or create a LXDex instance for the specified network
*/
public static getInstance(
network: string = 'mainnet',
configOverrides?: Partial<LXDexConfig>
): LXDex {
const key = network;
if (!LXDex._instances.has(key)) {
const config = getLXDexConfig(network, configOverrides);
LXDex._instances.set(key, new LXDex(network, config));
}
return LXDex._instances.get(key)!;
}
/**
* Check if the connector is ready
*/
public async ready(): Promise<boolean> {
try {
const response = await this.httpClient.get('/health');
return response.data?.status === 'healthy';
} catch {
return false;
}
}
/**
* Get current network
*/
public getNetwork(): string {
return this.network;
}
/**
* Get configuration
*/
public getConfig(): LXDexConfig {
return { ...this.config };
}
// ============================================================================
// Router Schema - Swap Operations
// ============================================================================
/**
* Get a swap quote
*/
public async getQuote(
request: LXDexQuoteSwapRequest
): Promise<LXDexQuoteSwapResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getQuote',
params: {
baseToken: request.baseToken,
quoteToken: request.quoteToken,
amount: request.amount,
side: request.side,
slippagePct: request.slippagePct ?? this.config.slippagePct,
maxHops: request.maxHops ?? this.config.maxHops,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Execute a swap directly
*/
public async executeSwap(
request: LXDexExecuteSwapRequest
): Promise<LXDexSwapResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_executeSwap',
params: {
walletAddress: request.walletAddress,
baseToken: request.baseToken,
quoteToken: request.quoteToken,
amount: request.amount,
side: request.side,
slippagePct: request.slippagePct ?? this.config.slippagePct,
maxHops: request.maxHops ?? this.config.maxHops,
gasPrice: request.gasPrice,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Execute a previously obtained quote
*/
public async executeQuote(
request: LXDexExecuteQuoteRequest
): Promise<LXDexSwapResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_executeQuote',
params: {
walletAddress: request.walletAddress,
quoteId: request.quoteId,
gasPrice: request.gasPrice,
},
id: Date.now(),
});
return response.data.result;
}
// ============================================================================
// AMM Schema - Liquidity Pool Operations
// ============================================================================
/**
* Get pool information
*/
public async getPoolInfo(
request: LXDexPoolInfoRequest
): Promise<LXDexPoolInfoResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getPoolInfo',
params: {
tokenA: request.tokenA,
tokenB: request.tokenB,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Get LP position information
*/
public async getPositionInfo(
request: LXDexPositionInfoRequest
): Promise<LXDexPositionInfoResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getPositionInfo',
params: {
walletAddress: request.walletAddress,
poolAddress: request.poolAddress,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Add liquidity to a pool
*/
public async addLiquidity(
request: LXDexAddLiquidityRequest
): Promise<LXDexAddLiquidityResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_addLiquidity',
params: {
walletAddress: request.walletAddress,
tokenA: request.tokenA,
tokenB: request.tokenB,
amountA: request.amountA,
amountB: request.amountB,
slippagePct: request.slippagePct ?? this.config.slippagePct,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Remove liquidity from a pool
*/
public async removeLiquidity(
request: LXDexRemoveLiquidityRequest
): Promise<LXDexRemoveLiquidityResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_removeLiquidity',
params: {
walletAddress: request.walletAddress,
poolAddress: request.poolAddress,
liquidity: request.liquidity,
slippagePct: request.slippagePct ?? this.config.slippagePct,
},
id: Date.now(),
});
return response.data.result;
}
// ============================================================================
// CLMM Schema - Concentrated Liquidity Operations
// ============================================================================
/**
* Get CLMM pool information
*/
public async getCLMMPoolInfo(
request: LXDexCLMMPoolInfoRequest
): Promise<LXDexCLMMPoolInfoResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getCLMMPoolInfo',
params: {
tokenA: request.tokenA,
tokenB: request.tokenB,
fee: request.fee,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Get all CLMM positions owned by an address
*/
public async getPositionsOwned(
request: LXDexPositionsOwnedRequest
): Promise<LXDexPositionsOwnedResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getPositionsOwned',
params: {
walletAddress: request.walletAddress,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Get a quote for opening a CLMM position
*/
public async quotePosition(
request: LXDexQuotePositionRequest
): Promise<LXDexQuotePositionResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_quotePosition',
params: {
tokenA: request.tokenA,
tokenB: request.tokenB,
fee: request.fee,
tickLower: request.tickLower,
tickUpper: request.tickUpper,
amountA: request.amountA,
amountB: request.amountB,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Open a new CLMM position
*/
public async openPosition(
request: LXDexOpenPositionRequest
): Promise<LXDexOpenPositionResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_openPosition',
params: {
walletAddress: request.walletAddress,
tokenA: request.tokenA,
tokenB: request.tokenB,
fee: request.fee,
tickLower: request.tickLower,
tickUpper: request.tickUpper,
amountA: request.amountA,
amountB: request.amountB,
slippagePct: request.slippagePct ?? this.config.slippagePct,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Close a CLMM position
*/
public async closePosition(
request: LXDexClosePositionRequest
): Promise<LXDexClosePositionResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_closePosition',
params: {
walletAddress: request.walletAddress,
tokenId: request.tokenId,
slippagePct: request.slippagePct ?? this.config.slippagePct,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Collect fees from a CLMM position
*/
public async collectFees(
request: LXDexCollectFeesRequest
): Promise<LXDexCollectFeesResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_collectFees',
params: {
walletAddress: request.walletAddress,
tokenId: request.tokenId,
},
id: Date.now(),
});
return response.data.result;
}
// ============================================================================
// Order Book Schema - Central Limit Order Book Operations
// ============================================================================
/**
* Place an order on the order book
*/
public async placeOrder(
request: LXDexPlaceOrderRequest
): Promise<LXDexPlaceOrderResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_placeOrder',
params: {
walletAddress: request.walletAddress,
symbol: request.symbol,
side: request.side,
type: request.type,
price: request.price,
size: request.size,
timeInForce: request.timeInForce ?? 'GTC',
clientOrderId: request.clientOrderId,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Cancel an order
*/
public async cancelOrder(
request: LXDexCancelOrderRequest
): Promise<LXDexCancelOrderResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_cancelOrder',
params: {
walletAddress: request.walletAddress,
orderId: request.orderId,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Get orders for an address
*/
public async getOrders(
request: LXDexGetOrdersRequest
): Promise<LXDexGetOrdersResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getOrders',
params: {
walletAddress: request.walletAddress,
symbol: request.symbol,
status: request.status,
limit: request.limit ?? 100,
},
id: Date.now(),
});
return response.data.result;
}
/**
* Get the current order book
*/
public async getOrderBook(
request: LXDexOrderBookRequest
): Promise<LXDexOrderBookResponse> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getOrderBook',
params: {
symbol: request.symbol,
depth: request.depth ?? 100,
},
id: Date.now(),
});
return response.data.result;
}
// ============================================================================
// WebSocket - Real-time Updates
// ============================================================================
/**
* Connect to WebSocket for real-time updates
*/
public async connectWebSocket(): Promise<void> {
if (this.wsConnected) {
return;
}
const endpoints = getNetworkEndpoints(this.network);
return new Promise((resolve, reject) => {
this.wsClient = new WebSocket(endpoints.ws);
this.wsClient.on('open', () => {
this.wsConnected = true;
this.reconnectAttempts = 0;
console.log('[LXDex] WebSocket connected');
this.emit('connected');
resolve();
});
this.wsClient.on('message', (data: WebSocket.Data) => {
try {
const message = JSON.parse(data.toString());
this.handleWebSocketMessage(message);
} catch (error) {
console.error('[LXDex] Failed to parse WebSocket message:', error);
}
});
this.wsClient.on('close', () => {
this.wsConnected = false;
console.log('[LXDex] WebSocket disconnected');
this.emit('disconnected');
this.attemptReconnect();
});
this.wsClient.on('error', (error) => {
console.error('[LXDex] WebSocket error:', error);
this.emit('error', error);
reject(error);
});
});
}
/**
* Subscribe to order book updates
*/
public subscribeOrderBook(symbol: string): void {
this.sendWebSocketMessage({
type: 'subscribe',
channel: 'orderbook',
symbol,
});
}
/**
* Subscribe to trade updates
*/
public subscribeTrades(symbol: string): void {
this.sendWebSocketMessage({
type: 'subscribe',
channel: 'trades',
symbol,
});
}
/**
* Subscribe to order updates for a wallet
*/
public subscribeOrders(walletAddress: string): void {
this.sendWebSocketMessage({
type: 'subscribe',
channel: 'orders',
walletAddress,
});
}
/**
* Disconnect WebSocket
*/
public disconnectWebSocket(): void {
if (this.wsClient) {
this.wsClient.close();
this.wsClient = null;
this.wsConnected = false;
}
}
/**
* Close the connector and cleanup resources
*/
public async close(): Promise<void> {
this.disconnectWebSocket();
this.tokenCache.clear();
LXDex._instances.delete(this.network);
}
// ============================================================================
// Token Operations
// ============================================================================
/**
* Get token information by symbol or address
*/
public async getToken(symbolOrAddress: string): Promise<TokenInfo | null> {
// Check cache first
if (this.tokenCache.has(symbolOrAddress)) {
return this.tokenCache.get(symbolOrAddress)!;
}
try {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getToken',
params: { token: symbolOrAddress },
id: Date.now(),
});
const token = response.data.result as TokenInfo;
this.tokenCache.set(symbolOrAddress, token);
this.tokenCache.set(token.address, token);
this.tokenCache.set(token.symbol, token);
return token;
} catch {
return null;
}
}
/**
* Get all available trading pairs
*/
public async getTradingPairs(): Promise<TradingPair[]> {
const response = await this.httpClient.post('/rpc', {
jsonrpc: '2.0',
method: 'lx_getTradingPairs',
params: {},
id: Date.now(),
});
return response.data.result.pairs;
}
// ============================================================================
// Private Methods
// ============================================================================
private handleError(error: AxiosError): never {
const responseData = error.response?.data as { error?: { code?: string; message?: string } } | undefined;
if (responseData?.error) {
const { code, message } = responseData.error;
switch (code) {
case 'INSUFFICIENT_LIQUIDITY':
throw new InsufficientLiquidityError(message);
case 'SLIPPAGE_EXCEEDED':
throw new SlippageExceededError(message);
case 'ORDER_NOT_FOUND':
throw new OrderNotFoundError(message || 'unknown');
default:
throw new LXDexError(
message || 'Unknown error',
code || 'UNKNOWN',
responseData
);
}
}
throw new LXDexError(
error.message || 'Request failed',
'REQUEST_FAILED',
{ status: error.response?.status }
);
}
private handleWebSocketMessage(message: {
type: string;
channel?: string;
data?: unknown;
}): void {
switch (message.type) {
case 'orderbook':
this.emit('orderbook', message.data);
break;
case 'trade':
this.emit('trade', message.data);
break;
case 'order':
this.emit('order', message.data);
break;
case 'position':
this.emit('position', message.data);
break;
default:
console.log('[LXDex] Unknown message type:', message.type);
}
}
private sendWebSocketMessage(message: object): void {
if (this.wsClient && this.wsConnected) {
this.wsClient.send(JSON.stringify(message));
} else {
console.warn('[LXDex] WebSocket not connected, message not sent');
}
}
private attemptReconnect(): void {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
console.log(
`[LXDex] Attempting reconnect ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`
);
setTimeout(() => {
this.connectWebSocket().catch((error) => {
console.error('[LXDex] Reconnect failed:', error);
});
}, delay);
} else {
console.error('[LXDex] Max reconnect attempts reached');
this.emit('maxReconnectAttemptsReached');
}
}
}
export default LXDex;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
{
"name": "@lxdex/hummingbot-gateway-connector",
"version": "1.0.0",
"description": "LX DEX connector for Hummingbot Gateway - Ultra-low latency DEX with CLOB, AMM, and CLMM support",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"prepublishOnly": "npm run build"
},
"keywords": [
"lxdex",
"lux",
"hummingbot",
"gateway",
"dex",
"trading",
"amm",
"clmm",
"clob",
"defi",
"crypto"
],
"author": "Lux Partners Limited",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/luxfi/dex.git",
"directory": "hummingbot-gateway/src/connectors/lxdex"
},
"homepage": "https://dex.lux.network",
"bugs": {
"url": "https://github.com/luxfi/dex/issues"
},
"peerDependencies": {
"fastify": "^4.0.0"
},
"dependencies": {
"axios": "^1.13.2",
"ethers": "^6.9.0",
"uuid": "^9.0.0",
"ws": "^8.14.0"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/node": "^20.0.0",
"@types/uuid": "^9.0.0",
"@types/ws": "^8.5.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.0.0",
"jest": "^29.0.0",
"ts-jest": "^29.0.0",
"typescript": "^5.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
@@ -0,0 +1,136 @@
/**
* LX DEX Router Schema Routes
*
* Implements the Router schema endpoints for swap operations.
* - GET /quote-swap: Get optimal swap quote
* - POST /execute-swap: Execute swap directly
* - POST /execute-quote: Execute pre-fetched quote
*/
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { LXDex } from '../lxdex';
import {
LXDexQuoteSwapRequest,
LXDexExecuteSwapRequest,
LXDexExecuteQuoteRequest,
quoteSwapRequestSchema,
executeSwapRequestSchema,
} from '../schemas';
export async function routes(
fastify: FastifyInstance,
_options: FastifyPluginOptions
): Promise<void> {
/**
* GET /quote-swap
*
* Get an optimal swap quote with routing details.
* Returns the best route across all available liquidity sources.
*/
fastify.get<{
Querystring: LXDexQuoteSwapRequest;
}>(
'/quote-swap',
{
schema: {
querystring: quoteSwapRequestSchema,
response: {
200: {
type: 'object',
properties: {
quoteId: { type: 'string' },
tokenIn: { type: 'object' },
tokenOut: { type: 'object' },
amountIn: { type: 'string' },
amountOut: { type: 'string' },
price: { type: 'string' },
priceImpactPct: { type: 'string' },
minAmountOut: { type: 'string' },
maxAmountIn: { type: 'string' },
route: { type: 'object' },
estimatedGas: { type: 'string' },
expiresAt: { type: 'number' },
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.query.network || 'mainnet');
const quote = await connector.getQuote(request.query);
return reply.send(quote);
}
);
/**
* POST /execute-swap
*
* Execute a swap directly without pre-fetching a quote.
* The swap is executed at the best available price.
*/
fastify.post<{
Body: LXDexExecuteSwapRequest;
}>(
'/execute-swap',
{
schema: {
body: executeSwapRequestSchema,
response: {
200: {
type: 'object',
properties: {
txHash: { type: 'string' },
status: { type: 'string' },
tokenIn: { type: 'object' },
tokenOut: { type: 'object' },
amountIn: { type: 'string' },
amountOut: { type: 'string' },
price: { type: 'string' },
fee: { type: 'string' },
gasUsed: { type: 'string' },
blockNumber: { type: 'number' },
},
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.executeSwap(request.body);
return reply.send(result);
}
);
/**
* POST /execute-quote
*
* Execute a previously fetched quote.
* The quote must not be expired.
*/
fastify.post<{
Body: LXDexExecuteQuoteRequest;
}>(
'/execute-quote',
{
schema: {
body: {
type: 'object',
required: ['walletAddress', 'quoteId'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
quoteId: { type: 'string' },
gasPrice: { type: 'string' },
},
},
},
},
async (request, reply) => {
const connector = LXDex.getInstance(request.body.network || 'mainnet');
const result = await connector.executeQuote(request.body);
return reply.send(result);
}
);
}
export default routes;
@@ -0,0 +1,452 @@
/**
* LX DEX Gateway Connector Schemas
*
* Request and response schemas for the LX DEX connector.
* Implements Router, AMM, and CLMM schema types.
*/
// ============================================================================
// Common Types
// ============================================================================
export interface TokenInfo {
symbol: string;
address: string;
decimals: number;
name?: string;
}
export interface TradingPair {
baseToken: string;
quoteToken: string;
symbol: string;
}
export type OrderSide = 'BUY' | 'SELL';
export type OrderType = 'LIMIT' | 'MARKET' | 'STOP' | 'STOP_LIMIT';
export type TimeInForce = 'GTC' | 'IOC' | 'FOK' | 'GTT';
// ============================================================================
// Router Schema - Swap/Quote Endpoints
// ============================================================================
export interface LXDexQuoteSwapRequest {
network?: string;
baseToken: string;
quoteToken: string;
amount: string;
side: OrderSide;
slippagePct?: number;
maxHops?: number;
}
export interface SwapRoute {
path: string[];
pools: string[];
expectedOutput: string;
priceImpact: string;
fee: string;
}
export interface LXDexQuoteSwapResponse {
quoteId: string;
tokenIn: TokenInfo;
tokenOut: TokenInfo;
amountIn: string;
amountOut: string;
price: string;
priceImpactPct: string;
minAmountOut: string;
maxAmountIn: string;
route: SwapRoute;
estimatedGas: string;
expiresAt: number;
}
export interface LXDexExecuteSwapRequest {
network?: string;
walletAddress: string;
baseToken: string;
quoteToken: string;
amount: string;
side: OrderSide;
slippagePct?: number;
maxHops?: number;
gasPrice?: string;
}
export interface LXDexExecuteQuoteRequest {
network?: string;
walletAddress: string;
quoteId: string;
gasPrice?: string;
}
export interface LXDexSwapResponse {
txHash: string;
status: 'pending' | 'confirmed' | 'failed';
tokenIn: TokenInfo;
tokenOut: TokenInfo;
amountIn: string;
amountOut: string;
price: string;
fee: string;
gasUsed?: string;
blockNumber?: number;
}
// ============================================================================
// AMM Schema - Liquidity Pool Endpoints
// ============================================================================
export interface LXDexPoolInfoRequest {
network?: string;
tokenA: string;
tokenB: string;
}
export interface PoolInfo {
address: string;
tokenA: TokenInfo;
tokenB: TokenInfo;
reserveA: string;
reserveB: string;
totalLiquidity: string;
fee: string;
apy?: string;
volume24h?: string;
}
export interface LXDexPoolInfoResponse {
pools: PoolInfo[];
}
export interface LXDexPositionInfoRequest {
network?: string;
walletAddress: string;
poolAddress?: string;
}
export interface LPPosition {
poolAddress: string;
tokenA: TokenInfo;
tokenB: TokenInfo;
liquidity: string;
sharePercent: string;
valueUSD: string;
unclaimedFees: {
tokenA: string;
tokenB: string;
};
}
export interface LXDexPositionInfoResponse {
positions: LPPosition[];
totalValueUSD: string;
}
export interface LXDexAddLiquidityRequest {
network?: string;
walletAddress: string;
tokenA: string;
tokenB: string;
amountA: string;
amountB: string;
slippagePct?: number;
}
export interface LXDexAddLiquidityResponse {
txHash: string;
status: 'pending' | 'confirmed' | 'failed';
poolAddress: string;
liquidityMinted: string;
amountA: string;
amountB: string;
}
export interface LXDexRemoveLiquidityRequest {
network?: string;
walletAddress: string;
poolAddress: string;
liquidity: string;
slippagePct?: number;
}
export interface LXDexRemoveLiquidityResponse {
txHash: string;
status: 'pending' | 'confirmed' | 'failed';
amountA: string;
amountB: string;
liquidityBurned: string;
}
// ============================================================================
// CLMM Schema - Concentrated Liquidity Endpoints
// ============================================================================
export interface LXDexCLMMPoolInfoRequest {
network?: string;
tokenA: string;
tokenB: string;
fee?: number; // Fee tier in basis points
}
export interface CLMMPoolInfo {
address: string;
tokenA: TokenInfo;
tokenB: TokenInfo;
fee: number;
tickSpacing: number;
currentTick: number;
currentPrice: string;
liquidity: string;
volume24h: string;
tvlUSD: string;
}
export interface LXDexCLMMPoolInfoResponse {
pools: CLMMPoolInfo[];
}
export interface LXDexPositionsOwnedRequest {
network?: string;
walletAddress: string;
}
export interface CLMMPosition {
tokenId: string;
poolAddress: string;
tokenA: TokenInfo;
tokenB: TokenInfo;
tickLower: number;
tickUpper: number;
liquidity: string;
amountA: string;
amountB: string;
unclaimedFeesA: string;
unclaimedFeesB: string;
inRange: boolean;
valueUSD: string;
}
export interface LXDexPositionsOwnedResponse {
positions: CLMMPosition[];
totalValueUSD: string;
}
export interface LXDexQuotePositionRequest {
network?: string;
tokenA: string;
tokenB: string;
fee: number;
tickLower: number;
tickUpper: number;
amountA?: string;
amountB?: string;
}
export interface LXDexQuotePositionResponse {
estimatedAmountA: string;
estimatedAmountB: string;
estimatedLiquidity: string;
priceRange: {
lower: string;
upper: string;
current: string;
};
inRange: boolean;
}
export interface LXDexOpenPositionRequest {
network?: string;
walletAddress: string;
tokenA: string;
tokenB: string;
fee: number;
tickLower: number;
tickUpper: number;
amountA: string;
amountB: string;
slippagePct?: number;
}
export interface LXDexOpenPositionResponse {
txHash: string;
status: 'pending' | 'confirmed' | 'failed';
tokenId: string;
liquidity: string;
amountA: string;
amountB: string;
}
export interface LXDexClosePositionRequest {
network?: string;
walletAddress: string;
tokenId: string;
slippagePct?: number;
}
export interface LXDexClosePositionResponse {
txHash: string;
status: 'pending' | 'confirmed' | 'failed';
amountA: string;
amountB: string;
feesCollectedA: string;
feesCollectedB: string;
}
export interface LXDexCollectFeesRequest {
network?: string;
walletAddress: string;
tokenId: string;
}
export interface LXDexCollectFeesResponse {
txHash: string;
status: 'pending' | 'confirmed' | 'failed';
feesCollectedA: string;
feesCollectedB: string;
}
// ============================================================================
// Order Book Schema - Central Limit Order Book
// ============================================================================
export interface LXDexPlaceOrderRequest {
network?: string;
walletAddress: string;
symbol: string;
side: OrderSide;
type: OrderType;
price?: string;
size: string;
timeInForce?: TimeInForce;
clientOrderId?: string;
}
export interface Order {
orderId: string;
clientOrderId?: string;
symbol: string;
side: OrderSide;
type: OrderType;
status: 'open' | 'partial' | 'filled' | 'cancelled' | 'rejected';
price: string;
size: string;
filledSize: string;
remainingSize: string;
avgFillPrice?: string;
fee: string;
createdAt: number;
updatedAt: number;
}
export interface LXDexPlaceOrderResponse {
txHash?: string;
order: Order;
}
export interface LXDexCancelOrderRequest {
network?: string;
walletAddress: string;
orderId: string;
}
export interface LXDexCancelOrderResponse {
txHash?: string;
orderId: string;
status: 'cancelled' | 'failed';
}
export interface LXDexGetOrdersRequest {
network?: string;
walletAddress: string;
symbol?: string;
status?: string[];
limit?: number;
}
export interface LXDexGetOrdersResponse {
orders: Order[];
total: number;
}
export interface LXDexOrderBookRequest {
network?: string;
symbol: string;
depth?: number;
}
export interface OrderBookLevel {
price: string;
size: string;
orders: number;
}
export interface LXDexOrderBookResponse {
symbol: string;
bids: OrderBookLevel[];
asks: OrderBookLevel[];
timestamp: number;
sequenceNumber: number;
}
// ============================================================================
// Validation Schemas (JSON Schema format for Fastify)
// ============================================================================
export const quoteSwapRequestSchema = {
type: 'object',
required: ['baseToken', 'quoteToken', 'amount', 'side'],
properties: {
network: { type: 'string' },
baseToken: { type: 'string' },
quoteToken: { type: 'string' },
amount: { type: 'string' },
side: { type: 'string', enum: ['BUY', 'SELL'] },
slippagePct: { type: 'number', minimum: 0, maximum: 100 },
maxHops: { type: 'number', minimum: 1, maximum: 10 },
},
};
export const executeSwapRequestSchema = {
type: 'object',
required: ['walletAddress', 'baseToken', 'quoteToken', 'amount', 'side'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
baseToken: { type: 'string' },
quoteToken: { type: 'string' },
amount: { type: 'string' },
side: { type: 'string', enum: ['BUY', 'SELL'] },
slippagePct: { type: 'number', minimum: 0, maximum: 100 },
maxHops: { type: 'number', minimum: 1, maximum: 10 },
gasPrice: { type: 'string' },
},
};
export const placeOrderRequestSchema = {
type: 'object',
required: ['walletAddress', 'symbol', 'side', 'type', 'size'],
properties: {
network: { type: 'string' },
walletAddress: { type: 'string' },
symbol: { type: 'string' },
side: { type: 'string', enum: ['BUY', 'SELL'] },
type: { type: 'string', enum: ['LIMIT', 'MARKET', 'STOP', 'STOP_LIMIT'] },
price: { type: 'string' },
size: { type: 'string' },
timeInForce: { type: 'string', enum: ['GTC', 'IOC', 'FOK', 'GTT'] },
clientOrderId: { type: 'string' },
},
};
export const orderBookRequestSchema = {
type: 'object',
required: ['symbol'],
properties: {
network: { type: 'string' },
symbol: { type: 'string' },
depth: { type: 'number', minimum: 1, maximum: 500 },
},
};
@@ -0,0 +1,47 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": false,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"@/*": ["./*"]
},
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": [
"./**/*.ts"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts"
]
}