tempo.ts 0.1.5 → 0.2.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 (276) hide show
  1. package/README.md +33 -2
  2. package/dist/chains.d.ts +509 -115
  3. package/dist/chains.d.ts.map +1 -1
  4. package/dist/chains.js +18 -9
  5. package/dist/chains.js.map +1 -1
  6. package/dist/ox/Order.d.ts +92 -0
  7. package/dist/ox/Order.d.ts.map +1 -0
  8. package/dist/ox/Order.js +88 -0
  9. package/dist/ox/Order.js.map +1 -0
  10. package/dist/ox/OrdersFilters.d.ts +72 -0
  11. package/dist/ox/OrdersFilters.d.ts.map +1 -0
  12. package/dist/ox/OrdersFilters.js +100 -0
  13. package/dist/ox/OrdersFilters.js.map +1 -0
  14. package/dist/ox/Pagination.d.ts +128 -0
  15. package/dist/ox/Pagination.d.ts.map +1 -0
  16. package/dist/ox/Pagination.js +78 -0
  17. package/dist/ox/Pagination.js.map +1 -0
  18. package/dist/ox/PoolId.d.ts +18 -0
  19. package/dist/ox/PoolId.d.ts.map +1 -0
  20. package/dist/ox/PoolId.js +13 -0
  21. package/dist/ox/PoolId.js.map +1 -0
  22. package/dist/ox/RpcSchema.d.ts +32 -0
  23. package/dist/ox/RpcSchema.d.ts.map +1 -0
  24. package/dist/ox/RpcSchema.js +2 -0
  25. package/dist/ox/RpcSchema.js.map +1 -0
  26. package/dist/ox/SignatureEnvelope.d.ts +1 -1
  27. package/dist/ox/SignatureEnvelope.d.ts.map +1 -1
  28. package/dist/ox/SignatureEnvelope.js.map +1 -1
  29. package/dist/{viem → ox}/Tick.d.ts +4 -0
  30. package/dist/ox/Tick.d.ts.map +1 -0
  31. package/dist/ox/Tick.js.map +1 -0
  32. package/dist/ox/Transaction.d.ts.map +1 -1
  33. package/dist/ox/Transaction.js +2 -1
  34. package/dist/ox/Transaction.js.map +1 -1
  35. package/dist/ox/TransactionEnvelopeAA.d.ts +6 -6
  36. package/dist/ox/TransactionEnvelopeAA.d.ts.map +1 -1
  37. package/dist/ox/TransactionEnvelopeAA.js +4 -2
  38. package/dist/ox/TransactionEnvelopeAA.js.map +1 -1
  39. package/dist/ox/TransactionRequest.d.ts +4 -0
  40. package/dist/ox/TransactionRequest.d.ts.map +1 -1
  41. package/dist/ox/TransactionRequest.js.map +1 -1
  42. package/dist/ox/index.d.ts +6 -0
  43. package/dist/ox/index.d.ts.map +1 -1
  44. package/dist/ox/index.js +6 -0
  45. package/dist/ox/index.js.map +1 -1
  46. package/dist/prool/Instance.d.ts.map +1 -1
  47. package/dist/prool/Instance.js +20 -4
  48. package/dist/prool/Instance.js.map +1 -1
  49. package/dist/viem/Abis.d.ts +1469 -1082
  50. package/dist/viem/Abis.d.ts.map +1 -1
  51. package/dist/viem/Abis.js +932 -671
  52. package/dist/viem/Abis.js.map +1 -1
  53. package/dist/viem/Account.d.ts +150 -0
  54. package/dist/viem/Account.d.ts.map +1 -0
  55. package/dist/viem/Account.js +221 -0
  56. package/dist/viem/Account.js.map +1 -0
  57. package/dist/viem/Actions/amm.d.ts +80 -118
  58. package/dist/viem/Actions/amm.d.ts.map +1 -1
  59. package/dist/viem/Actions/amm.js +47 -116
  60. package/dist/viem/Actions/amm.js.map +1 -1
  61. package/dist/viem/Actions/dex.d.ts +889 -633
  62. package/dist/viem/Actions/dex.d.ts.map +1 -1
  63. package/dist/viem/Actions/dex.js +99 -0
  64. package/dist/viem/Actions/dex.js.map +1 -1
  65. package/dist/viem/Actions/fee.d.ts +3 -17
  66. package/dist/viem/Actions/fee.d.ts.map +1 -1
  67. package/dist/viem/Actions/fee.js.map +1 -1
  68. package/dist/viem/Actions/index.d.ts +1 -0
  69. package/dist/viem/Actions/index.d.ts.map +1 -1
  70. package/dist/viem/Actions/index.js +1 -0
  71. package/dist/viem/Actions/index.js.map +1 -1
  72. package/dist/viem/Actions/reward.d.ts +3236 -0
  73. package/dist/viem/Actions/reward.d.ts.map +1 -0
  74. package/dist/viem/Actions/reward.js +725 -0
  75. package/dist/viem/Actions/reward.js.map +1 -0
  76. package/dist/viem/Actions/token.d.ts +4295 -2646
  77. package/dist/viem/Actions/token.d.ts.map +1 -1
  78. package/dist/viem/Actions/token.js +214 -335
  79. package/dist/viem/Actions/token.js.map +1 -1
  80. package/dist/viem/Addresses.d.ts +1 -2
  81. package/dist/viem/Addresses.d.ts.map +1 -1
  82. package/dist/viem/Addresses.js +1 -2
  83. package/dist/viem/Addresses.js.map +1 -1
  84. package/dist/viem/Chain.d.ts +38 -12
  85. package/dist/viem/Chain.d.ts.map +1 -1
  86. package/dist/viem/Chain.js +27 -18
  87. package/dist/viem/Chain.js.map +1 -1
  88. package/dist/viem/Decorator.d.ts +959 -405
  89. package/dist/viem/Decorator.d.ts.map +1 -1
  90. package/dist/viem/Decorator.js +13 -5
  91. package/dist/viem/Decorator.js.map +1 -1
  92. package/dist/viem/Formatters.d.ts +8 -1
  93. package/dist/viem/Formatters.d.ts.map +1 -1
  94. package/dist/viem/Formatters.js +17 -0
  95. package/dist/viem/Formatters.js.map +1 -1
  96. package/dist/viem/P256.d.ts +2 -0
  97. package/dist/viem/P256.d.ts.map +1 -0
  98. package/dist/viem/P256.js +2 -0
  99. package/dist/viem/P256.js.map +1 -0
  100. package/dist/viem/Secp256k1.d.ts +2 -0
  101. package/dist/viem/Secp256k1.d.ts.map +1 -0
  102. package/dist/viem/Secp256k1.js +2 -0
  103. package/dist/viem/Secp256k1.js.map +1 -0
  104. package/dist/viem/TokenIds.d.ts +1 -2
  105. package/dist/viem/TokenIds.d.ts.map +1 -1
  106. package/dist/viem/TokenIds.js +1 -2
  107. package/dist/viem/TokenIds.js.map +1 -1
  108. package/dist/viem/Transaction.d.ts +1 -1
  109. package/dist/viem/Transaction.d.ts.map +1 -1
  110. package/dist/viem/Transaction.js +46 -5
  111. package/dist/viem/Transaction.js.map +1 -1
  112. package/dist/viem/WebAuthnP256.d.ts +79 -0
  113. package/dist/viem/WebAuthnP256.d.ts.map +1 -0
  114. package/dist/viem/WebAuthnP256.js +95 -0
  115. package/dist/viem/WebAuthnP256.js.map +1 -0
  116. package/dist/viem/WebCryptoP256.d.ts +2 -0
  117. package/dist/viem/WebCryptoP256.d.ts.map +1 -0
  118. package/dist/viem/WebCryptoP256.js +2 -0
  119. package/dist/viem/WebCryptoP256.js.map +1 -0
  120. package/dist/viem/index.d.ts +6 -3
  121. package/dist/viem/index.d.ts.map +1 -1
  122. package/dist/viem/index.js +6 -3
  123. package/dist/viem/index.js.map +1 -1
  124. package/dist/viem/internal/account.d.ts +24 -0
  125. package/dist/viem/internal/account.d.ts.map +1 -0
  126. package/dist/viem/internal/account.js +68 -0
  127. package/dist/viem/internal/account.js.map +1 -0
  128. package/dist/viem/internal/types.d.ts +10 -0
  129. package/dist/viem/internal/types.d.ts.map +1 -1
  130. package/dist/wagmi/Actions/amm.d.ts +428 -0
  131. package/dist/wagmi/Actions/amm.d.ts.map +1 -0
  132. package/dist/wagmi/Actions/amm.js +472 -0
  133. package/dist/wagmi/Actions/amm.js.map +1 -0
  134. package/dist/wagmi/Actions/dex.d.ts +908 -0
  135. package/dist/wagmi/Actions/dex.d.ts.map +1 -0
  136. package/dist/wagmi/Actions/dex.js +1023 -0
  137. package/dist/wagmi/Actions/dex.js.map +1 -0
  138. package/dist/wagmi/Actions/fee.d.ts +111 -0
  139. package/dist/wagmi/Actions/fee.d.ts.map +1 -0
  140. package/dist/wagmi/Actions/fee.js +126 -0
  141. package/dist/wagmi/Actions/fee.js.map +1 -0
  142. package/dist/wagmi/Actions/index.d.ts +6 -0
  143. package/dist/wagmi/Actions/index.d.ts.map +1 -0
  144. package/dist/wagmi/Actions/index.js +6 -0
  145. package/dist/wagmi/Actions/index.js.map +1 -0
  146. package/dist/wagmi/Actions/reward.d.ts +348 -0
  147. package/dist/wagmi/Actions/reward.d.ts.map +1 -0
  148. package/dist/wagmi/Actions/reward.js +388 -0
  149. package/dist/wagmi/Actions/reward.js.map +1 -0
  150. package/dist/wagmi/Actions/token.d.ts +1546 -0
  151. package/dist/wagmi/Actions/token.d.ts.map +1 -0
  152. package/dist/wagmi/Actions/token.js +1712 -0
  153. package/dist/wagmi/Actions/token.js.map +1 -0
  154. package/dist/wagmi/Connector.d.ts +73 -0
  155. package/dist/wagmi/Connector.d.ts.map +1 -0
  156. package/dist/wagmi/Connector.js +249 -0
  157. package/dist/wagmi/Connector.js.map +1 -0
  158. package/dist/wagmi/Hooks/amm.d.ts +421 -0
  159. package/dist/wagmi/Hooks/amm.d.ts.map +1 -0
  160. package/dist/wagmi/Hooks/amm.js +504 -0
  161. package/dist/wagmi/Hooks/amm.js.map +1 -0
  162. package/dist/wagmi/Hooks/dex.d.ts +816 -0
  163. package/dist/wagmi/Hooks/dex.d.ts.map +1 -0
  164. package/dist/wagmi/Hooks/dex.js +973 -0
  165. package/dist/wagmi/Hooks/dex.js.map +1 -0
  166. package/dist/wagmi/Hooks/fee.d.ts +97 -0
  167. package/dist/wagmi/Hooks/fee.d.ts.map +1 -0
  168. package/dist/wagmi/Hooks/fee.js +109 -0
  169. package/dist/wagmi/Hooks/fee.js.map +1 -0
  170. package/dist/wagmi/Hooks/index.d.ts +6 -0
  171. package/dist/wagmi/Hooks/index.d.ts.map +1 -0
  172. package/dist/wagmi/Hooks/index.js +6 -0
  173. package/dist/wagmi/Hooks/index.js.map +1 -0
  174. package/dist/wagmi/Hooks/reward.d.ts +307 -0
  175. package/dist/wagmi/Hooks/reward.d.ts.map +1 -0
  176. package/dist/wagmi/Hooks/reward.js +349 -0
  177. package/dist/wagmi/Hooks/reward.js.map +1 -0
  178. package/dist/wagmi/Hooks/token.d.ts +1388 -0
  179. package/dist/wagmi/Hooks/token.d.ts.map +1 -0
  180. package/dist/wagmi/Hooks/token.js +1657 -0
  181. package/dist/wagmi/Hooks/token.js.map +1 -0
  182. package/dist/wagmi/index.d.ts +4 -0
  183. package/dist/wagmi/index.d.ts.map +1 -0
  184. package/dist/wagmi/index.js +4 -0
  185. package/dist/wagmi/index.js.map +1 -0
  186. package/package.json +54 -10
  187. package/src/chains.ts +19 -9
  188. package/src/ox/Order.test.ts +78 -0
  189. package/src/ox/Order.ts +125 -0
  190. package/src/ox/OrdersFilters.test.ts +182 -0
  191. package/src/ox/OrdersFilters.ts +125 -0
  192. package/src/ox/Pagination.test.ts +162 -0
  193. package/src/ox/Pagination.ts +164 -0
  194. package/src/ox/PoolId.test.ts +33 -0
  195. package/src/ox/PoolId.ts +27 -0
  196. package/src/ox/RpcSchema.ts +35 -0
  197. package/src/ox/SignatureEnvelope.ts +3 -1
  198. package/src/{viem → ox}/Tick.test.ts +1 -1
  199. package/src/{viem → ox}/Tick.ts +5 -0
  200. package/src/ox/Transaction.test.ts +1 -1
  201. package/src/ox/Transaction.ts +2 -1
  202. package/src/ox/TransactionEnvelopeAA.test.ts +239 -96
  203. package/src/ox/TransactionEnvelopeAA.ts +9 -7
  204. package/src/ox/TransactionRequest.ts +4 -0
  205. package/src/ox/index.ts +6 -0
  206. package/src/prool/Instance.ts +51 -37
  207. package/src/prool/internal/chain.json +104 -52
  208. package/src/tsconfig.json +9 -0
  209. package/src/viem/Abis.ts +972 -710
  210. package/src/viem/Account.ts +279 -0
  211. package/src/viem/Actions/__snapshots__/dex.test.ts.snap +850 -0
  212. package/src/viem/Actions/amm.test.ts +98 -169
  213. package/src/viem/Actions/amm.ts +68 -155
  214. package/src/viem/Actions/dex.test.ts +563 -484
  215. package/src/viem/Actions/dex.ts +173 -0
  216. package/src/viem/Actions/fee.test.ts +23 -34
  217. package/src/viem/Actions/fee.ts +7 -0
  218. package/src/viem/Actions/index.ts +1 -0
  219. package/src/viem/Actions/policy.test.ts +19 -33
  220. package/src/viem/Actions/reward.test.ts +457 -0
  221. package/src/viem/Actions/reward.ts +999 -0
  222. package/src/viem/Actions/token.test.ts +453 -287
  223. package/src/viem/Actions/token.ts +452 -540
  224. package/src/viem/Addresses.ts +1 -2
  225. package/src/viem/Chain.ts +70 -20
  226. package/src/viem/Decorator.test.ts +2 -1
  227. package/src/viem/Decorator.ts +996 -421
  228. package/src/viem/Formatters.ts +31 -5
  229. package/src/viem/P256.ts +1 -0
  230. package/src/viem/Secp256k1.ts +1 -0
  231. package/src/viem/TokenIds.ts +1 -2
  232. package/src/viem/Transaction.ts +53 -7
  233. package/src/viem/WebAuthnP256.ts +140 -0
  234. package/src/viem/WebCryptoP256.ts +1 -0
  235. package/src/viem/e2e.test.ts +1126 -297
  236. package/src/viem/index.ts +6 -3
  237. package/src/viem/internal/account.ts +107 -0
  238. package/src/viem/internal/types.ts +9 -0
  239. package/src/wagmi/Actions/__snapshots__/dex.test.ts.snap +310 -0
  240. package/src/wagmi/Actions/amm.test.ts +198 -0
  241. package/src/wagmi/Actions/amm.ts +691 -0
  242. package/src/wagmi/Actions/dex.test.ts +1507 -0
  243. package/src/wagmi/Actions/dex.ts +1640 -0
  244. package/src/wagmi/Actions/fee.test.ts +63 -0
  245. package/src/wagmi/Actions/fee.ts +208 -0
  246. package/src/wagmi/Actions/index.ts +5 -0
  247. package/src/wagmi/Actions/reward.test.ts +210 -0
  248. package/src/wagmi/Actions/reward.ts +632 -0
  249. package/src/wagmi/Actions/token.test.ts +1308 -0
  250. package/src/wagmi/Actions/token.ts +2613 -0
  251. package/src/wagmi/Connector.test.ts +53 -0
  252. package/src/wagmi/Connector.ts +367 -0
  253. package/src/wagmi/Hooks/__snapshots__/dex.test.ts.snap +457 -0
  254. package/src/wagmi/Hooks/amm.test.ts +424 -0
  255. package/src/wagmi/Hooks/amm.ts +806 -0
  256. package/src/wagmi/Hooks/dex.test.ts +1017 -0
  257. package/src/wagmi/Hooks/dex.ts +1685 -0
  258. package/src/wagmi/Hooks/fee.test.ts +166 -0
  259. package/src/wagmi/Hooks/fee.ts +206 -0
  260. package/src/wagmi/Hooks/index.ts +5 -0
  261. package/src/wagmi/Hooks/reward.test.ts +219 -0
  262. package/src/wagmi/Hooks/reward.ts +672 -0
  263. package/src/wagmi/Hooks/token.test.ts +1670 -0
  264. package/src/wagmi/Hooks/token.ts +2906 -0
  265. package/src/wagmi/index.ts +3 -0
  266. package/src/wagmi/internal/types.ts +16 -0
  267. package/dist/viem/Client.d.ts +0 -27
  268. package/dist/viem/Client.d.ts.map +0 -1
  269. package/dist/viem/Client.js +0 -28
  270. package/dist/viem/Client.js.map +0 -1
  271. package/dist/viem/Tick.d.ts.map +0 -1
  272. package/dist/viem/Tick.js.map +0 -1
  273. package/src/viem/Client.bench-d.ts +0 -8
  274. package/src/viem/Client.test.ts +0 -178
  275. package/src/viem/Client.ts +0 -91
  276. /package/dist/{viem → ox}/Tick.js +0 -0
