tempo.ts 0.12.1 → 0.13.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +28 -34
  3. package/package.json +2 -28
  4. package/dist/wagmi/Actions/amm.d.ts +0 -418
  5. package/dist/wagmi/Actions/amm.d.ts.map +0 -1
  6. package/dist/wagmi/Actions/amm.js +0 -462
  7. package/dist/wagmi/Actions/amm.js.map +0 -1
  8. package/dist/wagmi/Actions/dex.d.ts +0 -864
  9. package/dist/wagmi/Actions/dex.d.ts.map +0 -1
  10. package/dist/wagmi/Actions/dex.js +0 -968
  11. package/dist/wagmi/Actions/dex.js.map +0 -1
  12. package/dist/wagmi/Actions/faucet.d.ts +0 -66
  13. package/dist/wagmi/Actions/faucet.d.ts.map +0 -1
  14. package/dist/wagmi/Actions/faucet.js +0 -64
  15. package/dist/wagmi/Actions/faucet.js.map +0 -1
  16. package/dist/wagmi/Actions/fee.d.ts +0 -111
  17. package/dist/wagmi/Actions/fee.d.ts.map +0 -1
  18. package/dist/wagmi/Actions/fee.js +0 -126
  19. package/dist/wagmi/Actions/fee.js.map +0 -1
  20. package/dist/wagmi/Actions/index.d.ts +0 -9
  21. package/dist/wagmi/Actions/index.d.ts.map +0 -1
  22. package/dist/wagmi/Actions/index.js +0 -9
  23. package/dist/wagmi/Actions/index.js.map +0 -1
  24. package/dist/wagmi/Actions/nonce.d.ts +0 -77
  25. package/dist/wagmi/Actions/nonce.d.ts.map +0 -1
  26. package/dist/wagmi/Actions/nonce.js +0 -87
  27. package/dist/wagmi/Actions/nonce.js.map +0 -1
  28. package/dist/wagmi/Actions/policy.d.ts +0 -480
  29. package/dist/wagmi/Actions/policy.d.ts.map +0 -1
  30. package/dist/wagmi/Actions/policy.js +0 -530
  31. package/dist/wagmi/Actions/policy.js.map +0 -1
  32. package/dist/wagmi/Actions/reward.d.ts +0 -346
  33. package/dist/wagmi/Actions/reward.d.ts.map +0 -1
  34. package/dist/wagmi/Actions/reward.js +0 -382
  35. package/dist/wagmi/Actions/reward.js.map +0 -1
  36. package/dist/wagmi/Actions/token.d.ts +0 -1546
  37. package/dist/wagmi/Actions/token.d.ts.map +0 -1
  38. package/dist/wagmi/Actions/token.js +0 -1712
  39. package/dist/wagmi/Actions/token.js.map +0 -1
  40. package/dist/wagmi/Connector.d.ts +0 -91
  41. package/dist/wagmi/Connector.d.ts.map +0 -1
  42. package/dist/wagmi/Connector.js +0 -473
  43. package/dist/wagmi/Connector.js.map +0 -1
  44. package/dist/wagmi/Hooks/amm.d.ts +0 -411
  45. package/dist/wagmi/Hooks/amm.d.ts.map +0 -1
  46. package/dist/wagmi/Hooks/amm.js +0 -494
  47. package/dist/wagmi/Hooks/amm.js.map +0 -1
  48. package/dist/wagmi/Hooks/dex.d.ts +0 -773
  49. package/dist/wagmi/Hooks/dex.d.ts.map +0 -1
  50. package/dist/wagmi/Hooks/dex.js +0 -921
  51. package/dist/wagmi/Hooks/dex.js.map +0 -1
  52. package/dist/wagmi/Hooks/faucet.d.ts +0 -71
  53. package/dist/wagmi/Hooks/faucet.d.ts.map +0 -1
  54. package/dist/wagmi/Hooks/faucet.js +0 -76
  55. package/dist/wagmi/Hooks/faucet.js.map +0 -1
  56. package/dist/wagmi/Hooks/fee.d.ts +0 -97
  57. package/dist/wagmi/Hooks/fee.d.ts.map +0 -1
  58. package/dist/wagmi/Hooks/fee.js +0 -109
  59. package/dist/wagmi/Hooks/fee.js.map +0 -1
  60. package/dist/wagmi/Hooks/index.d.ts +0 -9
  61. package/dist/wagmi/Hooks/index.d.ts.map +0 -1
  62. package/dist/wagmi/Hooks/index.js +0 -9
  63. package/dist/wagmi/Hooks/index.js.map +0 -1
  64. package/dist/wagmi/Hooks/nonce.d.ts +0 -59
  65. package/dist/wagmi/Hooks/nonce.d.ts.map +0 -1
  66. package/dist/wagmi/Hooks/nonce.js +0 -75
  67. package/dist/wagmi/Hooks/nonce.js.map +0 -1
  68. package/dist/wagmi/Hooks/policy.d.ts +0 -423
  69. package/dist/wagmi/Hooks/policy.d.ts.map +0 -1
  70. package/dist/wagmi/Hooks/policy.js +0 -510
  71. package/dist/wagmi/Hooks/policy.js.map +0 -1
  72. package/dist/wagmi/Hooks/reward.d.ts +0 -305
  73. package/dist/wagmi/Hooks/reward.d.ts.map +0 -1
  74. package/dist/wagmi/Hooks/reward.js +0 -368
  75. package/dist/wagmi/Hooks/reward.js.map +0 -1
  76. package/dist/wagmi/Hooks/token.d.ts +0 -1388
  77. package/dist/wagmi/Hooks/token.d.ts.map +0 -1
  78. package/dist/wagmi/Hooks/token.js +0 -1657
  79. package/dist/wagmi/Hooks/token.js.map +0 -1
  80. package/dist/wagmi/KeyManager.d.ts +0 -60
  81. package/dist/wagmi/KeyManager.d.ts.map +0 -1
  82. package/dist/wagmi/KeyManager.js +0 -106
  83. package/dist/wagmi/KeyManager.js.map +0 -1
  84. package/dist/wagmi/index.d.ts +0 -5
  85. package/dist/wagmi/index.d.ts.map +0 -1
  86. package/dist/wagmi/index.js +0 -5
  87. package/dist/wagmi/index.js.map +0 -1
  88. package/src/wagmi/Actions/amm.test.ts +0 -208
  89. package/src/wagmi/Actions/amm.ts +0 -690
  90. package/src/wagmi/Actions/dex.test.ts +0 -1482
  91. package/src/wagmi/Actions/dex.ts +0 -1540
  92. package/src/wagmi/Actions/faucet.ts +0 -89
  93. package/src/wagmi/Actions/fee.test.ts +0 -63
  94. package/src/wagmi/Actions/fee.ts +0 -211
  95. package/src/wagmi/Actions/index.ts +0 -8
  96. package/src/wagmi/Actions/nonce.test.ts +0 -82
  97. package/src/wagmi/Actions/nonce.ts +0 -139
  98. package/src/wagmi/Actions/policy.test.ts +0 -461
  99. package/src/wagmi/Actions/policy.ts +0 -817
  100. package/src/wagmi/Actions/reward.test.ts +0 -216
  101. package/src/wagmi/Actions/reward.ts +0 -613
  102. package/src/wagmi/Actions/token.test.ts +0 -1309
  103. package/src/wagmi/Actions/token.ts +0 -2644
  104. package/src/wagmi/Connector.test.ts +0 -56
  105. package/src/wagmi/Connector.ts +0 -670
  106. package/src/wagmi/Hooks/amm.test.ts +0 -564
  107. package/src/wagmi/Hooks/amm.ts +0 -796
  108. package/src/wagmi/Hooks/dex.test.ts +0 -992
  109. package/src/wagmi/Hooks/dex.ts +0 -1598
  110. package/src/wagmi/Hooks/faucet.ts +0 -144
  111. package/src/wagmi/Hooks/fee.test.ts +0 -166
  112. package/src/wagmi/Hooks/fee.ts +0 -206
  113. package/src/wagmi/Hooks/index.ts +0 -8
  114. package/src/wagmi/Hooks/nonce.test.ts +0 -142
  115. package/src/wagmi/Hooks/nonce.ts +0 -117
  116. package/src/wagmi/Hooks/policy.test.ts +0 -665
  117. package/src/wagmi/Hooks/policy.ts +0 -873
  118. package/src/wagmi/Hooks/reward.test.ts +0 -249
  119. package/src/wagmi/Hooks/reward.ts +0 -645
  120. package/src/wagmi/Hooks/token.test.ts +0 -1183
  121. package/src/wagmi/Hooks/token.ts +0 -2906
  122. package/src/wagmi/KeyManager.ts +0 -172
  123. package/src/wagmi/index.ts +0 -7
