tempo.ts 0.1.5 → 0.2.1

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 (297) 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 +20 -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 +144 -161
  58. package/dist/viem/Actions/amm.d.ts.map +1 -1
  59. package/dist/viem/Actions/amm.js +109 -163
  60. package/dist/viem/Actions/amm.js.map +1 -1
  61. package/dist/viem/Actions/dex.d.ts +920 -664
  62. package/dist/viem/Actions/dex.d.ts.map +1 -1
  63. package/dist/viem/Actions/dex.js +129 -30
  64. package/dist/viem/Actions/dex.js.map +1 -1
  65. package/dist/viem/Actions/faucet.d.ts +34 -0
  66. package/dist/viem/Actions/faucet.d.ts.map +1 -0
  67. package/dist/viem/Actions/faucet.js +33 -0
  68. package/dist/viem/Actions/faucet.js.map +1 -0
  69. package/dist/viem/Actions/fee.d.ts +16 -30
  70. package/dist/viem/Actions/fee.d.ts.map +1 -1
  71. package/dist/viem/Actions/fee.js +13 -13
  72. package/dist/viem/Actions/fee.js.map +1 -1
  73. package/dist/viem/Actions/index.d.ts +2 -0
  74. package/dist/viem/Actions/index.d.ts.map +1 -1
  75. package/dist/viem/Actions/index.js +2 -0
  76. package/dist/viem/Actions/index.js.map +1 -1
  77. package/dist/viem/Actions/policy.d.ts +46 -46
  78. package/dist/viem/Actions/policy.js +46 -46
  79. package/dist/viem/Actions/reward.d.ts +3236 -0
  80. package/dist/viem/Actions/reward.d.ts.map +1 -0
  81. package/dist/viem/Actions/reward.js +725 -0
  82. package/dist/viem/Actions/reward.js.map +1 -0
  83. package/dist/viem/Actions/token.d.ts +4399 -2750
  84. package/dist/viem/Actions/token.d.ts.map +1 -1
  85. package/dist/viem/Actions/token.js +361 -482
  86. package/dist/viem/Actions/token.js.map +1 -1
  87. package/dist/viem/Addresses.d.ts +1 -2
  88. package/dist/viem/Addresses.d.ts.map +1 -1
  89. package/dist/viem/Addresses.js +1 -2
  90. package/dist/viem/Addresses.js.map +1 -1
  91. package/dist/viem/Chain.d.ts +38 -12
  92. package/dist/viem/Chain.d.ts.map +1 -1
  93. package/dist/viem/Chain.js +27 -18
  94. package/dist/viem/Chain.js.map +1 -1
  95. package/dist/viem/Decorator.d.ts +1009 -428
  96. package/dist/viem/Decorator.d.ts.map +1 -1
  97. package/dist/viem/Decorator.js +17 -5
  98. package/dist/viem/Decorator.js.map +1 -1
  99. package/dist/viem/Formatters.d.ts +8 -1
  100. package/dist/viem/Formatters.d.ts.map +1 -1
  101. package/dist/viem/Formatters.js +17 -0
  102. package/dist/viem/Formatters.js.map +1 -1
  103. package/dist/viem/P256.d.ts +2 -0
  104. package/dist/viem/P256.d.ts.map +1 -0
  105. package/dist/viem/P256.js +2 -0
  106. package/dist/viem/P256.js.map +1 -0
  107. package/dist/viem/Secp256k1.d.ts +2 -0
  108. package/dist/viem/Secp256k1.d.ts.map +1 -0
  109. package/dist/viem/Secp256k1.js +2 -0
  110. package/dist/viem/Secp256k1.js.map +1 -0
  111. package/dist/viem/TokenIds.d.ts +1 -2
  112. package/dist/viem/TokenIds.d.ts.map +1 -1
  113. package/dist/viem/TokenIds.js +1 -2
  114. package/dist/viem/TokenIds.js.map +1 -1
  115. package/dist/viem/Transaction.d.ts +1 -1
  116. package/dist/viem/Transaction.d.ts.map +1 -1
  117. package/dist/viem/Transaction.js +46 -5
  118. package/dist/viem/Transaction.js.map +1 -1
  119. package/dist/viem/WebAuthnP256.d.ts +79 -0
  120. package/dist/viem/WebAuthnP256.d.ts.map +1 -0
  121. package/dist/viem/WebAuthnP256.js +95 -0
  122. package/dist/viem/WebAuthnP256.js.map +1 -0
  123. package/dist/viem/WebCryptoP256.d.ts +2 -0
  124. package/dist/viem/WebCryptoP256.d.ts.map +1 -0
  125. package/dist/viem/WebCryptoP256.js +2 -0
  126. package/dist/viem/WebCryptoP256.js.map +1 -0
  127. package/dist/viem/index.d.ts +6 -3
  128. package/dist/viem/index.d.ts.map +1 -1
  129. package/dist/viem/index.js +6 -3
  130. package/dist/viem/index.js.map +1 -1
  131. package/dist/viem/internal/account.d.ts +24 -0
  132. package/dist/viem/internal/account.d.ts.map +1 -0
  133. package/dist/viem/internal/account.js +68 -0
  134. package/dist/viem/internal/account.js.map +1 -0
  135. package/dist/viem/internal/types.d.ts +10 -0
  136. package/dist/viem/internal/types.d.ts.map +1 -1
  137. package/dist/wagmi/Actions/amm.d.ts +428 -0
  138. package/dist/wagmi/Actions/amm.d.ts.map +1 -0
  139. package/dist/wagmi/Actions/amm.js +472 -0
  140. package/dist/wagmi/Actions/amm.js.map +1 -0
  141. package/dist/wagmi/Actions/dex.d.ts +908 -0
  142. package/dist/wagmi/Actions/dex.d.ts.map +1 -0
  143. package/dist/wagmi/Actions/dex.js +1023 -0
  144. package/dist/wagmi/Actions/dex.js.map +1 -0
  145. package/dist/wagmi/Actions/faucet.d.ts +35 -0
  146. package/dist/wagmi/Actions/faucet.d.ts.map +1 -0
  147. package/dist/wagmi/Actions/faucet.js +33 -0
  148. package/dist/wagmi/Actions/faucet.js.map +1 -0
  149. package/dist/wagmi/Actions/fee.d.ts +111 -0
  150. package/dist/wagmi/Actions/fee.d.ts.map +1 -0
  151. package/dist/wagmi/Actions/fee.js +126 -0
  152. package/dist/wagmi/Actions/fee.js.map +1 -0
  153. package/dist/wagmi/Actions/index.d.ts +7 -0
  154. package/dist/wagmi/Actions/index.d.ts.map +1 -0
  155. package/dist/wagmi/Actions/index.js +7 -0
  156. package/dist/wagmi/Actions/index.js.map +1 -0
  157. package/dist/wagmi/Actions/reward.d.ts +348 -0
  158. package/dist/wagmi/Actions/reward.d.ts.map +1 -0
  159. package/dist/wagmi/Actions/reward.js +388 -0
  160. package/dist/wagmi/Actions/reward.js.map +1 -0
  161. package/dist/wagmi/Actions/token.d.ts +1546 -0
  162. package/dist/wagmi/Actions/token.d.ts.map +1 -0
  163. package/dist/wagmi/Actions/token.js +1712 -0
  164. package/dist/wagmi/Actions/token.js.map +1 -0
  165. package/dist/wagmi/Connector.d.ts +81 -0
  166. package/dist/wagmi/Connector.d.ts.map +1 -0
  167. package/dist/wagmi/Connector.js +261 -0
  168. package/dist/wagmi/Connector.js.map +1 -0
  169. package/dist/wagmi/Hooks/amm.d.ts +421 -0
  170. package/dist/wagmi/Hooks/amm.d.ts.map +1 -0
  171. package/dist/wagmi/Hooks/amm.js +504 -0
  172. package/dist/wagmi/Hooks/amm.js.map +1 -0
  173. package/dist/wagmi/Hooks/dex.d.ts +816 -0
  174. package/dist/wagmi/Hooks/dex.d.ts.map +1 -0
  175. package/dist/wagmi/Hooks/dex.js +973 -0
  176. package/dist/wagmi/Hooks/dex.js.map +1 -0
  177. package/dist/wagmi/Hooks/faucet.d.ts +39 -0
  178. package/dist/wagmi/Hooks/faucet.d.ts.map +1 -0
  179. package/dist/wagmi/Hooks/faucet.js +40 -0
  180. package/dist/wagmi/Hooks/faucet.js.map +1 -0
  181. package/dist/wagmi/Hooks/fee.d.ts +97 -0
  182. package/dist/wagmi/Hooks/fee.d.ts.map +1 -0
  183. package/dist/wagmi/Hooks/fee.js +109 -0
  184. package/dist/wagmi/Hooks/fee.js.map +1 -0
  185. package/dist/wagmi/Hooks/index.d.ts +7 -0
  186. package/dist/wagmi/Hooks/index.d.ts.map +1 -0
  187. package/dist/wagmi/Hooks/index.js +7 -0
  188. package/dist/wagmi/Hooks/index.js.map +1 -0
  189. package/dist/wagmi/Hooks/reward.d.ts +307 -0
  190. package/dist/wagmi/Hooks/reward.d.ts.map +1 -0
  191. package/dist/wagmi/Hooks/reward.js +349 -0
  192. package/dist/wagmi/Hooks/reward.js.map +1 -0
  193. package/dist/wagmi/Hooks/token.d.ts +1388 -0
  194. package/dist/wagmi/Hooks/token.d.ts.map +1 -0
  195. package/dist/wagmi/Hooks/token.js +1657 -0
  196. package/dist/wagmi/Hooks/token.js.map +1 -0
  197. package/dist/wagmi/index.d.ts +4 -0
  198. package/dist/wagmi/index.d.ts.map +1 -0
  199. package/dist/wagmi/index.js +4 -0
  200. package/dist/wagmi/index.js.map +1 -0
  201. package/package.json +54 -10
  202. package/src/chains.ts +21 -9
  203. package/src/ox/Order.test.ts +78 -0
  204. package/src/ox/Order.ts +125 -0
  205. package/src/ox/OrdersFilters.test.ts +182 -0
  206. package/src/ox/OrdersFilters.ts +125 -0
  207. package/src/ox/Pagination.test.ts +162 -0
  208. package/src/ox/Pagination.ts +164 -0
  209. package/src/ox/PoolId.test.ts +33 -0
  210. package/src/ox/PoolId.ts +27 -0
  211. package/src/ox/RpcSchema.ts +35 -0
  212. package/src/ox/SignatureEnvelope.ts +3 -1
  213. package/src/{viem → ox}/Tick.test.ts +1 -1
  214. package/src/{viem → ox}/Tick.ts +5 -0
  215. package/src/ox/Transaction.test.ts +1 -1
  216. package/src/ox/Transaction.ts +2 -1
  217. package/src/ox/TransactionEnvelopeAA.test.ts +239 -96
  218. package/src/ox/TransactionEnvelopeAA.ts +9 -7
  219. package/src/ox/TransactionRequest.ts +4 -0
  220. package/src/ox/index.ts +6 -0
  221. package/src/prool/Instance.ts +51 -37
  222. package/src/prool/internal/chain.json +104 -52
  223. package/src/tsconfig.json +9 -0
  224. package/src/viem/Abis.ts +972 -710
  225. package/src/viem/Account.ts +279 -0
  226. package/src/viem/Actions/__snapshots__/dex.test.ts.snap +850 -0
  227. package/src/viem/Actions/amm.test.ts +173 -169
  228. package/src/viem/Actions/amm.ts +131 -203
  229. package/src/viem/Actions/dex.test.ts +563 -484
  230. package/src/viem/Actions/dex.ts +203 -30
  231. package/src/viem/Actions/faucet.ts +50 -0
  232. package/src/viem/Actions/fee.test.ts +23 -34
  233. package/src/viem/Actions/fee.ts +20 -13
  234. package/src/viem/Actions/index.ts +2 -0
  235. package/src/viem/Actions/policy.test.ts +19 -33
  236. package/src/viem/Actions/policy.ts +46 -46
  237. package/src/viem/Actions/reward.test.ts +457 -0
  238. package/src/viem/Actions/reward.ts +999 -0
  239. package/src/viem/Actions/token.test.ts +453 -287
  240. package/src/viem/Actions/token.ts +605 -693
  241. package/src/viem/Addresses.ts +1 -2
  242. package/src/viem/Chain.bench-d.ts +12 -0
  243. package/src/viem/Chain.ts +70 -20
  244. package/src/viem/Decorator.bench-d.ts +1 -1
  245. package/src/viem/Decorator.test.ts +3 -1
  246. package/src/viem/Decorator.ts +1049 -442
  247. package/src/viem/Formatters.ts +31 -5
  248. package/src/viem/P256.ts +1 -0
  249. package/src/viem/Secp256k1.ts +1 -0
  250. package/src/viem/TokenIds.ts +1 -2
  251. package/src/viem/Transaction.ts +53 -7
  252. package/src/viem/WebAuthnP256.ts +140 -0
  253. package/src/viem/WebCryptoP256.ts +1 -0
  254. package/src/viem/e2e.test.ts +1126 -297
  255. package/src/viem/index.ts +6 -3
  256. package/src/viem/internal/account.ts +107 -0
  257. package/src/viem/internal/types.ts +9 -0
  258. package/src/wagmi/Actions/__snapshots__/dex.test.ts.snap +310 -0
  259. package/src/wagmi/Actions/amm.test.ts +198 -0
  260. package/src/wagmi/Actions/amm.ts +691 -0
  261. package/src/wagmi/Actions/dex.test.ts +1507 -0
  262. package/src/wagmi/Actions/dex.ts +1640 -0
  263. package/src/wagmi/Actions/faucet.ts +46 -0
  264. package/src/wagmi/Actions/fee.test.ts +63 -0
  265. package/src/wagmi/Actions/fee.ts +208 -0
  266. package/src/wagmi/Actions/index.ts +6 -0
  267. package/src/wagmi/Actions/reward.test.ts +210 -0
  268. package/src/wagmi/Actions/reward.ts +632 -0
  269. package/src/wagmi/Actions/token.test.ts +1308 -0
  270. package/src/wagmi/Actions/token.ts +2613 -0
  271. package/src/wagmi/Connector.test.ts +53 -0
  272. package/src/wagmi/Connector.ts +390 -0
  273. package/src/wagmi/Hooks/__snapshots__/dex.test.ts.snap +457 -0
  274. package/src/wagmi/Hooks/amm.test.ts +424 -0
  275. package/src/wagmi/Hooks/amm.ts +806 -0
  276. package/src/wagmi/Hooks/dex.test.ts +1017 -0
  277. package/src/wagmi/Hooks/dex.ts +1685 -0
  278. package/src/wagmi/Hooks/faucet.ts +76 -0
  279. package/src/wagmi/Hooks/fee.test.ts +166 -0
  280. package/src/wagmi/Hooks/fee.ts +206 -0
  281. package/src/wagmi/Hooks/index.ts +6 -0
  282. package/src/wagmi/Hooks/reward.test.ts +219 -0
  283. package/src/wagmi/Hooks/reward.ts +672 -0
  284. package/src/wagmi/Hooks/token.test.ts +1670 -0
  285. package/src/wagmi/Hooks/token.ts +2906 -0
  286. package/src/wagmi/index.ts +3 -0
  287. package/src/wagmi/internal/types.ts +16 -0
  288. package/dist/viem/Client.d.ts +0 -27
  289. package/dist/viem/Client.d.ts.map +0 -1
  290. package/dist/viem/Client.js +0 -28
  291. package/dist/viem/Client.js.map +0 -1
  292. package/dist/viem/Tick.d.ts.map +0 -1
  293. package/dist/viem/Tick.js.map +0 -1
  294. package/src/viem/Client.bench-d.ts +0 -8
  295. package/src/viem/Client.test.ts +0 -178
  296. package/src/viem/Client.ts +0 -91
  297. /package/dist/{viem → ox}/Tick.js +0 -0