@@ -0,0 +1,1017 @@
1
+ import { Tick } from 'tempo.ts/viem'
2
+ import { Actions, Hooks } from 'tempo.ts/wagmi'
3
+ import { type Address, isAddress, parseUnits } from 'viem'
4
+ import { describe, expect, test, vi } from 'vitest'
5
+ import { useConnect } from 'wagmi'
6
+ import { accounts, addresses } from '../../../test/viem/config.js'
7
+ import {
8
+ config,
9
+ renderHook,
10
+ setupOrders,
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('useGetOrders', () => {
424
+ test('default', async () => {
425
+ await setupOrders()
426
+
427
+ const { result } = await renderHook(() =>
428
+ Hooks.dex.useGetOrders({
429
+ limit: 10,
430
+ }),
431
+ )
432
+
433
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
434
+
435
+ expect(result.current.data).matchSnapshot()
436
+
437
+ // fetch next page
438
+ result.current.fetchNextPage()
439
+
440
+ await vi.waitFor(() => {
441
+ expect(result.current.data?.pages.length).toBe(2)
442
+ })
443
+
444
+ expect(result.current.data).matchSnapshot()
445
+ })
446
+ })
447
+
448
+ describe('useOrderbook', () => {
449
+ test('default', async () => {
450
+ const { base, quote } = await setupTokenPair()
451
+
452
+ const { result } = await renderHook(() =>
453
+ Hooks.dex.useOrderbook({
454
+ base,
455
+ quote,
456
+ }),
457
+ )
458
+
459
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
460
+
461
+ const book = result.current.data!
462
+ expect(book.base).toBe(base)
463
+ expect(book.quote).toBe(quote)
464
+ expect(book.bestBidTick).toBeDefined()
465
+ expect(book.bestAskTick).toBeDefined()
466
+ })
467
+
468
+ test('behavior: shows best bid and ask after orders placed', async () => {
469
+ const { base, quote } = await setupTokenPair()
470
+
471
+ const bidTick = Tick.fromPrice('0.999')
472
+ const askTick = Tick.fromPrice('1.001')
473
+
474
+ // Place a bid order
475
+ await Actions.dex.placeSync(config, {
476
+ token: base,
477
+ amount: parseUnits('100', 6),
478
+ type: 'buy',
479
+ tick: bidTick,
480
+ })
481
+
482
+ // Place an ask order
483
+ await Actions.dex.placeSync(config, {
484
+ token: base,
485
+ amount: parseUnits('100', 6),
486
+ type: 'sell',
487
+ tick: askTick,
488
+ })
489
+
490
+ const { result } = await renderHook(() =>
491
+ Hooks.dex.useOrderbook({
492
+ base,
493
+ quote,
494
+ }),
495
+ )
496
+
497
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
498
+
499
+ const book = result.current.data!
500
+ expect(book.bestBidTick).toBe(bidTick)
501
+ expect(book.bestAskTick).toBe(askTick)
502
+ })
503
+ })
504
+
505
+ describe('usePriceLevel', () => {
506
+ test('default', async () => {
507
+ const { base } = await setupTokenPair()
508
+
509
+ const tick = Tick.fromPrice('1.001')
510
+
511
+ // Place an order to create liquidity at this tick
512
+ const { orderId } = await Actions.dex.placeSync(config, {
513
+ token: base,
514
+ amount: parseUnits('100', 6),
515
+ type: 'buy',
516
+ tick,
517
+ })
518
+
519
+ const { result } = await renderHook(() =>
520
+ Hooks.dex.usePriceLevel({
521
+ base,
522
+ tick,
523
+ isBid: true,
524
+ }),
525
+ )
526
+
527
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
528
+
529
+ const level = result.current.data!
530
+ expect(level.head).toBe(orderId) // head should be our order
531
+ expect(level.tail).toBe(orderId) // tail should also be our order (only one)
532
+ expect(level.totalLiquidity).toBeGreaterThan(0n)
533
+ })
534
+
535
+ test('behavior: empty price level', async () => {
536
+ const { base } = await setupTokenPair()
537
+
538
+ const tick = Tick.fromPrice('1.001')
539
+
540
+ // Query a tick with no orders
541
+ const { result } = await renderHook(() =>
542
+ Hooks.dex.usePriceLevel({
543
+ base,
544
+ tick,
545
+ isBid: true,
546
+ }),
547
+ )
548
+
549
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
550
+
551
+ const level = result.current.data!
552
+ expect(level.head).toBe(0n)
553
+ expect(level.tail).toBe(0n)
554
+ expect(level.totalLiquidity).toBe(0n)
555
+ })
556
+ })
557
+
558
+ describe('useSellQuote', () => {
559
+ test('default', async () => {
560
+ const { base, quote } = await setupTokenPair()
561
+
562
+ // Place bid orders to create liquidity
563
+ await Actions.dex.placeSync(config, {
564
+ token: base,
565
+ amount: parseUnits('500', 6),
566
+ type: 'buy',
567
+ tick: Tick.fromPrice('0.999'),
568
+ })
569
+
570
+ const { result } = await renderHook(() =>
571
+ Hooks.dex.useSellQuote({
572
+ tokenIn: base,
573
+ tokenOut: quote,
574
+ amountIn: parseUnits('100', 6),
575
+ }),
576
+ )
577
+
578
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
579
+
580
+ expect(result.current.data).toBeGreaterThan(0n)
581
+ // Should be approximately 100 * 0.999 = 99.9
582
+ expect(result.current.data).toBeLessThan(parseUnits('100', 6))
583
+ })
584
+
585
+ test('behavior: fails with no liquidity', async () => {
586
+ const { base, quote } = await setupTokenPair()
587
+
588
+ // Quote should fail with no liquidity
589
+ const { result } = await renderHook(() =>
590
+ Hooks.dex.useSellQuote({
591
+ tokenIn: base,
592
+ tokenOut: quote,
593
+ amountIn: parseUnits('100', 6),
594
+ }),
595
+ )
596
+
597
+ await vi.waitUntil(() => result.current.isError, {
598
+ timeout: 50_000,
599
+ })
600
+
601
+ expect(result.current.error?.message).toContain('InsufficientLiquidity')
602
+ })
603
+ })
604
+
605
+ describe('usePlace', () => {
606
+ test('default', async () => {
607
+ const { base } = await setupTokenPair()
608
+
609
+ const { result } = await renderHook(() => Hooks.dex.usePlaceSync())
610
+
611
+ // Place a sell order
612
+ const { receipt, orderId, token, ...resultData } =
613
+ await result.current.mutateAsync({
614
+ token: base,
615
+ amount: parseUnits('100', 6),
616
+ type: 'sell',
617
+ tick: Tick.fromPrice('1.001'),
618
+ })
619
+
620
+ expect(receipt).toBeDefined()
621
+ expect(receipt.status).toBe('success')
622
+ expect(orderId).toBeGreaterThan(0n)
623
+ expect(token).toBe(base)
624
+ expect(resultData).toMatchInlineSnapshot(`
625
+ {
626
+ "amount": 100000000n,
627
+ "isBid": false,
628
+ "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
629
+ "tick": 100,
630
+ }
631
+ `)
632
+
633
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
634
+
635
+ // Place a buy order
636
+ const {
637
+ receipt: receipt2,
638
+ orderId: orderId2,
639
+ token: token2,
640
+ ...result2
641
+ } = await result.current.mutateAsync({
642
+ token: base,
643
+ amount: parseUnits('100', 6),
644
+ type: 'buy',
645
+ tick: Tick.fromPrice('1.001'),
646
+ })
647
+ expect(receipt2.status).toBe('success')
648
+ expect(orderId2).toBeGreaterThan(0n)
649
+ expect(token2).toBe(base)
650
+ expect(result2).toMatchInlineSnapshot(`
651
+ {
652
+ "amount": 100000000n,
653
+ "isBid": true,
654
+ "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
655
+ "tick": 100,
656
+ }
657
+ `)
658
+ })
659
+ })
660
+
661
+ describe('usePlaceFlip', () => {
662
+ test('default', async () => {
663
+ const { base } = await setupTokenPair()
664
+
665
+ const { result } = await renderHook(() => Hooks.dex.usePlaceFlipSync())
666
+
667
+ // Place a flip bid order
668
+ const { receipt, orderId, token, ...resultData } =
669
+ await result.current.mutateAsync({
670
+ token: base,
671
+ amount: parseUnits('100', 6),
672
+ type: 'buy',
673
+ tick: Tick.fromPrice('1.001'),
674
+ flipTick: Tick.fromPrice('1.002'),
675
+ })
676
+
677
+ expect(receipt).toBeDefined()
678
+ expect(receipt.status).toBe('success')
679
+ expect(orderId).toBeGreaterThan(0n)
680
+ expect(token).toBe(base)
681
+ expect(resultData.flipTick).toBe(Tick.fromPrice('1.002'))
682
+ expect(resultData).toMatchInlineSnapshot(`
683
+ {
684
+ "amount": 100000000n,
685
+ "flipTick": 200,
686
+ "isBid": true,
687
+ "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
688
+ "tick": 100,
689
+ }
690
+ `)
691
+
692
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
693
+ })
694
+ })
695
+
696
+ describe('useSell', () => {
697
+ test('default', async () => {
698
+ const { base, quote } = await setupTokenPair()
699
+
700
+ const { result } = await renderHook(() => Hooks.dex.useSellSync())
701
+
702
+ // Place bid order to create liquidity
703
+ await Actions.dex.placeSync(config, {
704
+ token: base,
705
+ amount: parseUnits('500', 6),
706
+ type: 'buy',
707
+ tick: Tick.fromPrice('0.999'),
708
+ })
709
+
710
+ // Sell base tokens
711
+ const { receipt } = await result.current.mutateAsync({
712
+ tokenIn: base,
713
+ tokenOut: quote,
714
+ amountIn: parseUnits('100', 6),
715
+ minAmountOut: parseUnits('50', 6),
716
+ })
717
+
718
+ expect(receipt).toBeDefined()
719
+ expect(receipt.status).toBe('success')
720
+
721
+ await vi.waitFor(() => expect(result.current.isSuccess).toBeTruthy())
722
+ })
723
+
724
+ test('behavior: respects minAmountOut', async () => {
725
+ const { base, quote } = await setupTokenPair()
726
+
727
+ const { result } = await renderHook(() => Hooks.dex.useSellSync())
728
+
729
+ // Place bid order at low price
730
+ await Actions.dex.placeSync(config, {
731
+ token: base,
732
+ amount: parseUnits('500', 6),
733
+ type: 'buy',
734
+ tick: Tick.fromPrice('0.99'), // 1% below peg
735
+ })
736
+
737
+ // Try to sell with too high minAmountOut - should fail
738
+ await expect(
739
+ result.current.mutateAsync({
740
+ tokenIn: base,
741
+ tokenOut: quote,
742
+ amountIn: parseUnits('100', 6),
743
+ minAmountOut: parseUnits('150', 6), // Way too high
744
+ }),
745
+ ).rejects.toThrow('The contract function "swapExactAmountIn" reverted')
746
+ })
747
+
748
+ test('behavior: fails with insufficient liquidity', async () => {
749
+ const { base, quote } = await setupTokenPair()
750
+
751
+ const { result } = await renderHook(() => Hooks.dex.useSellSync())
752
+
753
+ // No orders - no liquidity
754
+
755
+ // Try to sell - should fail
756
+ await expect(
757
+ result.current.mutateAsync({
758
+ tokenIn: base,
759
+ tokenOut: quote,
760
+ amountIn: parseUnits('100', 6),
761
+ minAmountOut: parseUnits('50', 6),
762
+ }),
763
+ ).rejects.toThrow('The contract function "swapExactAmountIn" reverted')
764
+ })
765
+ })
766
+
767
+ describe('useWatchFlipOrderPlaced', () => {
768
+ test('default', async () => {
769
+ const { base } = await setupTokenPair()
770
+
771
+ const { result } = await renderHook(() => Hooks.dex.usePlaceFlipSync())
772
+
773
+ const events: any[] = []
774
+ await renderHook(() =>
775
+ Hooks.dex.useWatchFlipOrderPlaced({
776
+ onFlipOrderPlaced(args) {
777
+ events.push(args)
778
+ },
779
+ }),
780
+ )
781
+
782
+ // Place flip order to trigger event
783
+ await result.current.mutateAsync({
784
+ token: base,
785
+ amount: parseUnits('100', 6),
786
+ type: 'buy',
787
+ tick: Tick.fromPrice('1.001'),
788
+ flipTick: Tick.fromPrice('1.002'),
789
+ })
790
+
791
+ await vi.waitUntil(() => events.length >= 1)
792
+
793
+ expect(events.length).toBeGreaterThanOrEqual(1)
794
+ expect(events[0]?.flipTick).toBe(Tick.fromPrice('1.002'))
795
+ expect(events[0]?.tick).toBe(Tick.fromPrice('1.001'))
796
+ expect(events[0]?.isBid).toBe(true)
797
+ expect(events[0]?.amount).toBe(parseUnits('100', 6))
798
+ })
799
+ })
800
+
801
+ describe('useWatchOrderCancelled', () => {
802
+ test('default', async () => {
803
+ const { base } = await setupTokenPair()
804
+
805
+ // Place order first
806
+ const { orderId } = await Actions.dex.placeSync(config, {
807
+ token: base,
808
+ amount: parseUnits('100', 6),
809
+ type: 'buy',
810
+ tick: Tick.fromPrice('1.001'),
811
+ })
812
+
813
+ const events: any[] = []
814
+ await renderHook(() =>
815
+ Hooks.dex.useWatchOrderCancelled({
816
+ onOrderCancelled(args) {
817
+ events.push(args)
818
+ },
819
+ }),
820
+ )
821
+
822
+ // Cancel order to trigger event
823
+ await Actions.dex.cancelSync(config, {
824
+ orderId,
825
+ })
826
+
827
+ await vi.waitUntil(() => events.length >= 1)
828
+
829
+ expect(events.length).toBeGreaterThanOrEqual(1)
830
+ expect(events[0]?.orderId).toBe(orderId)
831
+ })
832
+
833
+ test('behavior: filter by orderId', async () => {
834
+ const { base } = await setupTokenPair()
835
+
836
+ // Place two orders
837
+ const { orderId: orderId1 } = await Actions.dex.placeSync(config, {
838
+ token: base,
839
+ amount: parseUnits('100', 6),
840
+ type: 'buy',
841
+ tick: Tick.fromPrice('1.001'),
842
+ })
843
+
844
+ const { orderId: orderId2 } = await Actions.dex.placeSync(config, {
845
+ token: base,
846
+ amount: parseUnits('50', 6),
847
+ type: 'buy',
848
+ tick: Tick.fromPrice('1.001'),
849
+ })
850
+
851
+ const events: any[] = []
852
+ await renderHook(() =>
853
+ Hooks.dex.useWatchOrderCancelled({
854
+ orderId: orderId1, // Filter for only orderId1
855
+ onOrderCancelled(args) {
856
+ events.push(args)
857
+ },
858
+ }),
859
+ )
860
+
861
+ // Cancel orderId1 (should be captured)
862
+ await Actions.dex.cancelSync(config, {
863
+ orderId: orderId1,
864
+ })
865
+
866
+ // Cancel orderId2 (should NOT be captured due to filter)
867
+ await Actions.dex.cancelSync(config, {
868
+ orderId: orderId2,
869
+ })
870
+
871
+ await vi.waitUntil(() => events.length >= 1, { timeout: 2000 })
872
+
873
+ // Should only receive 1 event (for orderId1)
874
+ expect(events.length).toBe(1)
875
+ expect(events[0]?.orderId).toBe(orderId1)
876
+ })
877
+ })
878
+
879
+ describe.todo('useWatchOrderFilled')
880
+
881
+ describe('useWatchOrderPlaced', () => {
882
+ test('default', async () => {
883
+ const { base } = await setupTokenPair()
884
+
885
+ const events: any[] = []
886
+ await renderHook(() =>
887
+ Hooks.dex.useWatchOrderPlaced({
888
+ onOrderPlaced(args) {
889
+ events.push(args)
890
+ },
891
+ }),
892
+ )
893
+
894
+ // Place first order
895
+ await Actions.dex.placeSync(config, {
896
+ token: base,
897
+ amount: parseUnits('100', 6),
898
+ type: 'buy',
899
+ tick: Tick.fromPrice('1.001'),
900
+ })
901
+
902
+ // Place second order
903
+ await Actions.dex.placeSync(config, {
904
+ token: base,
905
+ amount: parseUnits('50', 6),
906
+ type: 'sell',
907
+ tick: Tick.fromPrice('0.999'),
908
+ })
909
+
910
+ await vi.waitUntil(() => events.length >= 2)
911
+
912
+ expect(events.length).toBeGreaterThanOrEqual(2)
913
+ expect(events[0]?.isBid).toBe(true)
914
+ expect(events[0]?.amount).toBe(parseUnits('100', 6))
915
+ expect(events[1]?.isBid).toBe(false)
916
+ expect(events[1]?.amount).toBe(parseUnits('50', 6))
917
+ })
918
+
919
+ test('behavior: filter by token', async () => {
920
+ const { base } = await setupTokenPair()
921
+ const { base: base2 } = await setupTokenPair()
922
+
923
+ const events: any[] = []
924
+ await renderHook(() =>
925
+ Hooks.dex.useWatchOrderPlaced({
926
+ token: base, // Filter for only base token
927
+ onOrderPlaced(args) {
928
+ events.push(args)
929
+ },
930
+ }),
931
+ )
932
+
933
+ // Place order on base (should be captured)
934
+ await Actions.dex.placeSync(config, {
935
+ token: base,
936
+ amount: parseUnits('100', 6),
937
+ type: 'buy',
938
+ tick: Tick.fromPrice('1.001'),
939
+ })
940
+
941
+ // Place order on base2 (should NOT be captured due to filter)
942
+ await Actions.dex.placeSync(config, {
943
+ token: base2,
944
+ amount: parseUnits('50', 6),
945
+ type: 'buy',
946
+ tick: Tick.fromPrice('1.001'),
947
+ })
948
+
949
+ await vi.waitUntil(() => events.length >= 1, { timeout: 2000 })
950
+
951
+ // Should only receive 1 event (for base token)
952
+ expect(events.length).toBe(1)
953
+ expect(events[0]?.token.toLowerCase()).toBe(base.toLowerCase())
954
+ })
955
+ })
956
+
957
+ describe('useWithdraw', () => {
958
+ test('default', async () => {
959
+ const { base, quote } = await setupTokenPair()
960
+
961
+ const { result } = await renderHook(() => ({
962
+ place: Hooks.dex.usePlaceSync(),
963
+ cancel: Hooks.dex.useCancelSync(),
964
+ withdraw: Hooks.dex.useWithdrawSync(),
965
+ }))
966
+
967
+ // Create internal balance
968
+ const { orderId } = await result.current.place.mutateAsync({
969
+ token: base,
970
+ amount: parseUnits('100', 6),
971
+ type: 'buy',
972
+ tick: Tick.fromPrice('1.001'),
973
+ })
974
+
975
+ await result.current.cancel.mutateAsync({ orderId })
976
+
977
+ // Get DEX balance
978
+ const dexBalance = await Actions.dex.getBalance(config, {
979
+ account: account.address,
980
+ token: quote,
981
+ })
982
+ expect(dexBalance).toBeGreaterThan(0n)
983
+
984
+ // Get wallet balance before withdraw
985
+ const walletBalanceBefore = await Actions.token.getBalance(config, {
986
+ token: quote,
987
+ account: account.address,
988
+ })
989
+
990
+ // Withdraw from DEX
991
+ const { receipt } = await result.current.withdraw.mutateAsync({
992
+ token: quote,
993
+ amount: dexBalance,
994
+ })
995
+
996
+ expect(receipt).toBeDefined()
997
+ expect(receipt.status).toBe('success')
998
+
999
+ await vi.waitFor(() =>
1000
+ expect(result.current.withdraw.isSuccess).toBeTruthy(),
1001
+ )
1002
+
1003
+ // Check DEX balance is now 0
1004
+ const dexBalanceAfter = await Actions.dex.getBalance(config, {
1005
+ account: account.address,
1006
+ token: quote,
1007
+ })
1008
+ expect(dexBalanceAfter).toBe(0n)
1009
+
1010
+ // Check wallet balance increased
1011
+ const walletBalanceAfter = await Actions.token.getBalance(config, {
1012
+ token: quote,
1013
+ account: account.address,
1014
+ })
1015
+ expect(walletBalanceAfter).toBeGreaterThan(walletBalanceBefore)
1016
+ })
1017
+ })