mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
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:
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Integrations",
|
||||
"pages": [
|
||||
"index",
|
||||
"hummingbot",
|
||||
"hummingbot-quickstart",
|
||||
"hummingbot-strategies"
|
||||
]
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
"bridge",
|
||||
"...bridge",
|
||||
"contracts",
|
||||
"---Integrations---",
|
||||
"integrations",
|
||||
"---Infrastructure---",
|
||||
"connectivity",
|
||||
"data",
|
||||
|
||||
@@ -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"eToken=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
|
||||
@@ -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;
|
||||
+6136
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"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user