@@ -0,0 +1,1507 @@
1
+ import { connect } from '@wagmi/core'
2
+ import { Tick } from 'tempo.ts/viem'
3
+ import { Actions } from 'tempo.ts/wagmi'
4
+ import { isAddress, parseUnits } from 'viem'
5
+ import { beforeAll, describe, expect, test } from 'vitest'
6
+ import { accounts, addresses } from '../../../test/viem/config.js'
7
+ import {
8
+ config,
9
+ queryClient,
10
+ setupOrders,
11
+ setupTokenPair,
12
+ } from '../../../test/wagmi/config.js'
13
+
14
+ const account = accounts[0]
15
+ const account2 = accounts[1]
16
+
17
+ beforeAll(async () => {
18
+ await setupOrders()
19
+ })
20
+
21
+ describe('buy', () => {
22
+ test('default', async () => {
23
+ const { base, quote } = await setupTokenPair()
24
+
25
+ // Place ask order to create liquidity
26
+ await Actions.dex.placeSync(config, {
27
+ token: base,
28
+ amount: parseUnits('500', 6),
29
+ type: 'sell',
30
+ tick: Tick.fromPrice('1.001'),
31
+ })
32
+
33
+ // Get initial balances
34
+ const baseBalanceBefore = await Actions.token.getBalance(config, {
35
+ token: base,
36
+ account: account.address,
37
+ })
38
+
39
+ // Buy base tokens with quote tokens
40
+ const { receipt } = await Actions.dex.buySync(config, {
41
+ tokenIn: quote,
42
+ tokenOut: base,
43
+ amountOut: parseUnits('100', 6),
44
+ maxAmountIn: parseUnits('150', 6),
45
+ })
46
+
47
+ expect(receipt).toBeDefined()
48
+ expect(receipt.status).toBe('success')
49
+
50
+ // Verify balances changed
51
+ const baseBalanceAfter = await Actions.token.getBalance(config, {
52
+ token: base,
53
+ account: account.address,
54
+ })
55
+
56
+ // Should have received base tokens
57
+ expect(baseBalanceAfter).toBeGreaterThan(baseBalanceBefore)
58
+ })
59
+
60
+ test('behavior: respects maxAmountIn', async () => {
61
+ const { base, quote } = await setupTokenPair()
62
+
63
+ // Place ask order at high price
64
+ await Actions.dex.placeSync(config, {
65
+ token: base,
66
+ amount: parseUnits('500', 6),
67
+ type: 'sell',
68
+ tick: Tick.fromPrice('1.01'), // 1% above peg
69
+ })
70
+
71
+ // Try to buy with insufficient maxAmountIn - should fail
72
+ await expect(
73
+ Actions.dex.buySync(config, {
74
+ tokenIn: quote,
75
+ tokenOut: base,
76
+ amountOut: parseUnits('100', 6),
77
+ maxAmountIn: parseUnits('50', 6), // Way too low for 1% premium
78
+ }),
79
+ ).rejects.toThrow('The contract function "swapExactAmountOut" reverted')
80
+ })
81
+
82
+ test('behavior: fails with insufficient liquidity', async () => {
83
+ const { base, quote } = await setupTokenPair()
84
+
85
+ // Don't place any orders - no liquidity
86
+
87
+ // Try to buy - should fail due to no liquidity
88
+ await expect(
89
+ Actions.dex.buySync(config, {
90
+ tokenIn: quote,
91
+ tokenOut: base,
92
+ amountOut: parseUnits('100', 6),
93
+ maxAmountIn: parseUnits('150', 6),
94
+ }),
95
+ ).rejects.toThrow('The contract function "swapExactAmountOut" reverted')
96
+ })
97
+ })
98
+
99
+ describe('cancel', () => {
100
+ test('default', async () => {
101
+ const { base, quote } = await setupTokenPair()
102
+
103
+ // Place a bid order
104
+ const { orderId } = await Actions.dex.placeSync(config, {
105
+ token: base,
106
+ amount: parseUnits('100', 6),
107
+ type: 'buy',
108
+ tick: Tick.fromPrice('1.001'),
109
+ })
110
+
111
+ // Check initial DEX balance (should be 0)
112
+ const dexBalanceBefore = await Actions.dex.getBalance(config, {
113
+ account: account.address,
114
+ token: quote,
115
+ })
116
+ expect(dexBalanceBefore).toBe(0n)
117
+
118
+ // Cancel the order
119
+ const { receipt, ...result } = await Actions.dex.cancelSync(config, {
120
+ orderId,
121
+ })
122
+
123
+ expect(receipt).toBeDefined()
124
+ expect(receipt.status).toBe('success')
125
+ expect(result.orderId).toBe(orderId)
126
+
127
+ // Check DEX balance after cancel - tokens should be refunded to internal balance
128
+ const dexBalanceAfter = await Actions.dex.getBalance(config, {
129
+ account: account.address,
130
+ token: quote,
131
+ })
132
+ expect(dexBalanceAfter).toBeGreaterThan(0n)
133
+ })
134
+
135
+ test('behavior: only maker can cancel', async () => {
136
+ const { base } = await setupTokenPair()
137
+
138
+ // Account places order
139
+ const { orderId } = await Actions.dex.placeSync(config, {
140
+ token: base,
141
+ amount: parseUnits('100', 6),
142
+ type: 'buy',
143
+ tick: Tick.fromPrice('1.001'),
144
+ })
145
+
146
+ // Transfer gas to account2
147
+ await Actions.token.transferSync(config, {
148
+ to: account2.address,
149
+ amount: parseUnits('1', 6),
150
+ token: addresses.alphaUsd,
151
+ })
152
+
153
+ // Use a different account via the connector
154
+ await connect(config, {
155
+ connector: config.connectors[1]!,
156
+ })
157
+
158
+ // Account2 tries to cancel - should fail
159
+ await expect(
160
+ Actions.dex.cancelSync(config, {
161
+ orderId,
162
+ }),
163
+ ).rejects.toThrow('The contract function "cancel" reverted')
164
+ })
165
+
166
+ test('behavior: cannot cancel non-existent order', async () => {
167
+ await setupTokenPair()
168
+
169
+ // Try to cancel an order that doesn't exist
170
+ await expect(
171
+ Actions.dex.cancelSync(config, {
172
+ orderId: 999n,
173
+ }),
174
+ ).rejects.toThrow('The contract function "cancel" reverted')
175
+ })
176
+ })
177
+
178
+ describe('createPair', () => {
179
+ test('default', async () => {
180
+ await connect(config, {
181
+ connector: config.connectors[0]!,
182
+ })
183
+
184
+ const { token: baseToken } = await Actions.token.createSync(config, {
185
+ name: 'Test Base Token',
186
+ symbol: 'BASE',
187
+ currency: 'USD',
188
+ admin: account,
189
+ })
190
+
191
+ const { receipt, ...result } = await Actions.dex.createPairSync(config, {
192
+ base: baseToken,
193
+ })
194
+
195
+ expect(receipt).toBeDefined()
196
+ expect(receipt.status).toBe('success')
197
+
198
+ const { key, ...rest } = result
199
+ expect(key).toBeDefined()
200
+ expect(rest).toEqual(
201
+ expect.objectContaining({
202
+ base: expect.toSatisfy(isAddress),
203
+ quote: expect.toSatisfy(isAddress),
204
+ }),
205
+ )
206
+ })
207
+ })
208
+
209
+ describe('getBalance', () => {
210
+ test('default', async () => {
211
+ const { base, quote } = await setupTokenPair()
212
+
213
+ // Initial balance should be 0
214
+ const initialBalance = await Actions.dex.getBalance(config, {
215
+ account: account.address,
216
+ token: quote,
217
+ })
218
+ expect(initialBalance).toBe(0n)
219
+
220
+ // Place and cancel order to create internal balance
221
+ const { orderId } = await Actions.dex.placeSync(config, {
222
+ token: base,
223
+ amount: parseUnits('50', 6),
224
+ type: 'buy',
225
+ tick: Tick.fromPrice('1.0005'),
226
+ })
227
+
228
+ await Actions.dex.cancelSync(config, {
229
+ orderId,
230
+ })
231
+
232
+ // Now balance should be > 0 (refunded quote tokens)
233
+ const balance = await Actions.dex.getBalance(config, {
234
+ account: account.address,
235
+ token: quote,
236
+ })
237
+ expect(balance).toBeGreaterThan(0n)
238
+ })
239
+
240
+ test('behavior: check different account', async () => {
241
+ const { quote } = await setupTokenPair()
242
+
243
+ // Check account2's balance (should be 0)
244
+ const balance = await Actions.dex.getBalance(config, {
245
+ account: account2.address,
246
+ token: quote,
247
+ })
248
+ expect(balance).toBe(0n)
249
+ })
250
+
251
+ test('behavior: balances are per-token', async () => {
252
+ const { base, quote } = await setupTokenPair()
253
+
254
+ // Create balance in quote token
255
+ const { orderId } = await Actions.dex.placeSync(config, {
256
+ token: base,
257
+ amount: parseUnits('100', 6),
258
+ type: 'buy',
259
+ tick: Tick.fromPrice('1.001'),
260
+ })
261
+ await Actions.dex.cancelSync(config, { orderId })
262
+
263
+ // Check quote balance (should have refunded tokens)
264
+ const quoteBalance = await Actions.dex.getBalance(config, {
265
+ account: account.address,
266
+ token: quote,
267
+ })
268
+ expect(quoteBalance).toBeGreaterThan(0n)
269
+
270
+ // Check base balance (should still be 0)
271
+ const baseBalance = await Actions.dex.getBalance(config, {
272
+ account: account.address,
273
+ token: base,
274
+ })
275
+ expect(baseBalance).toBe(0n)
276
+ })
277
+ })
278
+
279
+ describe('getBuyQuote', () => {
280
+ test('default', async () => {
281
+ const { base, quote } = await setupTokenPair()
282
+
283
+ // Place ask orders to create liquidity
284
+ await Actions.dex.placeSync(config, {
285
+ token: base,
286
+ amount: parseUnits('500', 6),
287
+ type: 'sell',
288
+ tick: Tick.fromPrice('1.001'),
289
+ })
290
+
291
+ // Get quote for buying base tokens
292
+ const amountIn = await Actions.dex.getBuyQuote(config, {
293
+ tokenIn: quote,
294
+ tokenOut: base,
295
+ amountOut: parseUnits('100', 6),
296
+ })
297
+
298
+ expect(amountIn).toBeGreaterThan(0n)
299
+ // Should be approximately 100 * 1.001 = 100.1
300
+ expect(amountIn).toBeGreaterThan(parseUnits('100', 6))
301
+ })
302
+
303
+ test('behavior: fails with no liquidity', async () => {
304
+ const { base, quote } = await setupTokenPair()
305
+
306
+ // No orders placed - no liquidity
307
+
308
+ // Quote should fail
309
+ await expect(
310
+ Actions.dex.getBuyQuote(config, {
311
+ tokenIn: quote,
312
+ tokenOut: base,
313
+ amountOut: parseUnits('100', 6),
314
+ }),
315
+ ).rejects.toThrow(
316
+ 'The contract function "quoteSwapExactAmountOut" reverted',
317
+ )
318
+ })
319
+ })
320
+
321
+ describe('getOrder', () => {
322
+ test('default', async () => {
323
+ const { base } = await setupTokenPair()
324
+
325
+ // Place an order to get an order ID
326
+ const { orderId } = await Actions.dex.placeSync(config, {
327
+ token: base,
328
+ amount: parseUnits('100', 6),
329
+ type: 'buy',
330
+ tick: Tick.fromPrice('1.001'),
331
+ })
332
+
333
+ // Get the order details
334
+ const order = await Actions.dex.getOrder(config, {
335
+ orderId,
336
+ })
337
+
338
+ expect(order).toBeDefined()
339
+ expect(order.maker).toBe(account.address)
340
+ expect(order.isBid).toBe(true)
341
+ expect(order.tick).toBe(Tick.fromPrice('1.001'))
342
+ expect(order.amount).toBe(parseUnits('100', 6))
343
+ expect(order.remaining).toBe(parseUnits('100', 6))
344
+ expect(order.isFlip).toBe(false)
345
+ })
346
+
347
+ test('behavior: returns flip order details', async () => {
348
+ const { base } = await setupTokenPair()
349
+
350
+ // Place a flip order
351
+ const { orderId } = await Actions.dex.placeFlipSync(config, {
352
+ token: base,
353
+ amount: parseUnits('50', 6),
354
+ type: 'buy',
355
+ tick: Tick.fromPrice('1.001'),
356
+ flipTick: Tick.fromPrice('1.002'),
357
+ })
358
+
359
+ // Get the order details
360
+ const order = await Actions.dex.getOrder(config, {
361
+ orderId,
362
+ })
363
+
364
+ expect(order).toBeDefined()
365
+ expect(order.maker).toBe(account.address)
366
+ expect(order.isBid).toBe(true)
367
+ expect(order.tick).toBe(Tick.fromPrice('1.001'))
368
+ expect(order.amount).toBe(parseUnits('50', 6))
369
+ expect(order.isFlip).toBe(true)
370
+ expect(order.flipTick).toBe(Tick.fromPrice('1.002'))
371
+ })
372
+
373
+ test('behavior: fails for non-existent order', async () => {
374
+ await setupTokenPair()
375
+
376
+ // Try to get an order that doesn't exist
377
+ await expect(
378
+ Actions.dex.getOrder(config, {
379
+ orderId: 999n,
380
+ }),
381
+ ).rejects.toThrow('The contract function "getOrder" reverted')
382
+ })
383
+
384
+ test('behavior: reflects order state after partial fill', async () => {
385
+ const { base, quote } = await setupTokenPair()
386
+
387
+ // Place a large sell order
388
+ const { orderId } = await Actions.dex.placeSync(config, {
389
+ token: base,
390
+ amount: parseUnits('500', 6),
391
+ type: 'sell',
392
+ tick: Tick.fromPrice('1.001'),
393
+ })
394
+
395
+ // Get initial order state
396
+ const orderBefore = await Actions.dex.getOrder(config, {
397
+ orderId,
398
+ })
399
+ expect(orderBefore.amount).toBe(parseUnits('500', 6))
400
+ expect(orderBefore.remaining).toBe(parseUnits('500', 6))
401
+
402
+ // Partially fill the order with a buy
403
+ await Actions.dex.buySync(config, {
404
+ tokenIn: quote,
405
+ tokenOut: base,
406
+ amountOut: parseUnits('100', 6),
407
+ maxAmountIn: parseUnits('150', 6),
408
+ })
409
+
410
+ // Get order state after partial fill
411
+ const orderAfter = await Actions.dex.getOrder(config, {
412
+ orderId,
413
+ })
414
+ expect(orderAfter.amount).toBe(parseUnits('500', 6)) // amount unchanged
415
+ expect(orderAfter.remaining).toBeLessThan(parseUnits('500', 6)) // remaining decreased
416
+ })
417
+
418
+ test('behavior: linked list pointers for multiple orders at same tick', async () => {
419
+ const { base } = await setupTokenPair()
420
+
421
+ const tick = Tick.fromPrice('1.001')
422
+
423
+ // Place first order
424
+ const { orderId: orderId1 } = await Actions.dex.placeSync(config, {
425
+ token: base,
426
+ amount: parseUnits('100', 6),
427
+ type: 'buy',
428
+ tick,
429
+ })
430
+
431
+ // Place second order at same tick
432
+ const { orderId: orderId2 } = await Actions.dex.placeSync(config, {
433
+ token: base,
434
+ amount: parseUnits('50', 6),
435
+ type: 'buy',
436
+ tick,
437
+ })
438
+
439
+ // Get first order
440
+ const order1 = await Actions.dex.getOrder(config, {
441
+ orderId: orderId1,
442
+ })
443
+ expect(order1.prev).toBe(0n) // should be 0 as it's first
444
+ expect(order1.next).toBe(orderId2) // should point to second order
445
+
446
+ // Get second order
447
+ const order2 = await Actions.dex.getOrder(config, {
448
+ orderId: orderId2,
449
+ })
450
+ expect(order2.prev).toBe(orderId1) // should point to first order
451
+ expect(order2.next).toBe(0n) // should be 0 as it's last
452
+ })
453
+ })
454
+
455
+ describe('getOrders', () => {
456
+ test('default', async () => {
457
+ const response = await Actions.dex.getOrders(config, {
458
+ limit: 10,
459
+ })
460
+
461
+ expect(response).matchSnapshot()
462
+ })
463
+
464
+ describe('infiniteQueryOptions', () => {
465
+ test('default', async () => {
466
+ const options = Actions.dex.getOrders.infiniteQueryOptions(config, {
467
+ limit: 5,
468
+ query: {
469
+ pages: 2,
470
+ },
471
+ })
472
+
473
+ const result = await queryClient.fetchInfiniteQuery(options)
474
+
475
+ expect(result).matchSnapshot()
476
+ })
477
+ })
478
+ })
479
+
480
+ describe('getOrderbook', () => {
481
+ test('default', async () => {
482
+ const { base, quote } = await setupTokenPair()
483
+
484
+ // Get orderbook information
485
+ const book = await Actions.dex.getOrderbook(config, {
486
+ base,
487
+ quote,
488
+ })
489
+
490
+ expect(book).toBeDefined()
491
+ expect(book.base).toBe(base)
492
+ expect(book.quote).toBe(quote)
493
+ expect(book.bestBidTick).toBeDefined()
494
+ expect(book.bestAskTick).toBeDefined()
495
+ })
496
+
497
+ test('behavior: shows best bid and ask after orders placed', async () => {
498
+ const { base, quote } = await setupTokenPair()
499
+
500
+ const bidTick = Tick.fromPrice('0.999')
501
+ const askTick = Tick.fromPrice('1.001')
502
+
503
+ // Place a bid order
504
+ await Actions.dex.placeSync(config, {
505
+ token: base,
506
+ amount: parseUnits('100', 6),
507
+ type: 'buy',
508
+ tick: bidTick,
509
+ })
510
+
511
+ // Place an ask order
512
+ await Actions.dex.placeSync(config, {
513
+ token: base,
514
+ amount: parseUnits('100', 6),
515
+ type: 'sell',
516
+ tick: askTick,
517
+ })
518
+
519
+ // Get orderbook
520
+ const book = await Actions.dex.getOrderbook(config, {
521
+ base,
522
+ quote,
523
+ })
524
+
525
+ expect(book.bestBidTick).toBe(bidTick)
526
+ expect(book.bestAskTick).toBe(askTick)
527
+ })
528
+
529
+ test('behavior: best ticks update after better orders placed', async () => {
530
+ const { base, quote } = await setupTokenPair()
531
+
532
+ // Place initial bid at 0.999
533
+ await Actions.dex.placeSync(config, {
534
+ token: base,
535
+ amount: parseUnits('100', 6),
536
+ type: 'buy',
537
+ tick: Tick.fromPrice('0.999'),
538
+ })
539
+
540
+ // Get orderbook
541
+ const bookBefore = await Actions.dex.getOrderbook(config, {
542
+ base,
543
+ quote,
544
+ })
545
+ expect(bookBefore.bestBidTick).toBe(Tick.fromPrice('0.999'))
546
+
547
+ // Place better bid at 1.0
548
+ await Actions.dex.placeSync(config, {
549
+ token: base,
550
+ amount: parseUnits('100', 6),
551
+ type: 'buy',
552
+ tick: Tick.fromPrice('1.0'),
553
+ })
554
+
555
+ // Get orderbook again
556
+ const bookAfter = await Actions.dex.getOrderbook(config, {
557
+ base,
558
+ quote,
559
+ })
560
+ expect(bookAfter.bestBidTick).toBe(Tick.fromPrice('1.0'))
561
+ })
562
+
563
+ test.todo('behavior: best ticks update after order cancellation')
564
+
565
+ test('behavior: multiple pairs have independent orderbooks', async () => {
566
+ const { base: base1, quote: quote1 } = await setupTokenPair()
567
+ const { base: base2, quote: quote2 } = await setupTokenPair()
568
+
569
+ // Place order on first pair
570
+ await Actions.dex.placeSync(config, {
571
+ token: base1,
572
+ amount: parseUnits('100', 6),
573
+ type: 'buy',
574
+ tick: Tick.fromPrice('1.001'),
575
+ })
576
+
577
+ // Place order on second pair at different tick
578
+ await Actions.dex.placeSync(config, {
579
+ token: base2,
580
+ amount: parseUnits('100', 6),
581
+ type: 'buy',
582
+ tick: Tick.fromPrice('0.999'),
583
+ })
584
+
585
+ // Get orderbooks
586
+ const book1 = await Actions.dex.getOrderbook(config, {
587
+ base: base1,
588
+ quote: quote1,
589
+ })
590
+
591
+ const book2 = await Actions.dex.getOrderbook(config, {
592
+ base: base2,
593
+ quote: quote2,
594
+ })
595
+
596
+ // Each pair should have its own best tick
597
+ expect(book1.bestBidTick).toBe(Tick.fromPrice('1.001'))
598
+ expect(book2.bestBidTick).toBe(Tick.fromPrice('0.999'))
599
+ })
600
+ })
601
+
602
+ describe('getPriceLevel', () => {
603
+ test('default', async () => {
604
+ const { base } = await setupTokenPair()
605
+
606
+ const tick = Tick.fromPrice('1.001')
607
+
608
+ // Place an order to create liquidity at this tick
609
+ const { orderId } = await Actions.dex.placeSync(config, {
610
+ token: base,
611
+ amount: parseUnits('100', 6),
612
+ type: 'buy',
613
+ tick,
614
+ })
615
+
616
+ // Get the price level
617
+ const level = await Actions.dex.getPriceLevel(config, {
618
+ base,
619
+ tick,
620
+ isBid: true,
621
+ })
622
+
623
+ expect(level).toBeDefined()
624
+ expect(level.head).toBe(orderId) // head should be our order
625
+ expect(level.tail).toBe(orderId) // tail should also be our order (only one)
626
+ expect(level.totalLiquidity).toBeGreaterThan(0n)
627
+ })
628
+
629
+ test('behavior: empty price level', async () => {
630
+ const { base } = await setupTokenPair()
631
+
632
+ const tick = Tick.fromPrice('1.001')
633
+
634
+ // Query a tick with no orders
635
+ const level = await Actions.dex.getPriceLevel(config, {
636
+ base,
637
+ tick,
638
+ isBid: true,
639
+ })
640
+
641
+ expect(level).toBeDefined()
642
+ expect(level.head).toBe(0n)
643
+ expect(level.tail).toBe(0n)
644
+ expect(level.totalLiquidity).toBe(0n)
645
+ })
646
+
647
+ test('behavior: multiple orders at same tick', async () => {
648
+ const { base } = await setupTokenPair()
649
+
650
+ const tick = Tick.fromPrice('1.001')
651
+
652
+ // Place first order
653
+ const { orderId: orderId1 } = await Actions.dex.placeSync(config, {
654
+ token: base,
655
+ amount: parseUnits('100', 6),
656
+ type: 'buy',
657
+ tick,
658
+ })
659
+
660
+ // Place second order at same tick
661
+ const { orderId: orderId2 } = await Actions.dex.placeSync(config, {
662
+ token: base,
663
+ amount: parseUnits('50', 6),
664
+ type: 'buy',
665
+ tick,
666
+ })
667
+
668
+ // Get the price level
669
+ const level = await Actions.dex.getPriceLevel(config, {
670
+ base,
671
+ tick,
672
+ isBid: true,
673
+ })
674
+
675
+ expect(level.head).toBe(orderId1) // head should be first order
676
+ expect(level.tail).toBe(orderId2) // tail should be last order
677
+ // Total liquidity should be sum of both orders (approximately)
678
+ expect(level.totalLiquidity).toBeGreaterThan(parseUnits('145', 6))
679
+ })
680
+
681
+ test('behavior: bid vs ask sides', async () => {
682
+ const { base } = await setupTokenPair()
683
+
684
+ const tick = Tick.fromPrice('1.001')
685
+
686
+ // Place a buy order (bid)
687
+ await Actions.dex.placeSync(config, {
688
+ token: base,
689
+ amount: parseUnits('100', 6),
690
+ type: 'buy',
691
+ tick,
692
+ })
693
+
694
+ // Place a sell order (ask) at same tick
695
+ await Actions.dex.placeSync(config, {
696
+ token: base,
697
+ amount: parseUnits('50', 6),
698
+ type: 'sell',
699
+ tick,
700
+ })
701
+
702
+ // Get bid side
703
+ const bidLevel = await Actions.dex.getPriceLevel(config, {
704
+ base,
705
+ tick,
706
+ isBid: true,
707
+ })
708
+
709
+ // Get ask side
710
+ const askLevel = await Actions.dex.getPriceLevel(config, {
711
+ base,
712
+ tick,
713
+ isBid: false,
714
+ })
715
+
716
+ // Both should have liquidity but different amounts
717
+ expect(bidLevel.totalLiquidity).toBeGreaterThan(0n)
718
+ expect(askLevel.totalLiquidity).toBeGreaterThan(0n)
719
+ expect(bidLevel.head).not.toBe(askLevel.head)
720
+ })
721
+
722
+ test('behavior: liquidity changes after order cancellation', async () => {
723
+ const { base } = await setupTokenPair()
724
+
725
+ const tick = Tick.fromPrice('1.001')
726
+
727
+ // Place orders
728
+ const { orderId: orderId1 } = await Actions.dex.placeSync(config, {
729
+ token: base,
730
+ amount: parseUnits('100', 6),
731
+ type: 'buy',
732
+ tick,
733
+ })
734
+
735
+ await Actions.dex.placeSync(config, {
736
+ token: base,
737
+ amount: parseUnits('50', 6),
738
+ type: 'buy',
739
+ tick,
740
+ })
741
+
742
+ // Get level before cancellation
743
+ const levelBefore = await Actions.dex.getPriceLevel(config, {
744
+ base,
745
+ tick,
746
+ isBid: true,
747
+ })
748
+
749
+ // Cancel first order
750
+ await Actions.dex.cancelSync(config, {
751
+ orderId: orderId1,
752
+ })
753
+
754
+ // Get level after cancellation
755
+ const levelAfter = await Actions.dex.getPriceLevel(config, {
756
+ base,
757
+ tick,
758
+ isBid: true,
759
+ })
760
+
761
+ // Total liquidity should decrease
762
+ expect(levelAfter.totalLiquidity).toBeLessThan(levelBefore.totalLiquidity)
763
+ })
764
+
765
+ test('behavior: liquidity changes after partial fill', async () => {
766
+ const { base, quote } = await setupTokenPair()
767
+
768
+ const tick = Tick.fromPrice('1.001')
769
+
770
+ // Place sell order
771
+ await Actions.dex.placeSync(config, {
772
+ token: base,
773
+ amount: parseUnits('500', 6),
774
+ type: 'sell',
775
+ tick,
776
+ })
777
+
778
+ // Get level before fill
779
+ const levelBefore = await Actions.dex.getPriceLevel(config, {
780
+ base,
781
+ tick,
782
+ isBid: false,
783
+ })
784
+
785
+ // Partially fill the order
786
+ await Actions.dex.buySync(config, {
787
+ tokenIn: quote,
788
+ tokenOut: base,
789
+ amountOut: parseUnits('100', 6),
790
+ maxAmountIn: parseUnits('150', 6),
791
+ })
792
+
793
+ // Get level after fill
794
+ const levelAfter = await Actions.dex.getPriceLevel(config, {
795
+ base,
796
+ tick,
797
+ isBid: false,
798
+ })
799
+
800
+ // Total liquidity should decrease
801
+ expect(levelAfter.totalLiquidity).toBeLessThan(levelBefore.totalLiquidity)
802
+ })
803
+
804
+ test('behavior: tick at boundaries', async () => {
805
+ const { base } = await setupTokenPair()
806
+
807
+ // Place order at min tick
808
+ await Actions.dex.placeSync(config, {
809
+ token: base,
810
+ amount: parseUnits('10', 6),
811
+ type: 'sell',
812
+ tick: Tick.minTick,
813
+ })
814
+
815
+ // Query min tick
816
+ const minLevel = await Actions.dex.getPriceLevel(config, {
817
+ base,
818
+ tick: Tick.minTick,
819
+ isBid: false,
820
+ })
821
+ expect(minLevel.totalLiquidity).toBeGreaterThan(0n)
822
+
823
+ // Place order at max tick
824
+ await Actions.dex.placeSync(config, {
825
+ token: base,
826
+ amount: parseUnits('10', 6),
827
+ type: 'buy',
828
+ tick: Tick.maxTick,
829
+ })
830
+
831
+ // Query max tick
832
+ const maxLevel = await Actions.dex.getPriceLevel(config, {
833
+ base,
834
+ tick: Tick.maxTick,
835
+ isBid: true,
836
+ })
837
+ expect(maxLevel.totalLiquidity).toBeGreaterThan(0n)
838
+ })
839
+ })
840
+
841
+ describe('getSellQuote', () => {
842
+ test('default', async () => {
843
+ const { base, quote } = await setupTokenPair()
844
+
845
+ // Place bid orders to create liquidity
846
+ await Actions.dex.placeSync(config, {
847
+ token: base,
848
+ amount: parseUnits('500', 6),
849
+ type: 'buy',
850
+ tick: Tick.fromPrice('0.999'),
851
+ })
852
+
853
+ // Get quote for selling base tokens
854
+ const amountOut = await Actions.dex.getSellQuote(config, {
855
+ tokenIn: base,
856
+ tokenOut: quote,
857
+ amountIn: parseUnits('100', 6),
858
+ })
859
+
860
+ expect(amountOut).toBeGreaterThan(0n)
861
+ // Should be approximately 100 * 0.999 = 99.9
862
+ expect(amountOut).toBeLessThan(parseUnits('100', 6))
863
+ })
864
+
865
+ test('behavior: fails with no liquidity', async () => {
866
+ const { base, quote } = await setupTokenPair()
867
+
868
+ // Quote should fail with no liquidity
869
+ await expect(
870
+ Actions.dex.getSellQuote(config, {
871
+ tokenIn: base,
872
+ tokenOut: quote,
873
+ amountIn: parseUnits('100', 6),
874
+ }),
875
+ ).rejects.toThrow('The contract function "quoteSwapExactAmountIn" reverted')
876
+ })
877
+ })
878
+
879
+ describe('place', () => {
880
+ test('default', async () => {
881
+ // Setup token pair
882
+ const { base } = await setupTokenPair()
883
+
884
+ // Place a sell order
885
+ const { receipt, orderId, token, ...result } = await Actions.dex.placeSync(
886
+ config,
887
+ {
888
+ token: base,
889
+ amount: parseUnits('100', 6),
890
+ type: 'sell',
891
+ tick: Tick.fromPrice('1.001'),
892
+ },
893
+ )
894
+
895
+ expect(receipt).toBeDefined()
896
+ expect(receipt.status).toBe('success')
897
+ expect(orderId).toBeGreaterThan(0n)
898
+ expect(token).toBe(base)
899
+ expect(result).toMatchInlineSnapshot(`
900
+ {
901
+ "amount": 100000000n,
902
+ "isBid": false,
903
+ "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
904
+ "tick": 100,
905
+ }
906
+ `)
907
+
908
+ // Place a buy order
909
+ const {
910
+ receipt: receipt2,
911
+ orderId: orderId2,
912
+ token: token2,
913
+ ...result2
914
+ } = await Actions.dex.placeSync(config, {
915
+ token: base,
916
+ amount: parseUnits('100', 6),
917
+ type: 'buy',
918
+ tick: Tick.fromPrice('1.001'),
919
+ })
920
+ expect(receipt2.status).toBe('success')
921
+ expect(orderId2).toBeGreaterThan(0n)
922
+ expect(token2).toBe(base)
923
+ expect(result2).toMatchInlineSnapshot(`
924
+ {
925
+ "amount": 100000000n,
926
+ "isBid": true,
927
+ "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
928
+ "tick": 100,
929
+ }
930
+ `)
931
+ })
932
+
933
+ test('behavior: tick at boundaries', async () => {
934
+ const { base } = await setupTokenPair()
935
+
936
+ // Test at min tick (-2000)
937
+ const { receipt: receipt1, ...result1 } = await Actions.dex.placeSync(
938
+ config,
939
+ {
940
+ token: base,
941
+ amount: parseUnits('10', 6),
942
+ type: 'sell',
943
+ tick: Tick.minTick,
944
+ },
945
+ )
946
+ expect(receipt1.status).toBe('success')
947
+ expect(result1.tick).toBe(-2000)
948
+
949
+ // Test at max tick (2000)
950
+ const { receipt: receipt2, ...result2 } = await Actions.dex.placeSync(
951
+ config,
952
+ {
953
+ token: base,
954
+ amount: parseUnits('10', 6),
955
+ type: 'buy',
956
+ tick: Tick.maxTick,
957
+ },
958
+ )
959
+ expect(receipt2.status).toBe('success')
960
+ expect(result2.tick).toBe(2000)
961
+ })
962
+
963
+ test('behavior: tick validation fails outside bounds', async () => {
964
+ const { base } = await setupTokenPair()
965
+
966
+ // Test tick above max tick should fail
967
+ await expect(
968
+ Actions.dex.placeSync(config, {
969
+ token: base,
970
+ amount: parseUnits('10', 6),
971
+ type: 'buy',
972
+ tick: Tick.maxTick + 1,
973
+ }),
974
+ ).rejects.toThrow('The contract function "place" reverted')
975
+
976
+ // Test tick below min tick should fail
977
+ await expect(
978
+ Actions.dex.placeSync(config, {
979
+ token: base,
980
+ amount: parseUnits('10', 6),
981
+ type: 'sell',
982
+ tick: Tick.minTick - 1,
983
+ }),
984
+ ).rejects.toThrow('The contract function "place" reverted')
985
+ })
986
+
987
+ test('behavior: transfers from wallet', async () => {
988
+ const { base, quote } = await setupTokenPair()
989
+
990
+ // Get balances before placing order
991
+ const baseBalanceBefore = await Actions.token.getBalance(config, {
992
+ token: base,
993
+ account: account.address,
994
+ })
995
+ const quoteBalanceBefore = await Actions.token.getBalance(config, {
996
+ token: quote,
997
+ account: account.address,
998
+ })
999
+
1000
+ // Place a buy order - should transfer quote tokens to escrow
1001
+ const orderAmount = parseUnits('100', 6)
1002
+ const tick = Tick.fromPrice('1.001')
1003
+ await Actions.dex.placeSync(config, {
1004
+ token: base,
1005
+ amount: orderAmount,
1006
+ type: 'buy',
1007
+ tick,
1008
+ })
1009
+
1010
+ // Get balances after placing order
1011
+ const baseBalanceAfter = await Actions.token.getBalance(config, {
1012
+ token: base,
1013
+ account: account.address,
1014
+ })
1015
+ const quoteBalanceAfter = await Actions.token.getBalance(config, {
1016
+ token: quote,
1017
+ account: account.address,
1018
+ })
1019
+
1020
+ // Base token balance should be unchanged (we're buying base, not selling)
1021
+ expect(baseBalanceAfter).toBe(baseBalanceBefore)
1022
+
1023
+ // Quote token balance should decrease (escrowed for the bid)
1024
+ // Amount = orderAmount * (1 + tick/1000) for bids
1025
+ const expectedQuoteEscrowed =
1026
+ (orderAmount * BigInt(100000 + tick)) / BigInt(100000)
1027
+ expect(quoteBalanceBefore - quoteBalanceAfter).toBeGreaterThanOrEqual(
1028
+ expectedQuoteEscrowed,
1029
+ )
1030
+ })
1031
+
1032
+ test('behavior: multiple orders at same tick', async () => {
1033
+ const { base } = await setupTokenPair()
1034
+
1035
+ const tick = Tick.fromPrice('1.0005')
1036
+
1037
+ // Place first order
1038
+ const { orderId: orderId1 } = await Actions.dex.placeSync(config, {
1039
+ token: base,
1040
+ amount: parseUnits('100', 6),
1041
+ type: 'buy',
1042
+ tick,
1043
+ })
1044
+
1045
+ // Place second order at same tick
1046
+ const { orderId: orderId2 } = await Actions.dex.placeSync(config, {
1047
+ token: base,
1048
+ amount: parseUnits('50', 6),
1049
+ type: 'buy',
1050
+ tick,
1051
+ })
1052
+
1053
+ // Order IDs should be different and sequential
1054
+ expect(orderId2).toBeGreaterThan(orderId1)
1055
+ })
1056
+ })
1057
+
1058
+ describe('placeFlip', () => {
1059
+ test('default', async () => {
1060
+ const { base } = await setupTokenPair()
1061
+
1062
+ // Place a flip bid order
1063
+ const { receipt, orderId, token, ...result } =
1064
+ await Actions.dex.placeFlipSync(config, {
1065
+ token: base,
1066
+ amount: parseUnits('100', 6),
1067
+ type: 'buy',
1068
+ tick: Tick.fromPrice('1.001'),
1069
+ flipTick: Tick.fromPrice('1.002'),
1070
+ })
1071
+
1072
+ expect(receipt).toBeDefined()
1073
+ expect(receipt.status).toBe('success')
1074
+ expect(orderId).toBeGreaterThan(0n)
1075
+ expect(token).toBe(base)
1076
+ expect(result.flipTick).toBe(Tick.fromPrice('1.002'))
1077
+ expect(result).toMatchInlineSnapshot(`
1078
+ {
1079
+ "amount": 100000000n,
1080
+ "flipTick": 200,
1081
+ "isBid": true,
1082
+ "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1083
+ "tick": 100,
1084
+ }
1085
+ `)
1086
+ })
1087
+
1088
+ test('behavior: flip bid requires flipTick > tick', async () => {
1089
+ const { base } = await setupTokenPair()
1090
+
1091
+ // Valid: flipTick > tick for bid
1092
+ const { receipt: receipt1 } = await Actions.dex.placeFlipSync(config, {
1093
+ token: base,
1094
+ amount: parseUnits('10', 6),
1095
+ type: 'buy',
1096
+ tick: Tick.fromPrice('1.0005'),
1097
+ flipTick: Tick.fromPrice('1.001'),
1098
+ })
1099
+ expect(receipt1.status).toBe('success')
1100
+
1101
+ // Invalid: flipTick <= tick for bid should fail
1102
+ await expect(
1103
+ Actions.dex.placeFlipSync(config, {
1104
+ token: base,
1105
+ amount: parseUnits('10', 6),
1106
+ type: 'buy',
1107
+ tick: Tick.fromPrice('1.001'),
1108
+ flipTick: Tick.fromPrice('1.001'), // Equal
1109
+ }),
1110
+ ).rejects.toThrow('The contract function "placeFlip" reverted')
1111
+
1112
+ await expect(
1113
+ Actions.dex.placeFlipSync(config, {
1114
+ token: base,
1115
+ amount: parseUnits('10', 6),
1116
+ type: 'buy',
1117
+ tick: Tick.fromPrice('1.001'),
1118
+ flipTick: Tick.fromPrice('1.0005'), // Less than
1119
+ }),
1120
+ ).rejects.toThrow('The contract function "placeFlip" reverted')
1121
+ })
1122
+
1123
+ test('behavior: flip ask requires flipTick < tick', async () => {
1124
+ const { base } = await setupTokenPair()
1125
+
1126
+ // Valid: flipTick < tick for ask
1127
+ const { receipt: receipt1 } = await Actions.dex.placeFlipSync(config, {
1128
+ token: base,
1129
+ amount: parseUnits('10', 6),
1130
+ type: 'sell',
1131
+ tick: Tick.fromPrice('1.001'),
1132
+ flipTick: Tick.fromPrice('1.0005'),
1133
+ })
1134
+ expect(receipt1.status).toBe('success')
1135
+
1136
+ // Invalid: flipTick >= tick for ask should fail
1137
+ await expect(
1138
+ Actions.dex.placeFlipSync(config, {
1139
+ token: base,
1140
+ amount: parseUnits('10', 6),
1141
+ type: 'sell',
1142
+ tick: Tick.fromPrice('1.0005'),
1143
+ flipTick: Tick.fromPrice('1.0005'), // Equal
1144
+ }),
1145
+ ).rejects.toThrow('The contract function "placeFlip" reverted')
1146
+
1147
+ await expect(
1148
+ Actions.dex.placeFlipSync(config, {
1149
+ token: base,
1150
+ amount: parseUnits('10', 6),
1151
+ type: 'sell',
1152
+ tick: Tick.fromPrice('1.0005'),
1153
+ flipTick: Tick.fromPrice('1.001'), // Greater than
1154
+ }),
1155
+ ).rejects.toThrow('The contract function "placeFlip" reverted')
1156
+ })
1157
+
1158
+ test('behavior: flip ticks at boundaries', async () => {
1159
+ const { base } = await setupTokenPair()
1160
+
1161
+ // Flip order with ticks at extreme boundaries
1162
+ const { receipt } = await Actions.dex.placeFlipSync(config, {
1163
+ token: base,
1164
+ amount: parseUnits('10', 6),
1165
+ type: 'buy',
1166
+ tick: Tick.minTick,
1167
+ flipTick: Tick.maxTick,
1168
+ })
1169
+ expect(receipt.status).toBe('success')
1170
+ })
1171
+ })
1172
+
1173
+ describe('sell', () => {
1174
+ test('default', async () => {
1175
+ const { base, quote } = await setupTokenPair()
1176
+
1177
+ // Place bid order to create liquidity
1178
+ await Actions.dex.placeSync(config, {
1179
+ token: base,
1180
+ amount: parseUnits('500', 6),
1181
+ type: 'buy',
1182
+ tick: Tick.fromPrice('0.999'),
1183
+ })
1184
+
1185
+ // Sell base tokens
1186
+ const { receipt } = await Actions.dex.sellSync(config, {
1187
+ tokenIn: base,
1188
+ tokenOut: quote,
1189
+ amountIn: parseUnits('100', 6),
1190
+ minAmountOut: parseUnits('50', 6),
1191
+ })
1192
+
1193
+ expect(receipt).toBeDefined()
1194
+ expect(receipt.status).toBe('success')
1195
+ })
1196
+
1197
+ test('behavior: respects minAmountOut', async () => {
1198
+ const { base, quote } = await setupTokenPair()
1199
+
1200
+ // Place bid order at low price
1201
+ await Actions.dex.placeSync(config, {
1202
+ token: base,
1203
+ amount: parseUnits('500', 6),
1204
+ type: 'buy',
1205
+ tick: Tick.fromPrice('0.99'), // 1% below peg
1206
+ })
1207
+
1208
+ // Try to sell with too high minAmountOut - should fail
1209
+ await expect(
1210
+ Actions.dex.sellSync(config, {
1211
+ tokenIn: base,
1212
+ tokenOut: quote,
1213
+ amountIn: parseUnits('100', 6),
1214
+ minAmountOut: parseUnits('150', 6), // Way too high
1215
+ }),
1216
+ ).rejects.toThrow('The contract function "swapExactAmountIn" reverted')
1217
+ })
1218
+
1219
+ test('behavior: fails with insufficient liquidity', async () => {
1220
+ const { base, quote } = await setupTokenPair()
1221
+
1222
+ // No orders - no liquidity
1223
+
1224
+ // Try to sell - should fail
1225
+ await expect(
1226
+ Actions.dex.sellSync(config, {
1227
+ tokenIn: base,
1228
+ tokenOut: quote,
1229
+ amountIn: parseUnits('100', 6),
1230
+ minAmountOut: parseUnits('50', 6),
1231
+ }),
1232
+ ).rejects.toThrow('The contract function "swapExactAmountIn" reverted')
1233
+ })
1234
+ })
1235
+
1236
+ describe('watchFlipOrderPlaced', () => {
1237
+ test('default', async () => {
1238
+ const { base } = await setupTokenPair()
1239
+
1240
+ const receivedOrders: Array<{
1241
+ args: Actions.dex.watchFlipOrderPlaced.Args
1242
+ log: Actions.dex.watchFlipOrderPlaced.Log
1243
+ }> = []
1244
+
1245
+ const unwatch = Actions.dex.watchFlipOrderPlaced(config, {
1246
+ onFlipOrderPlaced: (args, log) => {
1247
+ receivedOrders.push({ args, log })
1248
+ },
1249
+ })
1250
+
1251
+ try {
1252
+ // Place flip order
1253
+ await Actions.dex.placeFlipSync(config, {
1254
+ token: base,
1255
+ amount: parseUnits('100', 6),
1256
+ type: 'buy',
1257
+ tick: Tick.fromPrice('1.001'),
1258
+ flipTick: Tick.fromPrice('1.002'),
1259
+ })
1260
+
1261
+ await new Promise((resolve) => setTimeout(resolve, 200))
1262
+
1263
+ expect(receivedOrders).toHaveLength(1)
1264
+ expect(receivedOrders[0]?.args.flipTick).toBe(Tick.fromPrice('1.002'))
1265
+ expect(receivedOrders[0]?.args.tick).toBe(Tick.fromPrice('1.001'))
1266
+ } finally {
1267
+ unwatch()
1268
+ }
1269
+ })
1270
+ })
1271
+
1272
+ describe('watchOrderCancelled', () => {
1273
+ test('default', async () => {
1274
+ const { base } = await setupTokenPair()
1275
+
1276
+ const receivedCancellations: Array<{
1277
+ args: Actions.dex.watchOrderCancelled.Args
1278
+ log: Actions.dex.watchOrderCancelled.Log
1279
+ }> = []
1280
+
1281
+ const unwatch = Actions.dex.watchOrderCancelled(config, {
1282
+ onOrderCancelled: (args, log) => {
1283
+ receivedCancellations.push({ args, log })
1284
+ },
1285
+ })
1286
+
1287
+ try {
1288
+ // Place order
1289
+ const { orderId } = await Actions.dex.placeSync(config, {
1290
+ token: base,
1291
+ amount: parseUnits('100', 6),
1292
+ type: 'buy',
1293
+ tick: Tick.fromPrice('1.001'),
1294
+ })
1295
+
1296
+ // Cancel order
1297
+ await Actions.dex.cancelSync(config, {
1298
+ orderId,
1299
+ })
1300
+
1301
+ await new Promise((resolve) => setTimeout(resolve, 200))
1302
+
1303
+ expect(receivedCancellations).toHaveLength(1)
1304
+ expect(receivedCancellations[0]?.args.orderId).toBe(orderId)
1305
+ } finally {
1306
+ unwatch()
1307
+ }
1308
+ })
1309
+
1310
+ test('behavior: filter by orderId', async () => {
1311
+ const { base } = await setupTokenPair()
1312
+
1313
+ // Place two orders
1314
+ const { orderId: orderId1 } = await Actions.dex.placeSync(config, {
1315
+ token: base,
1316
+ amount: parseUnits('100', 6),
1317
+ type: 'buy',
1318
+ tick: Tick.fromPrice('1.001'),
1319
+ })
1320
+
1321
+ const { orderId: orderId2 } = await Actions.dex.placeSync(config, {
1322
+ token: base,
1323
+ amount: parseUnits('50', 6),
1324
+ type: 'buy',
1325
+ tick: Tick.fromPrice('1.001'),
1326
+ })
1327
+
1328
+ const receivedCancellations: Array<{
1329
+ args: Actions.dex.watchOrderCancelled.Args
1330
+ log: Actions.dex.watchOrderCancelled.Log
1331
+ }> = []
1332
+
1333
+ // Watch only for cancellation of orderId1
1334
+ const unwatch = Actions.dex.watchOrderCancelled(config, {
1335
+ orderId: orderId1,
1336
+ onOrderCancelled: (args, log) => {
1337
+ receivedCancellations.push({ args, log })
1338
+ },
1339
+ })
1340
+
1341
+ try {
1342
+ // Cancel orderId1 (should be captured)
1343
+ await Actions.dex.cancelSync(config, {
1344
+ orderId: orderId1,
1345
+ })
1346
+
1347
+ // Cancel orderId2 (should NOT be captured)
1348
+ await Actions.dex.cancelSync(config, {
1349
+ orderId: orderId2,
1350
+ })
1351
+
1352
+ await new Promise((resolve) => setTimeout(resolve, 200))
1353
+
1354
+ // Should only receive 1 event
1355
+ expect(receivedCancellations).toHaveLength(1)
1356
+ expect(receivedCancellations[0]?.args.orderId).toBe(orderId1)
1357
+ } finally {
1358
+ unwatch()
1359
+ }
1360
+ })
1361
+ })
1362
+
1363
+ describe.todo('watchOrderFilled')
1364
+
1365
+ describe('watchOrderPlaced', () => {
1366
+ test('default', async () => {
1367
+ const { base } = await setupTokenPair()
1368
+
1369
+ const receivedOrders: Array<{
1370
+ args: Actions.dex.watchOrderPlaced.Args
1371
+ log: Actions.dex.watchOrderPlaced.Log
1372
+ }> = []
1373
+
1374
+ const unwatch = Actions.dex.watchOrderPlaced(config, {
1375
+ onOrderPlaced: (args, log) => {
1376
+ receivedOrders.push({ args, log })
1377
+ },
1378
+ })
1379
+
1380
+ try {
1381
+ // Place first order
1382
+ await Actions.dex.placeSync(config, {
1383
+ token: base,
1384
+ amount: parseUnits('100', 6),
1385
+ type: 'buy',
1386
+ tick: Tick.fromPrice('1.001'),
1387
+ })
1388
+
1389
+ // Place second order
1390
+ await Actions.dex.placeSync(config, {
1391
+ token: base,
1392
+ amount: parseUnits('50', 6),
1393
+ type: 'sell',
1394
+ tick: Tick.fromPrice('0.999'),
1395
+ })
1396
+
1397
+ // Wait for events
1398
+ await new Promise((resolve) => setTimeout(resolve, 200))
1399
+
1400
+ expect(receivedOrders).toHaveLength(2)
1401
+ expect(receivedOrders[0]?.args.isBid).toBe(true)
1402
+ expect(receivedOrders[0]?.args.amount).toBe(parseUnits('100', 6))
1403
+ expect(receivedOrders[1]?.args.isBid).toBe(false)
1404
+ expect(receivedOrders[1]?.args.amount).toBe(parseUnits('50', 6))
1405
+ } finally {
1406
+ unwatch()
1407
+ }
1408
+ })
1409
+
1410
+ test('behavior: filter by token', async () => {
1411
+ const { base } = await setupTokenPair()
1412
+ const { base: base2 } = await setupTokenPair()
1413
+
1414
+ const receivedOrders: Array<{
1415
+ args: Actions.dex.watchOrderPlaced.Args
1416
+ log: Actions.dex.watchOrderPlaced.Log
1417
+ }> = []
1418
+
1419
+ // Watch only for orders on base
1420
+ const unwatch = Actions.dex.watchOrderPlaced(config, {
1421
+ token: base,
1422
+ onOrderPlaced: (args, log) => {
1423
+ receivedOrders.push({ args, log })
1424
+ },
1425
+ })
1426
+
1427
+ try {
1428
+ // Place order on base (should be captured)
1429
+ await Actions.dex.placeSync(config, {
1430
+ token: base,
1431
+ amount: parseUnits('100', 6),
1432
+ type: 'buy',
1433
+ tick: Tick.fromPrice('1.001'),
1434
+ })
1435
+
1436
+ // Place order on base2 (should NOT be captured)
1437
+ await Actions.dex.placeSync(config, {
1438
+ token: base2,
1439
+ amount: parseUnits('50', 6),
1440
+ type: 'buy',
1441
+ tick: Tick.fromPrice('1.001'),
1442
+ })
1443
+
1444
+ await new Promise((resolve) => setTimeout(resolve, 200))
1445
+
1446
+ // Should only receive 1 event
1447
+ expect(receivedOrders).toHaveLength(1)
1448
+ expect(receivedOrders[0]?.args.token.toLowerCase()).toBe(
1449
+ base.toLowerCase(),
1450
+ )
1451
+ } finally {
1452
+ unwatch()
1453
+ }
1454
+ })
1455
+ })
1456
+
1457
+ describe('withdraw', () => {
1458
+ test('default', async () => {
1459
+ const { base, quote } = await setupTokenPair()
1460
+
1461
+ // Create internal balance
1462
+ const { orderId } = await Actions.dex.placeSync(config, {
1463
+ token: base,
1464
+ amount: parseUnits('100', 6),
1465
+ type: 'buy',
1466
+ tick: Tick.fromPrice('1.001'),
1467
+ })
1468
+
1469
+ await Actions.dex.cancelSync(config, { orderId })
1470
+
1471
+ // Get DEX balance
1472
+ const dexBalance = await Actions.dex.getBalance(config, {
1473
+ account: account.address,
1474
+ token: quote,
1475
+ })
1476
+ expect(dexBalance).toBeGreaterThan(0n)
1477
+
1478
+ // Get wallet balance before withdraw
1479
+ const walletBalanceBefore = await Actions.token.getBalance(config, {
1480
+ token: quote,
1481
+ account: account.address,
1482
+ })
1483
+
1484
+ // Withdraw from DEX
1485
+ const { receipt } = await Actions.dex.withdrawSync(config, {
1486
+ token: quote,
1487
+ amount: dexBalance,
1488
+ })
1489
+
1490
+ expect(receipt).toBeDefined()
1491
+ expect(receipt.status).toBe('success')
1492
+
1493
+ // Check DEX balance is now 0
1494
+ const dexBalanceAfter = await Actions.dex.getBalance(config, {
1495
+ account: account.address,
1496
+ token: quote,
1497
+ })
1498
+ expect(dexBalanceAfter).toBe(0n)
1499
+
1500
+ // Check wallet balance increased
1501
+ const walletBalanceAfter = await Actions.token.getBalance(config, {
1502
+ token: quote,
1503
+ account: account.address,
1504
+ })
1505
+ expect(walletBalanceAfter).toBeGreaterThan(walletBalanceBefore)
1506
+ })
1507
+ })