@luxfi/dex 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/clob.d.ts +52 -0
- package/dist/client/clob.d.ts.map +1 -0
- package/dist/client/clob.js +196 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +6 -0
- package/dist/client/types.d.ts +126 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +5 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/use-quote.d.ts +18 -0
- package/dist/hooks/use-quote.d.ts.map +1 -0
- package/dist/hooks/use-quote.js +65 -0
- package/dist/hooks/use-swap.d.ts +17 -0
- package/dist/hooks/use-swap.d.ts.map +1 -0
- package/dist/hooks/use-swap.js +75 -0
- package/dist/index.d.ts +33 -115
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -225
- package/dist/precompile/abis.d.ts +400 -0
- package/dist/precompile/abis.d.ts.map +1 -0
- package/dist/precompile/abis.js +287 -0
- package/dist/precompile/addresses.d.ts +65 -0
- package/dist/precompile/addresses.d.ts.map +1 -0
- package/dist/precompile/addresses.js +52 -0
- package/dist/precompile/index.d.ts +8 -0
- package/dist/precompile/index.d.ts.map +1 -0
- package/dist/precompile/index.js +7 -0
- package/dist/precompile/types.d.ts +76 -0
- package/dist/precompile/types.d.ts.map +1 -0
- package/dist/precompile/types.js +17 -0
- package/dist/router/index.d.ts +7 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +6 -0
- package/dist/router/router.d.ts +58 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/router.js +272 -0
- package/dist/router/types.d.ts +76 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/types.js +1 -0
- package/package.json +55 -29
- package/src/client/clob.ts +256 -0
- package/src/client/index.ts +6 -0
- package/src/client/types.ts +148 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/use-quote.ts +92 -0
- package/src/hooks/use-swap.ts +103 -0
- package/src/index.ts +76 -309
- package/src/precompile/abis.ts +291 -0
- package/src/precompile/addresses.ts +72 -0
- package/src/precompile/index.ts +7 -0
- package/src/precompile/types.ts +96 -0
- package/src/router/index.ts +6 -0
- package/src/router/router.ts +338 -0
- package/src/router/types.ts +87 -0
- package/dist/marketData.d.ts +0 -152
- package/dist/marketData.d.ts.map +0 -1
- package/dist/marketData.js +0 -253
- package/src/marketData.ts +0 -351
- package/tsconfig.json +0 -19
package/package.json
CHANGED
|
@@ -1,41 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luxfi/dex",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Lux DEX SDK - CLOB client, AMM precompiles, and omnichain routing",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./client": {
|
|
15
|
+
"types": "./dist/client/index.d.ts",
|
|
16
|
+
"import": "./dist/client/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./router": {
|
|
19
|
+
"types": "./dist/router/index.d.ts",
|
|
20
|
+
"import": "./dist/router/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./precompile": {
|
|
23
|
+
"types": "./dist/precompile/index.d.ts",
|
|
24
|
+
"import": "./dist/precompile/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./hooks": {
|
|
27
|
+
"types": "./dist/hooks/index.d.ts",
|
|
28
|
+
"import": "./dist/hooks/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"src"
|
|
34
|
+
],
|
|
7
35
|
"scripts": {
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
8
37
|
"build": "tsc",
|
|
9
|
-
"
|
|
10
|
-
"lint": "eslint src/**/*.ts",
|
|
11
|
-
"prepublish": "npm run build"
|
|
38
|
+
"prepublishOnly": "bun run build"
|
|
12
39
|
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"luxfi",
|
|
15
|
-
"dex",
|
|
16
|
-
"trading",
|
|
17
|
-
"blockchain",
|
|
18
|
-
"sdk"
|
|
19
|
-
],
|
|
20
|
-
"author": "Lux Network",
|
|
21
|
-
"license": "MIT",
|
|
22
40
|
"dependencies": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
41
|
+
"viem": "^2.30.6",
|
|
42
|
+
"wagmi": "^3.1.3",
|
|
43
|
+
"zustand": "^5.0.5"
|
|
25
44
|
},
|
|
26
45
|
"devDependencies": {
|
|
27
|
-
"@types/
|
|
28
|
-
"@types/
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"jest": "^29.5.0",
|
|
34
|
-
"ts-jest": "^29.1.0",
|
|
35
|
-
"typescript": "^5.0.0"
|
|
46
|
+
"@types/node": "^25.0.3",
|
|
47
|
+
"@types/react": "^19.2.3",
|
|
48
|
+
"typescript": "^5.8.3"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"react": "^19.0.0"
|
|
36
52
|
},
|
|
37
53
|
"repository": {
|
|
38
54
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/luxfi/
|
|
40
|
-
|
|
55
|
+
"url": "https://github.com/luxfi/exchange.git",
|
|
56
|
+
"directory": "packages/dex"
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"lux",
|
|
60
|
+
"dex",
|
|
61
|
+
"defi",
|
|
62
|
+
"clob",
|
|
63
|
+
"amm",
|
|
64
|
+
"swap",
|
|
65
|
+
"precompile"
|
|
66
|
+
]
|
|
41
67
|
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLOB Client
|
|
3
|
+
* WebSocket client for lux/dex order book
|
|
4
|
+
*/
|
|
5
|
+
import type {
|
|
6
|
+
ICLOBClient,
|
|
7
|
+
OrderRequest,
|
|
8
|
+
Order,
|
|
9
|
+
OrderBook,
|
|
10
|
+
Trade,
|
|
11
|
+
Position,
|
|
12
|
+
Balance
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
interface CLOBClientOptions {
|
|
16
|
+
url: string
|
|
17
|
+
debug?: boolean
|
|
18
|
+
reconnect?: boolean
|
|
19
|
+
reconnectInterval?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type MessageHandler = (data: any) => void
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Central Limit Order Book client
|
|
26
|
+
* Connects to lux/dex WebSocket server
|
|
27
|
+
*/
|
|
28
|
+
export class CLOBClient implements ICLOBClient {
|
|
29
|
+
private ws: WebSocket | null = null
|
|
30
|
+
private url: string
|
|
31
|
+
private debug: boolean
|
|
32
|
+
private reconnect: boolean
|
|
33
|
+
private reconnectInterval: number
|
|
34
|
+
private connected: boolean = false
|
|
35
|
+
private authenticated: boolean = false
|
|
36
|
+
private requestId: number = 0
|
|
37
|
+
private pendingRequests: Map<number, { resolve: Function; reject: Function }> = new Map()
|
|
38
|
+
private subscriptions: Map<string, Set<MessageHandler>> = new Map()
|
|
39
|
+
|
|
40
|
+
constructor(options: CLOBClientOptions) {
|
|
41
|
+
this.url = options.url
|
|
42
|
+
this.debug = options.debug ?? false
|
|
43
|
+
this.reconnect = options.reconnect ?? true
|
|
44
|
+
this.reconnectInterval = options.reconnectInterval ?? 5000
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private log(...args: any[]) {
|
|
48
|
+
if (this.debug) {
|
|
49
|
+
console.log('[CLOBClient]', ...args)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async connect(): Promise<void> {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
try {
|
|
56
|
+
this.ws = new WebSocket(this.url)
|
|
57
|
+
|
|
58
|
+
this.ws.onopen = () => {
|
|
59
|
+
this.connected = true
|
|
60
|
+
this.log('Connected to', this.url)
|
|
61
|
+
resolve()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.ws.onclose = () => {
|
|
65
|
+
this.connected = false
|
|
66
|
+
this.authenticated = false
|
|
67
|
+
this.log('Disconnected')
|
|
68
|
+
|
|
69
|
+
if (this.reconnect) {
|
|
70
|
+
setTimeout(() => this.connect(), this.reconnectInterval)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.ws.onerror = (error) => {
|
|
75
|
+
this.log('Error:', error)
|
|
76
|
+
reject(error)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.ws.onmessage = (event) => {
|
|
80
|
+
this.handleMessage(event.data)
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
reject(error)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async disconnect(): Promise<void> {
|
|
89
|
+
this.reconnect = false
|
|
90
|
+
this.ws?.close()
|
|
91
|
+
this.ws = null
|
|
92
|
+
this.connected = false
|
|
93
|
+
this.authenticated = false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
isConnected(): boolean {
|
|
97
|
+
return this.connected
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private handleMessage(data: string) {
|
|
101
|
+
try {
|
|
102
|
+
const message = JSON.parse(data)
|
|
103
|
+
this.log('Received:', message)
|
|
104
|
+
|
|
105
|
+
// Handle response to request
|
|
106
|
+
if (message.id && this.pendingRequests.has(message.id)) {
|
|
107
|
+
const { resolve, reject } = this.pendingRequests.get(message.id)!
|
|
108
|
+
this.pendingRequests.delete(message.id)
|
|
109
|
+
|
|
110
|
+
if (message.error) {
|
|
111
|
+
reject(new Error(message.error))
|
|
112
|
+
} else {
|
|
113
|
+
resolve(message.result || message)
|
|
114
|
+
}
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle subscription updates
|
|
119
|
+
const type = message.type || message.channel
|
|
120
|
+
if (type && this.subscriptions.has(type)) {
|
|
121
|
+
this.subscriptions.get(type)!.forEach(handler => handler(message))
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.log('Parse error:', error)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async request<T>(method: string, params?: any): Promise<T> {
|
|
129
|
+
if (!this.connected) {
|
|
130
|
+
throw new Error('Not connected')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const id = ++this.requestId
|
|
134
|
+
const message = {
|
|
135
|
+
jsonrpc: '2.0',
|
|
136
|
+
id,
|
|
137
|
+
method,
|
|
138
|
+
params,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
this.pendingRequests.set(id, { resolve, reject })
|
|
143
|
+
this.ws!.send(JSON.stringify(message))
|
|
144
|
+
this.log('Sent:', message)
|
|
145
|
+
|
|
146
|
+
// Timeout after 30 seconds
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
if (this.pendingRequests.has(id)) {
|
|
149
|
+
this.pendingRequests.delete(id)
|
|
150
|
+
reject(new Error('Request timeout'))
|
|
151
|
+
}
|
|
152
|
+
}, 30000)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async authenticate(apiKey: string, apiSecret: string): Promise<void> {
|
|
157
|
+
await this.request('authenticate', { apiKey, apiSecret })
|
|
158
|
+
this.authenticated = true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async placeOrder(order: OrderRequest): Promise<Order> {
|
|
162
|
+
return this.request('placeOrder', order)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async cancelOrder(orderId: string): Promise<void> {
|
|
166
|
+
await this.request('cancelOrder', { orderId })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async getOrder(orderId: string): Promise<Order> {
|
|
170
|
+
return this.request('getOrder', { orderId })
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async getOrders(symbol?: string): Promise<Order[]> {
|
|
174
|
+
return this.request('getOrders', { symbol })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async getOrderBook(symbol: string, depth: number = 20): Promise<OrderBook> {
|
|
178
|
+
return this.request('getOrderBook', { symbol, depth })
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async getTrades(symbol: string, limit: number = 100): Promise<Trade[]> {
|
|
182
|
+
return this.request('getTrades', { symbol, limit })
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async getPositions(): Promise<Position[]> {
|
|
186
|
+
return this.request('getPositions', {})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async getBalances(): Promise<Balance[]> {
|
|
190
|
+
return this.request('getBalances', {})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
subscribeOrderBook(symbol: string, callback: (book: OrderBook) => void): () => void {
|
|
194
|
+
const channel = `orderbook:${symbol}`
|
|
195
|
+
|
|
196
|
+
if (!this.subscriptions.has(channel)) {
|
|
197
|
+
this.subscriptions.set(channel, new Set())
|
|
198
|
+
this.request('subscribe', { channel: 'orderbook', symbol }).catch(console.error)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.subscriptions.get(channel)!.add(callback)
|
|
202
|
+
|
|
203
|
+
return () => {
|
|
204
|
+
this.subscriptions.get(channel)?.delete(callback)
|
|
205
|
+
if (this.subscriptions.get(channel)?.size === 0) {
|
|
206
|
+
this.subscriptions.delete(channel)
|
|
207
|
+
this.request('unsubscribe', { channel: 'orderbook', symbol }).catch(console.error)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
subscribeTrades(symbol: string, callback: (trade: Trade) => void): () => void {
|
|
213
|
+
const channel = `trades:${symbol}`
|
|
214
|
+
|
|
215
|
+
if (!this.subscriptions.has(channel)) {
|
|
216
|
+
this.subscriptions.set(channel, new Set())
|
|
217
|
+
this.request('subscribe', { channel: 'trades', symbol }).catch(console.error)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.subscriptions.get(channel)!.add(callback)
|
|
221
|
+
|
|
222
|
+
return () => {
|
|
223
|
+
this.subscriptions.get(channel)?.delete(callback)
|
|
224
|
+
if (this.subscriptions.get(channel)?.size === 0) {
|
|
225
|
+
this.subscriptions.delete(channel)
|
|
226
|
+
this.request('unsubscribe', { channel: 'trades', symbol }).catch(console.error)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
subscribeOrders(callback: (order: Order) => void): () => void {
|
|
232
|
+
const channel = 'orders'
|
|
233
|
+
|
|
234
|
+
if (!this.subscriptions.has(channel)) {
|
|
235
|
+
this.subscriptions.set(channel, new Set())
|
|
236
|
+
this.request('subscribe', { channel: 'orders' }).catch(console.error)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.subscriptions.get(channel)!.add(callback)
|
|
240
|
+
|
|
241
|
+
return () => {
|
|
242
|
+
this.subscriptions.get(channel)?.delete(callback)
|
|
243
|
+
if (this.subscriptions.get(channel)?.size === 0) {
|
|
244
|
+
this.subscriptions.delete(channel)
|
|
245
|
+
this.request('unsubscribe', { channel: 'orders' }).catch(console.error)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Create a CLOB client
|
|
253
|
+
*/
|
|
254
|
+
export function createCLOBClient(url: string, debug: boolean = false): ICLOBClient {
|
|
255
|
+
return new CLOBClient({ url, debug })
|
|
256
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLOB Client Types
|
|
3
|
+
* Interfaces for Central Limit Order Book operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Order side
|
|
8
|
+
*/
|
|
9
|
+
export type OrderSide = 'buy' | 'sell'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Order type
|
|
13
|
+
*/
|
|
14
|
+
export type OrderType = 'limit' | 'market' | 'stop' | 'stop_limit'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Order status
|
|
18
|
+
*/
|
|
19
|
+
export type OrderStatus = 'pending' | 'open' | 'partial' | 'filled' | 'cancelled' | 'rejected'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Time in force
|
|
23
|
+
*/
|
|
24
|
+
export type TimeInForce = 'GTC' | 'IOC' | 'FOK' | 'GTD'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Order request
|
|
28
|
+
*/
|
|
29
|
+
export interface OrderRequest {
|
|
30
|
+
symbol: string
|
|
31
|
+
side: OrderSide
|
|
32
|
+
type: OrderType
|
|
33
|
+
price?: number // Required for limit orders
|
|
34
|
+
size: number
|
|
35
|
+
timeInForce?: TimeInForce
|
|
36
|
+
clientOrderId?: string
|
|
37
|
+
reduceOnly?: boolean
|
|
38
|
+
postOnly?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Order response
|
|
43
|
+
*/
|
|
44
|
+
export interface Order {
|
|
45
|
+
orderId: string
|
|
46
|
+
clientOrderId?: string
|
|
47
|
+
symbol: string
|
|
48
|
+
side: OrderSide
|
|
49
|
+
type: OrderType
|
|
50
|
+
price: number
|
|
51
|
+
size: number
|
|
52
|
+
filledSize: number
|
|
53
|
+
remainingSize: number
|
|
54
|
+
status: OrderStatus
|
|
55
|
+
timeInForce: TimeInForce
|
|
56
|
+
createdAt: number
|
|
57
|
+
updatedAt: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Order book entry
|
|
62
|
+
*/
|
|
63
|
+
export interface OrderBookEntry {
|
|
64
|
+
price: number
|
|
65
|
+
size: number
|
|
66
|
+
count: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Order book
|
|
71
|
+
*/
|
|
72
|
+
export interface OrderBook {
|
|
73
|
+
symbol: string
|
|
74
|
+
bids: OrderBookEntry[]
|
|
75
|
+
asks: OrderBookEntry[]
|
|
76
|
+
timestamp: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Trade
|
|
81
|
+
*/
|
|
82
|
+
export interface Trade {
|
|
83
|
+
tradeId: string
|
|
84
|
+
symbol: string
|
|
85
|
+
side: OrderSide
|
|
86
|
+
price: number
|
|
87
|
+
size: number
|
|
88
|
+
timestamp: number
|
|
89
|
+
makerOrderId: string
|
|
90
|
+
takerOrderId: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Position
|
|
95
|
+
*/
|
|
96
|
+
export interface Position {
|
|
97
|
+
symbol: string
|
|
98
|
+
side: 'long' | 'short'
|
|
99
|
+
size: number
|
|
100
|
+
entryPrice: number
|
|
101
|
+
markPrice: number
|
|
102
|
+
liquidationPrice: number
|
|
103
|
+
unrealizedPnl: number
|
|
104
|
+
margin: number
|
|
105
|
+
leverage: number
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Balance
|
|
110
|
+
*/
|
|
111
|
+
export interface Balance {
|
|
112
|
+
currency: string
|
|
113
|
+
available: number
|
|
114
|
+
locked: number
|
|
115
|
+
total: number
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* CLOB client interface
|
|
120
|
+
*/
|
|
121
|
+
export interface ICLOBClient {
|
|
122
|
+
// Connection
|
|
123
|
+
connect(): Promise<void>
|
|
124
|
+
disconnect(): Promise<void>
|
|
125
|
+
isConnected(): boolean
|
|
126
|
+
|
|
127
|
+
// Authentication
|
|
128
|
+
authenticate(apiKey: string, apiSecret: string): Promise<void>
|
|
129
|
+
|
|
130
|
+
// Orders
|
|
131
|
+
placeOrder(order: OrderRequest): Promise<Order>
|
|
132
|
+
cancelOrder(orderId: string): Promise<void>
|
|
133
|
+
getOrder(orderId: string): Promise<Order>
|
|
134
|
+
getOrders(symbol?: string): Promise<Order[]>
|
|
135
|
+
|
|
136
|
+
// Market data
|
|
137
|
+
getOrderBook(symbol: string, depth?: number): Promise<OrderBook>
|
|
138
|
+
getTrades(symbol: string, limit?: number): Promise<Trade[]>
|
|
139
|
+
|
|
140
|
+
// Account
|
|
141
|
+
getPositions(): Promise<Position[]>
|
|
142
|
+
getBalances(): Promise<Balance[]>
|
|
143
|
+
|
|
144
|
+
// Subscriptions
|
|
145
|
+
subscribeOrderBook(symbol: string, callback: (book: OrderBook) => void): () => void
|
|
146
|
+
subscribeTrades(symbol: string, callback: (trade: Trade) => void): () => void
|
|
147
|
+
subscribeOrders(callback: (order: Order) => void): () => void
|
|
148
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect, useRef } from 'react'
|
|
4
|
+
import type { Address } from 'viem'
|
|
5
|
+
import { usePublicClient } from 'wagmi'
|
|
6
|
+
import { OmnichainRouter, type Quote, type QuoteRequest } from '../router'
|
|
7
|
+
|
|
8
|
+
interface UseQuoteOptions {
|
|
9
|
+
refreshInterval?: number // ms
|
|
10
|
+
enabled?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface UseQuoteResult {
|
|
14
|
+
quote: Quote | null
|
|
15
|
+
isLoading: boolean
|
|
16
|
+
error: Error | null
|
|
17
|
+
refetch: () => Promise<void>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Hook to get swap quotes from the omnichain router
|
|
22
|
+
*/
|
|
23
|
+
export function useQuote(
|
|
24
|
+
tokenIn: Address | undefined,
|
|
25
|
+
tokenOut: Address | undefined,
|
|
26
|
+
amountIn: bigint | undefined,
|
|
27
|
+
options: UseQuoteOptions = {}
|
|
28
|
+
): UseQuoteResult {
|
|
29
|
+
const { refreshInterval = 10000, enabled = true } = options
|
|
30
|
+
const publicClient = usePublicClient()
|
|
31
|
+
const routerRef = useRef<OmnichainRouter | null>(null)
|
|
32
|
+
|
|
33
|
+
const [quote, setQuote] = useState<Quote | null>(null)
|
|
34
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
35
|
+
const [error, setError] = useState<Error | null>(null)
|
|
36
|
+
|
|
37
|
+
// Initialize router
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!routerRef.current) {
|
|
40
|
+
routerRef.current = new OmnichainRouter()
|
|
41
|
+
}
|
|
42
|
+
if (publicClient) {
|
|
43
|
+
routerRef.current.setPublicClient(publicClient)
|
|
44
|
+
}
|
|
45
|
+
}, [publicClient])
|
|
46
|
+
|
|
47
|
+
const fetchQuote = useCallback(async () => {
|
|
48
|
+
if (!tokenIn || !tokenOut || !amountIn || amountIn === 0n || !enabled) {
|
|
49
|
+
setQuote(null)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setIsLoading(true)
|
|
54
|
+
setError(null)
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const request: QuoteRequest = {
|
|
58
|
+
tokenIn,
|
|
59
|
+
tokenOut,
|
|
60
|
+
amountIn,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const newQuote = await routerRef.current!.getQuote(request)
|
|
64
|
+
setQuote(newQuote)
|
|
65
|
+
} catch (err) {
|
|
66
|
+
setError(err instanceof Error ? err : new Error('Failed to get quote'))
|
|
67
|
+
setQuote(null)
|
|
68
|
+
} finally {
|
|
69
|
+
setIsLoading(false)
|
|
70
|
+
}
|
|
71
|
+
}, [tokenIn, tokenOut, amountIn, enabled])
|
|
72
|
+
|
|
73
|
+
// Fetch on mount and when inputs change
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
fetchQuote()
|
|
76
|
+
}, [fetchQuote])
|
|
77
|
+
|
|
78
|
+
// Auto-refresh
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!enabled || refreshInterval <= 0) return
|
|
81
|
+
|
|
82
|
+
const interval = setInterval(fetchQuote, refreshInterval)
|
|
83
|
+
return () => clearInterval(interval)
|
|
84
|
+
}, [fetchQuote, enabled, refreshInterval])
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
quote,
|
|
88
|
+
isLoading,
|
|
89
|
+
error,
|
|
90
|
+
refetch: fetchQuote,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import type { Address } from 'viem'
|
|
5
|
+
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
|
|
6
|
+
import { DEX_PRECOMPILES } from '../precompile/addresses'
|
|
7
|
+
import { SWAP_ROUTER_ABI } from '../precompile/abis'
|
|
8
|
+
import { createPoolKey } from '../precompile/types'
|
|
9
|
+
import type { Quote } from '../router'
|
|
10
|
+
|
|
11
|
+
interface UseSwapResult {
|
|
12
|
+
swap: (quote: Quote, recipient: Address) => Promise<void>
|
|
13
|
+
isPending: boolean
|
|
14
|
+
isConfirming: boolean
|
|
15
|
+
isSuccess: boolean
|
|
16
|
+
error: Error | null
|
|
17
|
+
txHash: `0x${string}` | undefined
|
|
18
|
+
reset: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook to execute swaps via the DEX precompiles
|
|
23
|
+
*/
|
|
24
|
+
export function useSwap(): UseSwapResult {
|
|
25
|
+
const [error, setError] = useState<Error | null>(null)
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
data: txHash,
|
|
29
|
+
writeContractAsync,
|
|
30
|
+
isPending,
|
|
31
|
+
reset: resetWrite,
|
|
32
|
+
} = useWriteContract()
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
isLoading: isConfirming,
|
|
36
|
+
isSuccess,
|
|
37
|
+
} = useWaitForTransactionReceipt({
|
|
38
|
+
hash: txHash,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const swap = useCallback(async (quote: Quote, recipient: Address) => {
|
|
42
|
+
setError(null)
|
|
43
|
+
|
|
44
|
+
if (!quote || quote.route.length === 0) {
|
|
45
|
+
setError(new Error('Invalid quote'))
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const step = quote.route[0]
|
|
50
|
+
|
|
51
|
+
if (step.source === 'amm') {
|
|
52
|
+
try {
|
|
53
|
+
// Execute AMM swap via precompile
|
|
54
|
+
const poolKey = createPoolKey(quote.tokenIn, quote.tokenOut)
|
|
55
|
+
const zeroForOne = quote.tokenIn.toLowerCase() < quote.tokenOut.toLowerCase()
|
|
56
|
+
|
|
57
|
+
await writeContractAsync({
|
|
58
|
+
address: DEX_PRECOMPILES.SWAP_ROUTER,
|
|
59
|
+
abi: SWAP_ROUTER_ABI,
|
|
60
|
+
functionName: 'exactInputSingle',
|
|
61
|
+
args: [{
|
|
62
|
+
poolKey,
|
|
63
|
+
zeroForOne,
|
|
64
|
+
amountIn: quote.amountIn,
|
|
65
|
+
amountOutMinimum: quote.minimumAmountOut,
|
|
66
|
+
sqrtPriceLimitX96: 0n,
|
|
67
|
+
hookData: '0x',
|
|
68
|
+
}],
|
|
69
|
+
// Include native value if swapping from native LUX
|
|
70
|
+
value: quote.tokenIn === '0x0000000000000000000000000000000000000000'
|
|
71
|
+
? quote.amountIn
|
|
72
|
+
: 0n,
|
|
73
|
+
})
|
|
74
|
+
} catch (err) {
|
|
75
|
+
setError(err instanceof Error ? err : new Error('Swap failed'))
|
|
76
|
+
throw err
|
|
77
|
+
}
|
|
78
|
+
} else if (step.source === 'clob') {
|
|
79
|
+
// CLOB swaps are handled off-chain
|
|
80
|
+
// The frontend should use the CLOB client directly
|
|
81
|
+
setError(new Error('CLOB swaps should be executed via CLOB client'))
|
|
82
|
+
throw new Error('CLOB swaps not supported in this hook')
|
|
83
|
+
} else {
|
|
84
|
+
setError(new Error(`Unknown route source: ${step.source}`))
|
|
85
|
+
throw new Error(`Unknown route source: ${step.source}`)
|
|
86
|
+
}
|
|
87
|
+
}, [writeContractAsync])
|
|
88
|
+
|
|
89
|
+
const reset = useCallback(() => {
|
|
90
|
+
setError(null)
|
|
91
|
+
resetWrite()
|
|
92
|
+
}, [resetWrite])
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
swap,
|
|
96
|
+
isPending,
|
|
97
|
+
isConfirming,
|
|
98
|
+
isSuccess,
|
|
99
|
+
error,
|
|
100
|
+
txHash,
|
|
101
|
+
reset,
|
|
102
|
+
}
|
|
103
|
+
}
|