@@ -1,992 +0,0 @@
1
- import { Actions, Hooks } from 'tempo.ts/wagmi'
2
- import { type Address, isAddress, parseUnits } from 'viem'
3
- import { Tick } from 'viem/tempo'
4
- import { describe, expect, test, vi } from 'vitest'
5
- import { useConnect } from 'wagmi'
6
- import { addresses } from '../../../test/config.js'
7
- import { accounts } from '../../../test/viem/config.js'
8
- import {
9
- config,
10
- renderHook,
11
- setupTokenPair,
12
- } from '../../../test/wagmi/config.js'
13
-
14
- const account = accounts[0]
15
- const account2 = accounts[1]
16
-
17
- describe('useBuy', () => {
18
- test('default', async () => {
19
- const { base, quote } = await setupTokenPair()
20
-
21
- const { result } = await renderHook(() => Hooks.dex.useBuySync())
22
-
23
- // Place ask order to create liquidity
24
- await Actions.dex.placeSync(config, {
25
- token: base,
26
- amount: parseUnits('500', 6),
27
- type: 'sell',
28
- tick: Tick.fromPrice('1.001'),
29
- })
30
-
31
- // Buy base tokens with quote tokens
32
- const { receipt } = await result.current.mutateAsync({
33
- tokenIn: quote,
34
- tokenOut: base,
35
- amountOut: parseUnits('100', 6),
36
- maxAmountIn: parseUnits('150', 6),
37
- })
38
-
39
- expect(receipt).toBeDefined()
40
- expect(receipt.status).toBe('success')
41
-
42
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
43
- })
44
-
45
- test('behavior: respects maxAmountIn', async () => {
46
- const { base, quote } = await setupTokenPair()
47
-
48
- const { result } = await renderHook(() => Hooks.dex.useBuySync())
49
-
50
- // Place ask order at high price
51
- await Actions.dex.placeSync(config, {
52
- token: base,
53
- amount: parseUnits('500', 6),
54
- type: 'sell',
55
- tick: Tick.fromPrice('1.01'), // 1% above peg
56
- })
57
-
58
- // Try to buy with insufficient maxAmountIn - should fail
59
- await expect(
60
- result.current.mutateAsync({
61
- tokenIn: quote,
62
- tokenOut: base,
63
- amountOut: parseUnits('100', 6),
64
- maxAmountIn: parseUnits('50', 6), // Way too low for 1% premium
65
- }),
66
- ).rejects.toThrow('The contract function "swapExactAmountOut" reverted')
67
- })
68
-
69
- test('behavior: fails with insufficient liquidity', async () => {
70
- const { base, quote } = await setupTokenPair()
71
-
72
- const { result } = await renderHook(() => Hooks.dex.useBuySync())
73
-
74
- // Don't place any orders - no liquidity
75
-
76
- // Try to buy - should fail due to no liquidity
77
- await expect(
78
- result.current.mutateAsync({
79
- tokenIn: quote,
80
- tokenOut: base,
81
- amountOut: parseUnits('100', 6),
82
- maxAmountIn: parseUnits('150', 6),
83
- }),
84
- ).rejects.toThrow('The contract function "swapExactAmountOut" reverted')
85
- })
86
- })
87
-
88
- describe('useCancel', () => {
89
- test('default', async () => {
90
- const { base, quote } = await setupTokenPair()
91
-
92
- const { result } = await renderHook(() => ({
93
- place: Hooks.dex.usePlaceSync(),
94
- cancel: Hooks.dex.useCancelSync(),
95
- }))
96
-
97
- // Place a bid order
98
- const { orderId } = await result.current.place.mutateAsync({
99
- token: base,
100
- amount: parseUnits('100', 6),
101
- type: 'buy',
102
- tick: Tick.fromPrice('1.001'),
103
- })
104
-
105
- // Check initial DEX balance (should be 0)
106
- const dexBalanceBefore = await Actions.dex.getBalance(config, {
107
- account: account.address,
108
- token: quote,
109
- })
110
- expect(dexBalanceBefore).toBe(0n)
111
-
112
- // Cancel the order
113
- const { receipt, orderId: returnedOrderId } =
114
- await result.current.cancel.mutateAsync({
115
- orderId,
116
- })
117
-
118
- expect(receipt).toBeDefined()
119
- expect(receipt.status).toBe('success')
120
- expect(returnedOrderId).toBe(orderId)
121
-
122
- await vi.waitFor(() => expect(result.current.cancel.isSuccess).toBeTruthy())
123
-
124
- // Check DEX balance after cancel - tokens should be refunded to internal balance
125
- const dexBalanceAfter = await Actions.dex.getBalance(config, {
126
- account: account.address,
127
- token: quote,
128
- })
129
- expect(dexBalanceAfter).toBeGreaterThan(0n)
130
- })
131
-
132
- test('behavior: only maker can cancel', async () => {
133
- const { base } = await setupTokenPair()
134
-
135
- const { result } = await renderHook(() => ({
136
- connect: useConnect(),
137
- place: Hooks.dex.usePlaceSync(),
138
- cancel: Hooks.dex.useCancelSync(),
139
- }))
140
-
141
- // Account places order
142
- const { orderId } = await result.current.place.mutateAsync({
143
- token: base,
144
- amount: parseUnits('100', 6),
145
- type: 'buy',
146
- tick: Tick.fromPrice('1.001'),
147
- })
148
-
149
- // Transfer gas to account2
150
- await Actions.token.transferSync(config, {
151
- to: account2.address,
152
- amount: parseUnits('1', 6),
153
- token: addresses.alphaUsd,
154
- })
155
-
156
- // Use a different account via the connector
157
- await result.current.connect.connectAsync({
158
- connector: config.connectors[1]!,
159
- })
160
-
161
- // Account2 tries to cancel - should fail
162
- await expect(
163
- result.current.cancel.mutateAsync({
164
- orderId,
165
- }),
166
- ).rejects.toThrow('The contract function "cancel" reverted')
167
- })
168
-
169
- test('behavior: cannot cancel non-existent order', async () => {
170
- await setupTokenPair()
171
-
172
- const { result } = await renderHook(() => Hooks.dex.useCancelSync())
173
-
174
- // Try to cancel an order that doesn't exist
175
- await expect(
176
- result.current.mutateAsync({
177
- orderId: 999n,
178
- }),
179
- ).rejects.toThrow('The contract function "cancel" reverted')
180
- })
181
- })
182
-
183
- describe('useCreatePair', () => {
184
- test('default', async () => {
185
- await setupTokenPair() // This ensures connection
186
-
187
- const { result } = await renderHook(() => Hooks.dex.useCreatePairSync())
188
-
189
- const { token: baseToken } = await Actions.token.createSync(config, {
190
- name: 'Test Base Token',
191
- symbol: 'BASE',
192
- currency: 'USD',
193
- admin: account,
194
- })
195
-
196
- const { receipt, ...resultData } = await result.current.mutateAsync({
197
- base: baseToken,
198
- })
199
-
200
- expect(receipt).toBeDefined()
201
- expect(receipt.status).toBe('success')
202
-
203
- const { key, ...rest } = resultData
204
- expect(key).toBeDefined()
205
- expect(rest).toEqual(
206
- expect.objectContaining({
207
- base: expect.toSatisfy(isAddress),
208
- quote: expect.toSatisfy(isAddress),
209
- }),
210
- )
211
-
212
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
213
- })
214
- })
215
-
216
- describe('useBalance', () => {
217
- test('default', async () => {
218
- const { base, quote } = await setupTokenPair()
219
-
220
- const { result, rerender } = await renderHook(
221
- (props) =>
222
- Hooks.dex.useBalance({ account: props?.account, token: quote }),
223
- { initialProps: { account: undefined as Address | undefined } },
224
- )
225
-
226
- await vi.waitFor(() => result.current.fetchStatus === 'fetching')
227
-
228
- // Verify initial state (disabled/pending when account missing)
229
- expect(result.current.status).toBe('pending')
230
-
231
- // Set account and rerender
232
- rerender({ account: accounts[0].address })
233
-
234
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
235
-
236
- // Initial balance should be 0
237
- expect(result.current.data).toBe(0n)
238
-
239
- // Place and cancel order to create internal balance
240
- const { orderId } = await Actions.dex.placeSync(config, {
241
- token: base,
242
- amount: parseUnits('50', 6),
243
- type: 'buy',
244
- tick: Tick.fromPrice('1.0005'),
245
- })
246
-
247
- await Actions.dex.cancelSync(config, {
248
- orderId,
249
- })
250
-
251
- // Trigger refetch and verify updated balance
252
- const { data } = await result.current.refetch()
253
- expect(data).toBeGreaterThan(0n)
254
- })
255
-
256
- test('behavior: check different account', async () => {
257
- const { quote } = await setupTokenPair()
258
-
259
- const { result } = await renderHook(() =>
260
- Hooks.dex.useBalance({ account: account2.address, token: quote }),
261
- )
262
-
263
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
264
-
265
- // Check account2's balance (should be 0)
266
- expect(result.current.data).toBe(0n)
267
- })
268
-
269
- test('behavior: balances are per-token', async () => {
270
- const { base, quote } = await setupTokenPair()
271
-
272
- // Create balance in quote token
273
- const { orderId } = await Actions.dex.placeSync(config, {
274
- token: base,
275
- amount: parseUnits('100', 6),
276
- type: 'buy',
277
- tick: Tick.fromPrice('1.001'),
278
- })
279
- await Actions.dex.cancelSync(config, { orderId })
280
-
281
- const { result } = await renderHook(() => ({
282
- quote: Hooks.dex.useBalance({ account: account.address, token: quote }),
283
- base: Hooks.dex.useBalance({ account: account.address, token: base }),
284
- }))
285
-
286
- await vi.waitUntil(
287
- () => result.current.base.isSuccess && result.current.quote.isSuccess,
288
- {
289
- timeout: 50_000,
290
- },
291
- )
292
-
293
- // Check quote balance (should have refunded tokens)
294
- expect(result.current.quote.data).toBeGreaterThan(0n)
295
- // Check base balance (should still be 0)
296
- expect(result.current.base.data).toBe(0n)
297
- })
298
- })
299
-
300
- describe('useBuyQuote', () => {
301
- test('default', async () => {
302
- const { base, quote } = await setupTokenPair()
303
-
304
- // Place ask orders to create liquidity
305
- await Actions.dex.placeSync(config, {
306
- token: base,
307
- amount: parseUnits('500', 6),
308
- type: 'sell',
309
- tick: Tick.fromPrice('1.001'),
310
- })
311
-
312
- const { result } = await renderHook(() =>
313
- Hooks.dex.useBuyQuote({
314
- tokenIn: quote,
315
- tokenOut: base,
316
- amountOut: parseUnits('100', 6),
317
- }),
318
- )
319
-
320
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
321
-
322
- expect(result.current.data).toBeGreaterThan(0n)
323
- // Should be approximately 100 * 1.001 = 100.1
324
- expect(result.current.data).toBeGreaterThan(parseUnits('100', 6))
325
- })
326
-
327
- test('behavior: fails with no liquidity', async () => {
328
- const { base, quote } = await setupTokenPair()
329
-
330
- // No orders placed - no liquidity
331
-
332
- const { result } = await renderHook(() =>
333
- Hooks.dex.useBuyQuote({
334
- tokenIn: quote,
335
- tokenOut: base,
336
- amountOut: parseUnits('100', 6),
337
- }),
338
- )
339
-
340
- await vi.waitUntil(() => result.current.isError, {
341
- timeout: 50_000,
342
- })
343
-
344
- expect(result.current.error?.message).toContain('InsufficientLiquidity')
345
- })
346
- })
347
-
348
- describe('useOrder', () => {
349
- test('default', async () => {
350
- const { base } = await setupTokenPair()
351
-
352
- // Place an order to get an order ID
353
- const { orderId } = await Actions.dex.placeSync(config, {
354
- token: base,
355
- amount: parseUnits('100', 6),
356
- type: 'buy',
357
- tick: Tick.fromPrice('1.001'),
358
- })
359
-
360
- const { result } = await renderHook(() =>
361
- Hooks.dex.useOrder({
362
- orderId,
363
- }),
364
- )
365
-
366
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
367
-
368
- const order = result.current.data!
369
- expect(order.maker).toBe(account.address)
370
- expect(order.isBid).toBe(true)
371
- expect(order.tick).toBe(Tick.fromPrice('1.001'))
372
- expect(order.amount).toBe(parseUnits('100', 6))
373
- expect(order.remaining).toBe(parseUnits('100', 6))
374
- expect(order.isFlip).toBe(false)
375
- })
376
-
377
- test('behavior: returns flip order details', async () => {
378
- const { base } = await setupTokenPair()
379
-
380
- // Place a flip order
381
- const { orderId } = await Actions.dex.placeFlipSync(config, {
382
- token: base,
383
- amount: parseUnits('50', 6),
384
- type: 'buy',
385
- tick: Tick.fromPrice('1.001'),
386
- flipTick: Tick.fromPrice('1.002'),
387
- })
388
-
389
- const { result } = await renderHook(() =>
390
- Hooks.dex.useOrder({
391
- orderId,
392
- }),
393
- )
394
-
395
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
396
-
397
- const order = result.current.data!
398
- expect(order.maker).toBe(account.address)
399
- expect(order.isBid).toBe(true)
400
- expect(order.tick).toBe(Tick.fromPrice('1.001'))
401
- expect(order.amount).toBe(parseUnits('50', 6))
402
- expect(order.isFlip).toBe(true)
403
- expect(order.flipTick).toBe(Tick.fromPrice('1.002'))
404
- })
405
-
406
- test('behavior: fails for non-existent order', async () => {
407
- await setupTokenPair()
408
-
409
- const { result } = await renderHook(() =>
410
- Hooks.dex.useOrder({
411
- orderId: 999n,
412
- }),
413
- )
414
-
415
- await vi.waitUntil(() => result.current.isError, {
416
- timeout: 50_000,
417
- })
418
-
419
- expect(result.current.error?.message).toContain('OrderDoesNotExist')
420
- })
421
- })
422
-
423
- describe('useOrderbook', () => {
424
- test('default', async () => {
425
- const { base, quote } = await setupTokenPair()
426
-
427
- const { result } = await renderHook(() =>
428
- Hooks.dex.useOrderbook({
429
- base,
430
- quote,
431
- }),
432
- )
433
-
434
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
435
-
436
- const book = result.current.data!
437
- expect(book.base).toBe(base)
438
- expect(book.quote).toBe(quote)
439
- expect(book.bestBidTick).toBeDefined()
440
- expect(book.bestAskTick).toBeDefined()
441
- })
442
-
443
- test('behavior: shows best bid and ask after orders placed', async () => {
444
- const { base, quote } = await setupTokenPair()
445
-
446
- const bidTick = Tick.fromPrice('0.999')
447
- const askTick = Tick.fromPrice('1.001')
448
-
449
- // Place a bid order
450
- await Actions.dex.placeSync(config, {
451
- token: base,
452
- amount: parseUnits('100', 6),
453
- type: 'buy',
454
- tick: bidTick,
455
- })
456
-
457
- // Place an ask order
458
- await Actions.dex.placeSync(config, {
459
- token: base,
460
- amount: parseUnits('100', 6),
461
- type: 'sell',
462
- tick: askTick,
463
- })
464
-
465
- const { result } = await renderHook(() =>
466
- Hooks.dex.useOrderbook({
467
- base,
468
- quote,
469
- }),
470
- )
471
-
472
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
473
-
474
- const book = result.current.data!
475
- expect(book.bestBidTick).toBe(bidTick)
476
- expect(book.bestAskTick).toBe(askTick)
477
- })
478
- })
479
-
480
- describe('usePriceLevel', () => {
481
- test('default', async () => {
482
- const { base } = await setupTokenPair()
483
-
484
- const tick = Tick.fromPrice('1.001')
485
-
486
- // Place an order to create liquidity at this tick
487
- const { orderId } = await Actions.dex.placeSync(config, {
488
- token: base,
489
- amount: parseUnits('100', 6),
490
- type: 'buy',
491
- tick,
492
- })
493
-
494
- const { result } = await renderHook(() =>
495
- Hooks.dex.usePriceLevel({
496
- base,
497
- tick,
498
- isBid: true,
499
- }),
500
- )
501
-
502
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
503
-
504
- const level = result.current.data!
505
- expect(level.head).toBe(orderId) // head should be our order
506
- expect(level.tail).toBe(orderId) // tail should also be our order (only one)
507
- expect(level.totalLiquidity).toBeGreaterThan(0n)
508
- })
509
-
510
- test('behavior: empty price level', async () => {
511
- const { base } = await setupTokenPair()
512
-
513
- const tick = Tick.fromPrice('1.001')
514
-
515
- // Query a tick with no orders
516
- const { result } = await renderHook(() =>
517
- Hooks.dex.usePriceLevel({
518
- base,
519
- tick,
520
- isBid: true,
521
- }),
522
- )
523
-
524
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
525
-
526
- const level = result.current.data!
527
- expect(level.head).toBe(0n)
528
- expect(level.tail).toBe(0n)
529
- expect(level.totalLiquidity).toBe(0n)
530
- })
531
- })
532
-
533
- describe('useSellQuote', () => {
534
- test('default', async () => {
535
- const { base, quote } = await setupTokenPair()
536
-
537
- // Place bid orders to create liquidity
538
- await Actions.dex.placeSync(config, {
539
- token: base,
540
- amount: parseUnits('500', 6),
541
- type: 'buy',
542
- tick: Tick.fromPrice('0.999'),
543
- })
544
-
545
- const { result } = await renderHook(() =>
546
- Hooks.dex.useSellQuote({
547
- tokenIn: base,
548
- tokenOut: quote,
549
- amountIn: parseUnits('100', 6),
550
- }),
551
- )
552
-
553
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
554
-
555
- expect(result.current.data).toBeGreaterThan(0n)
556
- // Should be approximately 100 * 0.999 = 99.9
557
- expect(result.current.data).toBeLessThan(parseUnits('100', 6))
558
- })
559
-
560
- test('behavior: fails with no liquidity', async () => {
561
- const { base, quote } = await setupTokenPair()
562
-
563
- // Quote should fail with no liquidity
564
- const { result } = await renderHook(() =>
565
- Hooks.dex.useSellQuote({
566
- tokenIn: base,
567
- tokenOut: quote,
568
- amountIn: parseUnits('100', 6),
569
- }),
570
- )
571
-
572
- await vi.waitUntil(() => result.current.isError, {
573
- timeout: 50_000,
574
- })
575
-
576
- expect(result.current.error?.message).toContain('InsufficientLiquidity')
577
- })
578
- })
579
-
580
- describe('usePlace', () => {
581
- test('default', async () => {
582
- const { base } = await setupTokenPair()
583
-
584
- const { result } = await renderHook(() => Hooks.dex.usePlaceSync())
585
-
586
- // Place a sell order
587
- const { receipt, orderId, token, ...resultData } =
588
- await result.current.mutateAsync({
589
- token: base,
590
- amount: parseUnits('100', 6),
591
- type: 'sell',
592
- tick: Tick.fromPrice('1.001'),
593
- })
594
-
595
- expect(receipt).toBeDefined()
596
- expect(receipt.status).toBe('success')
597
- expect(orderId).toBeGreaterThan(0n)
598
- expect(token).toBe(base)
599
- expect(resultData).toMatchInlineSnapshot(`
600
- {
601
- "amount": 100000000n,
602
- "isBid": false,
603
- "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
604
- "tick": 100,
605
- }
606
- `)
607
-
608
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
609
-
610
- // Place a buy order
611
- const {
612
- receipt: receipt2,
613
- orderId: orderId2,
614
- token: token2,
615
- ...result2
616
- } = await result.current.mutateAsync({
617
- token: base,
618
- amount: parseUnits('100', 6),
619
- type: 'buy',
620
- tick: Tick.fromPrice('1.001'),
621
- })
622
- expect(receipt2.status).toBe('success')
623
- expect(orderId2).toBeGreaterThan(0n)
624
- expect(token2).toBe(base)
625
- expect(result2).toMatchInlineSnapshot(`
626
- {
627
- "amount": 100000000n,
628
- "isBid": true,
629
- "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
630
- "tick": 100,
631
- }
632
- `)
633
- })
634
- })
635
-
636
- describe('usePlaceFlip', () => {
637
- test('default', async () => {
638
- const { base } = await setupTokenPair()
639
-
640
- const { result } = await renderHook(() => Hooks.dex.usePlaceFlipSync())
641
-
642
- // Place a flip bid order
643
- const { receipt, orderId, token, ...resultData } =
644
- await result.current.mutateAsync({
645
- token: base,
646
- amount: parseUnits('100', 6),
647
- type: 'buy',
648
- tick: Tick.fromPrice('1.001'),
649
- flipTick: Tick.fromPrice('1.002'),
650
- })
651
-
652
- expect(receipt).toBeDefined()
653
- expect(receipt.status).toBe('success')
654
- expect(orderId).toBeGreaterThan(0n)
655
- expect(token).toBe(base)
656
- expect(resultData.flipTick).toBe(Tick.fromPrice('1.002'))
657
- expect(resultData).toMatchInlineSnapshot(`
658
- {
659
- "amount": 100000000n,
660
- "flipTick": 200,
661
- "isBid": true,
662
- "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
663
- "tick": 100,
664
- }
665
- `)
666
-
667
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
668
- })
669
- })
670
-
671
- describe('useSell', () => {
672
- test('default', async () => {
673
- const { base, quote } = await setupTokenPair()
674
-
675
- const { result } = await renderHook(() => Hooks.dex.useSellSync())
676
-
677
- // Place bid order to create liquidity
678
- await Actions.dex.placeSync(config, {
679
- token: base,
680
- amount: parseUnits('500', 6),
681
- type: 'buy',
682
- tick: Tick.fromPrice('0.999'),
683
- })
684
-
685
- // Sell base tokens
686
- const { receipt } = await result.current.mutateAsync({
687
- tokenIn: base,
688
- tokenOut: quote,
689
- amountIn: parseUnits('100', 6),
690
- minAmountOut: parseUnits('50', 6),
691
- })
692
-
693
- expect(receipt).toBeDefined()
694
- expect(receipt.status).toBe('success')
695
-
696
- await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
697
- })
698
-
699
- test('behavior: respects minAmountOut', async () => {
700
- const { base, quote } = await setupTokenPair()
701
-
702
- const { result } = await renderHook(() => Hooks.dex.useSellSync())
703
-
704
- // Place bid order at low price
705
- await Actions.dex.placeSync(config, {
706
- token: base,
707
- amount: parseUnits('500', 6),
708
- type: 'buy',
709
- tick: Tick.fromPrice('0.99'), // 1% below peg
710
- })
711
-
712
- // Try to sell with too high minAmountOut - should fail
713
- await expect(
714
- result.current.mutateAsync({
715
- tokenIn: base,
716
- tokenOut: quote,
717
- amountIn: parseUnits('100', 6),
718
- minAmountOut: parseUnits('150', 6), // Way too high
719
- }),
720
- ).rejects.toThrow('The contract function "swapExactAmountIn" reverted')
721
- })
722
-
723
- test('behavior: fails with insufficient liquidity', async () => {
724
- const { base, quote } = await setupTokenPair()
725
-
726
- const { result } = await renderHook(() => Hooks.dex.useSellSync())
727
-
728
- // No orders - no liquidity
729
-
730
- // Try to sell - should fail
731
- await expect(
732
- result.current.mutateAsync({
733
- tokenIn: base,
734
- tokenOut: quote,
735
- amountIn: parseUnits('100', 6),
736
- minAmountOut: parseUnits('50', 6),
737
- }),
738
- ).rejects.toThrow('The contract function "swapExactAmountIn" reverted')
739
- })
740
- })
741
-
742
- describe('useWatchFlipOrderPlaced', () => {
743
- test('default', async () => {
744
- const { base } = await setupTokenPair()
745
-
746
- const { result } = await renderHook(() => Hooks.dex.usePlaceFlipSync())
747
-
748
- const events: any[] = []
749
- await renderHook(() =>
750
- Hooks.dex.useWatchFlipOrderPlaced({
751
- onFlipOrderPlaced(args) {
752
- events.push(args)
753
- },
754
- }),
755
- )
756
-
757
- // Place flip order to trigger event
758
- await result.current.mutateAsync({
759
- token: base,
760
- amount: parseUnits('100', 6),
761
- type: 'buy',
762
- tick: Tick.fromPrice('1.001'),
763
- flipTick: Tick.fromPrice('1.002'),
764
- })
765
-
766
- await vi.waitUntil(() => events.length >= 1)
767
-
768
- expect(events.length).toBeGreaterThanOrEqual(1)
769
- expect(events[0]?.flipTick).toBe(Tick.fromPrice('1.002'))
770
- expect(events[0]?.tick).toBe(Tick.fromPrice('1.001'))
771
- expect(events[0]?.isBid).toBe(true)
772
- expect(events[0]?.amount).toBe(parseUnits('100', 6))
773
- })
774
- })
775
-
776
- describe('useWatchOrderCancelled', () => {
777
- test('default', async () => {
778
- const { base } = await setupTokenPair()
779
-
780
- // Place order first
781
- const { orderId } = await Actions.dex.placeSync(config, {
782
- token: base,
783
- amount: parseUnits('100', 6),
784
- type: 'buy',
785
- tick: Tick.fromPrice('1.001'),
786
- })
787
-
788
- const events: any[] = []
789
- await renderHook(() =>
790
- Hooks.dex.useWatchOrderCancelled({
791
- onOrderCancelled(args) {
792
- events.push(args)
793
- },
794
- }),
795
- )
796
-
797
- // Cancel order to trigger event
798
- await Actions.dex.cancelSync(config, {
799
- orderId,
800
- })
801
-
802
- await vi.waitUntil(() => events.length >= 1)
803
-
804
- expect(events.length).toBeGreaterThanOrEqual(1)
805
- expect(events[0]?.orderId).toBe(orderId)
806
- })
807
-
808
- test('behavior: filter by orderId', async () => {
809
- const { base } = await setupTokenPair()
810
-
811
- // Place two orders
812
- const { orderId: orderId1 } = await Actions.dex.placeSync(config, {
813
- token: base,
814
- amount: parseUnits('100', 6),
815
- type: 'buy',
816
- tick: Tick.fromPrice('1.001'),
817
- })
818
-
819
- const { orderId: orderId2 } = await Actions.dex.placeSync(config, {
820
- token: base,
821
- amount: parseUnits('50', 6),
822
- type: 'buy',
823
- tick: Tick.fromPrice('1.001'),
824
- })
825
-
826
- const events: any[] = []
827
- await renderHook(() =>
828
- Hooks.dex.useWatchOrderCancelled({
829
- orderId: orderId1, // Filter for only orderId1
830
- onOrderCancelled(args) {
831
- events.push(args)
832
- },
833
- }),
834
- )
835
-
836
- // Cancel orderId1 (should be captured)
837
- await Actions.dex.cancelSync(config, {
838
- orderId: orderId1,
839
- })
840
-
841
- // Cancel orderId2 (should NOT be captured due to filter)
842
- await Actions.dex.cancelSync(config, {
843
- orderId: orderId2,
844
- })
845
-
846
- await vi.waitUntil(() => events.length >= 1, { timeout: 2000 })
847
-
848
- // Should only receive 1 event (for orderId1)
849
- expect(events.length).toBe(1)
850
- expect(events[0]?.orderId).toBe(orderId1)
851
- })
852
- })
853
-
854
- describe.todo('useWatchOrderFilled')
855
-
856
- describe('useWatchOrderPlaced', () => {
857
- test('default', async () => {
858
- const { base } = await setupTokenPair()
859
-
860
- const events: any[] = []
861
- await renderHook(() =>
862
- Hooks.dex.useWatchOrderPlaced({
863
- onOrderPlaced(args) {
864
- events.push(args)
865
- },
866
- }),
867
- )
868
-
869
- // Place first order
870
- await Actions.dex.placeSync(config, {
871
- token: base,
872
- amount: parseUnits('100', 6),
873
- type: 'buy',
874
- tick: Tick.fromPrice('1.001'),
875
- })
876
-
877
- // Place second order
878
- await Actions.dex.placeSync(config, {
879
- token: base,
880
- amount: parseUnits('50', 6),
881
- type: 'sell',
882
- tick: Tick.fromPrice('0.999'),
883
- })
884
-
885
- await vi.waitUntil(() => events.length >= 2)
886
-
887
- expect(events.length).toBeGreaterThanOrEqual(2)
888
- expect(events[0]?.isBid).toBe(true)
889
- expect(events[0]?.amount).toBe(parseUnits('100', 6))
890
- expect(events[1]?.isBid).toBe(false)
891
- expect(events[1]?.amount).toBe(parseUnits('50', 6))
892
- })
893
-
894
- test('behavior: filter by token', async () => {
895
- const { base } = await setupTokenPair()
896
- const { base: base2 } = await setupTokenPair()
897
-
898
- const events: any[] = []
899
- await renderHook(() =>
900
- Hooks.dex.useWatchOrderPlaced({
901
- token: base, // Filter for only base token
902
- onOrderPlaced(args) {
903
- events.push(args)
904
- },
905
- }),
906
- )
907
-
908
- // Place order on base (should be captured)
909
- await Actions.dex.placeSync(config, {
910
- token: base,
911
- amount: parseUnits('100', 6),
912
- type: 'buy',
913
- tick: Tick.fromPrice('1.001'),
914
- })
915
-
916
- // Place order on base2 (should NOT be captured due to filter)
917
- await Actions.dex.placeSync(config, {
918
- token: base2,
919
- amount: parseUnits('50', 6),
920
- type: 'buy',
921
- tick: Tick.fromPrice('1.001'),
922
- })
923
-
924
- await vi.waitUntil(() => events.length >= 1, { timeout: 2000 })
925
-
926
- // Should only receive 1 event (for base token)
927
- expect(events.length).toBe(1)
928
- expect(events[0]?.token.toLowerCase()).toBe(base.toLowerCase())
929
- })
930
- })
931
-
932
- describe('useWithdraw', () => {
933
- test('default', async () => {
934
- const { base, quote } = await setupTokenPair()
935
-
936
- const { result } = await renderHook(() => ({
937
- place: Hooks.dex.usePlaceSync(),
938
- cancel: Hooks.dex.useCancelSync(),
939
- withdraw: Hooks.dex.useWithdrawSync(),
940
- }))
941
-
942
- // Create internal balance
943
- const { orderId } = await result.current.place.mutateAsync({
944
- token: base,
945
- amount: parseUnits('100', 6),
946
- type: 'buy',
947
- tick: Tick.fromPrice('1.001'),
948
- })
949
-
950
- await result.current.cancel.mutateAsync({ orderId })
951
-
952
- // Get DEX balance
953
- const dexBalance = await Actions.dex.getBalance(config, {
954
- account: account.address,
955
- token: quote,
956
- })
957
- expect(dexBalance).toBeGreaterThan(0n)
958
-
959
- // Get wallet balance before withdraw
960
- const walletBalanceBefore = await Actions.token.getBalance(config, {
961
- token: quote,
962
- account: account.address,
963
- })
964
-
965
- // Withdraw from DEX
966
- const { receipt } = await result.current.withdraw.mutateAsync({
967
- token: quote,
968
- amount: dexBalance,
969
- })
970
-
971
- expect(receipt).toBeDefined()
972
- expect(receipt.status).toBe('success')
973
-
974
- await vi.waitFor(() =>
975
- expect(result.current.withdraw.isSuccess).toBeTruthy(),
976
- )
977
-
978
- // Check DEX balance is now 0
979
- const dexBalanceAfter = await Actions.dex.getBalance(config, {
980
- account: account.address,
981
- token: quote,
982
- })
983
- expect(dexBalanceAfter).toBe(0n)
984
-
985
- // Check wallet balance increased
986
- const walletBalanceAfter = await Actions.token.getBalance(config, {
987
- token: quote,
988
- account: account.address,
989
- })
990
- expect(walletBalanceAfter).toBeGreaterThan(walletBalanceBefore)
991
- })
992
- })