@clober/v2-sdk 0.0.1-a → 0.0.1-b

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/.eslintignore +1 -0
  2. package/.eslintrc.yaml +34 -0
  3. package/.github/workflows/ci.yaml +88 -0
  4. package/.github/workflows/deployer.yaml +19 -0
  5. package/.nvmrc +1 -0
  6. package/.prettierignore +6 -0
  7. package/.prettierrc +7 -0
  8. package/package.json +8 -4
  9. package/src/.graphclient/index.ts +1695 -0
  10. package/src/.graphclient/schema.graphql +1116 -0
  11. package/src/.graphclient/sources/clober-v2/introspectionSchema.ts +14481 -0
  12. package/src/.graphclient/sources/clober-v2/schema.graphql +1116 -0
  13. package/src/.graphclient/sources/clober-v2/types.ts +1133 -0
  14. package/src/.graphclientrc.yml +12 -0
  15. package/src/abis/core/controller-abi.ts +985 -0
  16. package/src/abis/core/params-abi.ts +59 -0
  17. package/src/apis/currency.ts +92 -0
  18. package/src/apis/graphql/books.graphql +25 -0
  19. package/src/apis/graphql/open-order.graphql +29 -0
  20. package/src/apis/graphql/open-orders.graphql +29 -0
  21. package/src/apis/market.ts +85 -0
  22. package/src/apis/open-order.ts +145 -0
  23. package/src/approval.ts +82 -0
  24. package/src/call.ts +705 -0
  25. package/src/constants/action.ts +9 -0
  26. package/src/constants/addresses.ts +17 -0
  27. package/src/constants/chain.ts +12 -0
  28. package/src/constants/currency.ts +15 -0
  29. package/src/constants/fee.ts +4 -0
  30. package/src/constants/price.ts +3 -0
  31. package/src/constants/subgraph-url.ts +8 -0
  32. package/src/index.ts +5 -0
  33. package/src/model/book.ts +166 -0
  34. package/src/model/currency.ts +6 -0
  35. package/src/model/depth.ts +11 -0
  36. package/src/model/fee-policy.ts +51 -0
  37. package/src/model/market.ts +320 -0
  38. package/src/model/open-order.ts +16 -0
  39. package/src/signature.ts +196 -0
  40. package/src/type.ts +38 -0
  41. package/src/utils/approval.ts +48 -0
  42. package/{dist/esm/utils/book-id.js → src/utils/book-id.ts} +19 -9
  43. package/src/utils/build-transaction.ts +39 -0
  44. package/src/utils/decimals.ts +22 -0
  45. package/src/utils/market.ts +74 -0
  46. package/src/utils/math.ts +117 -0
  47. package/src/utils/prices.ts +29 -0
  48. package/src/utils/tick.ts +104 -0
  49. package/src/utils/time.ts +6 -0
  50. package/src/utils/unit.ts +43 -0
  51. package/src/view.ts +258 -0
  52. package/test/book-id.test.ts +101 -0
  53. package/test/fee-policy.test.ts +228 -0
  54. package/test/get-expected-input.test.ts +206 -0
  55. package/test/get-expected-output.test.ts +206 -0
  56. package/test/limit-order.test.ts +279 -0
  57. package/test/market-order.test.ts +245 -0
  58. package/test/market.test.ts +68 -0
  59. package/test/math.test.ts +91 -0
  60. package/test/open-order.test.ts +112 -0
  61. package/test/open.test.ts +15 -0
  62. package/test/tick.test.ts +230 -0
  63. package/test/tsconfig.json +12 -0
  64. package/test/utils/chain.ts +12 -0
  65. package/test/utils/constants.ts +25 -0
  66. package/test/utils/currency.ts +44 -0
  67. package/test/utils/depth.ts +148 -0
  68. package/test/utils/test-chain.ts +26 -0
  69. package/test/vitest.config.ts +15 -0
  70. package/tsconfig.base.json +37 -0
  71. package/tsconfig.build.json +9 -0
  72. package/tsconfig.json +9 -0
  73. package/dist/cjs/.graphclient/index.js +0 -243
  74. package/dist/cjs/.graphclient/index.js.map +0 -1
  75. package/dist/cjs/.graphclient/sources/clober-v2/introspectionSchema.js +0 -14482
  76. package/dist/cjs/.graphclient/sources/clober-v2/introspectionSchema.js.map +0 -1
  77. package/dist/cjs/.graphclient/sources/clober-v2/types.js +0 -4
  78. package/dist/cjs/.graphclient/sources/clober-v2/types.js.map +0 -1
  79. package/dist/cjs/abis/core/controller-abi.js +0 -989
  80. package/dist/cjs/abis/core/controller-abi.js.map +0 -1
  81. package/dist/cjs/abis/core/params-abi.js +0 -62
  82. package/dist/cjs/abis/core/params-abi.js.map +0 -1
  83. package/dist/cjs/apis/currency.js +0 -87
  84. package/dist/cjs/apis/currency.js.map +0 -1
  85. package/dist/cjs/apis/market.js +0 -63
  86. package/dist/cjs/apis/market.js.map +0 -1
  87. package/dist/cjs/apis/open-order.js +0 -90
  88. package/dist/cjs/apis/open-order.js.map +0 -1
  89. package/dist/cjs/approval.js +0 -74
  90. package/dist/cjs/approval.js.map +0 -1
  91. package/dist/cjs/call.js +0 -545
  92. package/dist/cjs/call.js.map +0 -1
  93. package/dist/cjs/constants/action.js +0 -14
  94. package/dist/cjs/constants/action.js.map +0 -1
  95. package/dist/cjs/constants/addresses.js +0 -13
  96. package/dist/cjs/constants/addresses.js.map +0 -1
  97. package/dist/cjs/constants/chain.js +0 -13
  98. package/dist/cjs/constants/chain.js.map +0 -1
  99. package/dist/cjs/constants/currency.js +0 -12
  100. package/dist/cjs/constants/currency.js.map +0 -1
  101. package/dist/cjs/constants/fee.js +0 -7
  102. package/dist/cjs/constants/fee.js.map +0 -1
  103. package/dist/cjs/constants/price.js +0 -6
  104. package/dist/cjs/constants/price.js.map +0 -1
  105. package/dist/cjs/constants/subgraph-url.js +0 -8
  106. package/dist/cjs/constants/subgraph-url.js.map +0 -1
  107. package/dist/cjs/index.js +0 -22
  108. package/dist/cjs/index.js.map +0 -1
  109. package/dist/cjs/model/book.js +0 -130
  110. package/dist/cjs/model/book.js.map +0 -1
  111. package/dist/cjs/model/currency.js +0 -3
  112. package/dist/cjs/model/currency.js.map +0 -1
  113. package/dist/cjs/model/depth.js +0 -3
  114. package/dist/cjs/model/depth.js.map +0 -1
  115. package/dist/cjs/model/fee-policy.js +0 -42
  116. package/dist/cjs/model/fee-policy.js.map +0 -1
  117. package/dist/cjs/model/market.js +0 -215
  118. package/dist/cjs/model/market.js.map +0 -1
  119. package/dist/cjs/model/open-order.js +0 -3
  120. package/dist/cjs/model/open-order.js.map +0 -1
  121. package/dist/cjs/package.json +0 -1
  122. package/dist/cjs/signature.js +0 -178
  123. package/dist/cjs/signature.js.map +0 -1
  124. package/dist/cjs/tsconfig.build.tsbuildinfo +0 -1
  125. package/dist/cjs/type.js +0 -6
  126. package/dist/cjs/type.js.map +0 -1
  127. package/dist/cjs/utils/approval.js +0 -46
  128. package/dist/cjs/utils/approval.js.map +0 -1
  129. package/dist/cjs/utils/book-id.js +0 -25
  130. package/dist/cjs/utils/book-id.js.map +0 -1
  131. package/dist/cjs/utils/build-transaction.js +0 -31
  132. package/dist/cjs/utils/build-transaction.js.map +0 -1
  133. package/dist/cjs/utils/decimals.js +0 -17
  134. package/dist/cjs/utils/decimals.js.map +0 -1
  135. package/dist/cjs/utils/market.js +0 -53
  136. package/dist/cjs/utils/market.js.map +0 -1
  137. package/dist/cjs/utils/math.js +0 -83
  138. package/dist/cjs/utils/math.js.map +0 -1
  139. package/dist/cjs/utils/prices.js +0 -22
  140. package/dist/cjs/utils/prices.js.map +0 -1
  141. package/dist/cjs/utils/tick.js +0 -106
  142. package/dist/cjs/utils/tick.js.map +0 -1
  143. package/dist/cjs/utils/time.js +0 -9
  144. package/dist/cjs/utils/time.js.map +0 -1
  145. package/dist/cjs/utils/unit.js +0 -38
  146. package/dist/cjs/utils/unit.js.map +0 -1
  147. package/dist/cjs/view.js +0 -185
  148. package/dist/cjs/view.js.map +0 -1
  149. package/dist/esm/.graphclient/index.js +0 -233
  150. package/dist/esm/.graphclient/index.js.map +0 -1
  151. package/dist/esm/.graphclient/sources/clober-v2/introspectionSchema.js +0 -14480
  152. package/dist/esm/.graphclient/sources/clober-v2/introspectionSchema.js.map +0 -1
  153. package/dist/esm/.graphclient/sources/clober-v2/types.js +0 -3
  154. package/dist/esm/.graphclient/sources/clober-v2/types.js.map +0 -1
  155. package/dist/esm/abis/core/controller-abi.js +0 -986
  156. package/dist/esm/abis/core/controller-abi.js.map +0 -1
  157. package/dist/esm/abis/core/params-abi.js +0 -59
  158. package/dist/esm/abis/core/params-abi.js.map +0 -1
  159. package/dist/esm/apis/currency.js +0 -83
  160. package/dist/esm/apis/currency.js.map +0 -1
  161. package/dist/esm/apis/market.js +0 -59
  162. package/dist/esm/apis/market.js.map +0 -1
  163. package/dist/esm/apis/open-order.js +0 -85
  164. package/dist/esm/apis/open-order.js.map +0 -1
  165. package/dist/esm/approval.js +0 -70
  166. package/dist/esm/approval.js.map +0 -1
  167. package/dist/esm/call.js +0 -535
  168. package/dist/esm/call.js.map +0 -1
  169. package/dist/esm/constants/action.js +0 -11
  170. package/dist/esm/constants/action.js.map +0 -1
  171. package/dist/esm/constants/addresses.js +0 -10
  172. package/dist/esm/constants/addresses.js.map +0 -1
  173. package/dist/esm/constants/chain.js +0 -10
  174. package/dist/esm/constants/chain.js.map +0 -1
  175. package/dist/esm/constants/currency.js +0 -9
  176. package/dist/esm/constants/currency.js.map +0 -1
  177. package/dist/esm/constants/fee.js +0 -4
  178. package/dist/esm/constants/fee.js.map +0 -1
  179. package/dist/esm/constants/price.js +0 -3
  180. package/dist/esm/constants/price.js.map +0 -1
  181. package/dist/esm/constants/subgraph-url.js +0 -5
  182. package/dist/esm/constants/subgraph-url.js.map +0 -1
  183. package/dist/esm/index.js +0 -6
  184. package/dist/esm/index.js.map +0 -1
  185. package/dist/esm/model/book.js +0 -126
  186. package/dist/esm/model/book.js.map +0 -1
  187. package/dist/esm/model/currency.js +0 -2
  188. package/dist/esm/model/currency.js.map +0 -1
  189. package/dist/esm/model/depth.js +0 -2
  190. package/dist/esm/model/depth.js.map +0 -1
  191. package/dist/esm/model/fee-policy.js +0 -38
  192. package/dist/esm/model/fee-policy.js.map +0 -1
  193. package/dist/esm/model/market.js +0 -211
  194. package/dist/esm/model/market.js.map +0 -1
  195. package/dist/esm/model/open-order.js +0 -2
  196. package/dist/esm/model/open-order.js.map +0 -1
  197. package/dist/esm/package.json +0 -1
  198. package/dist/esm/signature.js +0 -174
  199. package/dist/esm/signature.js.map +0 -1
  200. package/dist/esm/tsconfig.build.tsbuildinfo +0 -1
  201. package/dist/esm/type.js +0 -2
  202. package/dist/esm/type.js.map +0 -1
  203. package/dist/esm/utils/approval.js +0 -42
  204. package/dist/esm/utils/approval.js.map +0 -1
  205. package/dist/esm/utils/book-id.js.map +0 -1
  206. package/dist/esm/utils/build-transaction.js +0 -27
  207. package/dist/esm/utils/build-transaction.js.map +0 -1
  208. package/dist/esm/utils/decimals.js +0 -12
  209. package/dist/esm/utils/decimals.js.map +0 -1
  210. package/dist/esm/utils/market.js +0 -49
  211. package/dist/esm/utils/market.js.map +0 -1
  212. package/dist/esm/utils/math.js +0 -78
  213. package/dist/esm/utils/math.js.map +0 -1
  214. package/dist/esm/utils/prices.js +0 -17
  215. package/dist/esm/utils/prices.js.map +0 -1
  216. package/dist/esm/utils/tick.js +0 -100
  217. package/dist/esm/utils/tick.js.map +0 -1
  218. package/dist/esm/utils/time.js +0 -5
  219. package/dist/esm/utils/time.js.map +0 -1
  220. package/dist/esm/utils/unit.js +0 -34
  221. package/dist/esm/utils/unit.js.map +0 -1
  222. package/dist/esm/view.js +0 -177
  223. package/dist/esm/view.js.map +0 -1
  224. package/dist/types/.graphclient/index.d.ts +0 -1281
  225. package/dist/types/.graphclient/index.d.ts.map +0 -1
  226. package/dist/types/.graphclient/sources/clober-v2/introspectionSchema.d.ts +0 -3
  227. package/dist/types/.graphclient/sources/clober-v2/introspectionSchema.d.ts.map +0 -1
  228. package/dist/types/.graphclient/sources/clober-v2/types.d.ts +0 -984
  229. package/dist/types/.graphclient/sources/clober-v2/types.d.ts.map +0 -1
  230. package/dist/types/abis/core/controller-abi.d.ts +0 -757
  231. package/dist/types/abis/core/controller-abi.d.ts.map +0 -1
  232. package/dist/types/abis/core/params-abi.d.ts +0 -21
  233. package/dist/types/abis/core/params-abi.d.ts.map +0 -1
  234. package/dist/types/apis/currency.d.ts +0 -4
  235. package/dist/types/apis/currency.d.ts.map +0 -1
  236. package/dist/types/apis/market.d.ts +0 -4
  237. package/dist/types/apis/market.d.ts.map +0 -1
  238. package/dist/types/apis/open-order.d.ts +0 -5
  239. package/dist/types/apis/open-order.d.ts.map +0 -1
  240. package/dist/types/approval.d.ts +0 -32
  241. package/dist/types/approval.d.ts.map +0 -1
  242. package/dist/types/call.d.ts +0 -234
  243. package/dist/types/call.d.ts.map +0 -1
  244. package/dist/types/constants/action.d.ts +0 -10
  245. package/dist/types/constants/action.d.ts.map +0 -1
  246. package/dist/types/constants/addresses.d.ts +0 -9
  247. package/dist/types/constants/addresses.d.ts.map +0 -1
  248. package/dist/types/constants/chain.d.ts +0 -9
  249. package/dist/types/constants/chain.d.ts.map +0 -1
  250. package/dist/types/constants/currency.d.ts +0 -8
  251. package/dist/types/constants/currency.d.ts.map +0 -1
  252. package/dist/types/constants/fee.d.ts +0 -4
  253. package/dist/types/constants/fee.d.ts.map +0 -1
  254. package/dist/types/constants/price.d.ts +0 -3
  255. package/dist/types/constants/price.d.ts.map +0 -1
  256. package/dist/types/constants/subgraph-url.d.ts +0 -5
  257. package/dist/types/constants/subgraph-url.d.ts.map +0 -1
  258. package/dist/types/index.d.ts +0 -6
  259. package/dist/types/index.d.ts.map +0 -1
  260. package/dist/types/model/book.d.ts +0 -31
  261. package/dist/types/model/book.d.ts.map +0 -1
  262. package/dist/types/model/currency.d.ts +0 -7
  263. package/dist/types/model/currency.d.ts.map +0 -1
  264. package/dist/types/model/depth.d.ts +0 -11
  265. package/dist/types/model/depth.d.ts.map +0 -1
  266. package/dist/types/model/fee-policy.d.ts +0 -15
  267. package/dist/types/model/fee-policy.d.ts.map +0 -1
  268. package/dist/types/model/market.d.ts +0 -44
  269. package/dist/types/model/market.d.ts.map +0 -1
  270. package/dist/types/model/open-order.d.ts +0 -28
  271. package/dist/types/model/open-order.d.ts.map +0 -1
  272. package/dist/types/signature.d.ts +0 -40
  273. package/dist/types/signature.d.ts.map +0 -1
  274. package/dist/types/type.d.ts +0 -34
  275. package/dist/types/type.d.ts.map +0 -1
  276. package/dist/types/utils/approval.d.ts +0 -3
  277. package/dist/types/utils/approval.d.ts.map +0 -1
  278. package/dist/types/utils/book-id.d.ts +0 -2
  279. package/dist/types/utils/book-id.d.ts.map +0 -1
  280. package/dist/types/utils/build-transaction.d.ts +0 -5
  281. package/dist/types/utils/build-transaction.d.ts.map +0 -1
  282. package/dist/types/utils/decimals.d.ts +0 -3
  283. package/dist/types/utils/decimals.d.ts.map +0 -1
  284. package/dist/types/utils/market.d.ts +0 -7
  285. package/dist/types/utils/market.d.ts.map +0 -1
  286. package/dist/types/utils/math.d.ts +0 -3
  287. package/dist/types/utils/math.d.ts.map +0 -1
  288. package/dist/types/utils/prices.d.ts +0 -3
  289. package/dist/types/utils/prices.d.ts.map +0 -1
  290. package/dist/types/utils/tick.d.ts +0 -4
  291. package/dist/types/utils/tick.d.ts.map +0 -1
  292. package/dist/types/utils/time.d.ts +0 -2
  293. package/dist/types/utils/time.d.ts.map +0 -1
  294. package/dist/types/utils/unit.d.ts +0 -4
  295. package/dist/types/utils/unit.d.ts.map +0 -1
  296. package/dist/types/view.d.ts +0 -129
  297. package/dist/types/view.d.ts.map +0 -1
@@ -0,0 +1,17 @@
1
+ import { getAddress } from 'viem'
2
+
3
+ import { CHAIN_IDS } from './chain'
4
+
5
+ export const CONTRACT_ADDRESSES: {
6
+ [chain in CHAIN_IDS]: {
7
+ Controller: `0x${string}`
8
+ BookManager: `0x${string}`
9
+ BookViewer: `0x${string}`
10
+ }
11
+ } = {
12
+ [CHAIN_IDS.ARBITRUM_SEPOLIA]: {
13
+ Controller: getAddress('0xfAe4A04fa491DC21F77796394532a1B62d8331BF'),
14
+ BookManager: getAddress('0x4a4eaF7382821da4Fb85e8A8d515f5555383d58A'),
15
+ BookViewer: getAddress('0xA7603C4c895a533E66c30EA76cC6F6A6A0c5cbFe'),
16
+ },
17
+ }
@@ -0,0 +1,12 @@
1
+ import { arbitrumSepolia, type Chain, mainnet } from 'viem/chains'
2
+
3
+ export enum CHAIN_IDS {
4
+ MAINNET = mainnet.id,
5
+ ARBITRUM_SEPOLIA = arbitrumSepolia.id,
6
+ }
7
+
8
+ export const CHAIN_MAP: {
9
+ [chain in CHAIN_IDS]: Chain
10
+ } = {
11
+ [CHAIN_IDS.ARBITRUM_SEPOLIA]: arbitrumSepolia,
12
+ }
@@ -0,0 +1,15 @@
1
+ import { zeroAddress } from 'viem'
2
+
3
+ import { CHAIN_IDS } from './chain'
4
+
5
+ export const WETH_ADDRESSES: {
6
+ [chain in CHAIN_IDS]: `0x${string}`[]
7
+ } = {
8
+ [CHAIN_IDS.ARBITRUM_SEPOLIA]: [zeroAddress],
9
+ }
10
+
11
+ export const STABLE_COIN_ADDRESSES: {
12
+ [chain in CHAIN_IDS]: `0x${string}`[]
13
+ } = {
14
+ [CHAIN_IDS.ARBITRUM_SEPOLIA]: ['0x00BFD44e79FB7f6dd5887A9426c8EF85A0CD23e0'],
15
+ }
@@ -0,0 +1,4 @@
1
+ import { FeePolicy } from '../model/fee-policy'
2
+
3
+ export const MAKER_DEFAULT_POLICY = new FeePolicy(true, -300n) // -0.03%
4
+ export const TAKER_DEFAULT_POLICY = new FeePolicy(true, 1000n) // 0.1%
@@ -0,0 +1,3 @@
1
+ export const MAX_PRICE = 4647684107270898330752324302845848816923571339324334n
2
+
3
+ export const PRICE_PRECISION = 96n
@@ -0,0 +1,8 @@
1
+ import { CHAIN_IDS } from './chain'
2
+
3
+ export const SUBGRAPH_URL: {
4
+ [chain in CHAIN_IDS]: string
5
+ } = {
6
+ [CHAIN_IDS.ARBITRUM_SEPOLIA]:
7
+ 'https://subgraph.satsuma-prod.com/f6a8c4889b7b/clober/v2-core-subgraph/api',
8
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './type'
2
+ export * from './view'
3
+ export * from './call'
4
+ export * from './signature'
5
+ export * from './approval'
@@ -0,0 +1,166 @@
1
+ import { toPrice } from '../utils/tick'
2
+ import { TAKER_DEFAULT_POLICY } from '../constants/fee'
3
+ import { divide } from '../utils/math'
4
+ import { baseToQuote, quoteToBase } from '../utils/decimals'
5
+
6
+ import type { Currency } from './currency'
7
+ import type { RawDepth } from './depth'
8
+
9
+ export class Book {
10
+ id: bigint
11
+ base: Currency
12
+ unit: bigint
13
+ quote: Currency
14
+ depths: RawDepth[]
15
+
16
+ constructor({
17
+ id,
18
+ base,
19
+ quote,
20
+ unit,
21
+ depths,
22
+ }: {
23
+ id: bigint
24
+ base: Currency
25
+ quote: Currency
26
+ unit: bigint
27
+ depths: RawDepth[]
28
+ }) {
29
+ this.id = id
30
+ this.base = base
31
+ this.unit = unit
32
+ this.quote = quote
33
+ this.depths = depths
34
+ }
35
+
36
+ take = ({
37
+ limitPrice,
38
+ amountOut, // quote
39
+ }: {
40
+ limitPrice: bigint
41
+ amountOut: bigint
42
+ }) => {
43
+ let takenQuoteAmount = 0n
44
+ let spendBaseAmount = 0n
45
+ if (this.depths.length === 0) {
46
+ return {
47
+ takenQuoteAmount,
48
+ spendBaseAmount,
49
+ }
50
+ }
51
+
52
+ const ticks = this.depths
53
+ .sort((a, b) => Number(b.tick) - Number(a.tick))
54
+ .map((depth) => depth.tick)
55
+ let index = 0
56
+ let tick = ticks[index]!
57
+ while (tick > -8388608n) {
58
+ if (limitPrice > toPrice(tick)) {
59
+ break
60
+ }
61
+ let maxAmount = TAKER_DEFAULT_POLICY.usesQuote
62
+ ? TAKER_DEFAULT_POLICY.calculateOriginalAmount(
63
+ amountOut - takenQuoteAmount,
64
+ true,
65
+ )
66
+ : amountOut - takenQuoteAmount
67
+ maxAmount = divide(maxAmount, this.unit, true)
68
+
69
+ if (maxAmount === 0n) {
70
+ break
71
+ }
72
+ const currentDepth = this.depths.find((depth) => depth.tick === tick)!
73
+ let quoteAmount =
74
+ (currentDepth.rawAmount > maxAmount
75
+ ? maxAmount
76
+ : currentDepth.rawAmount) * this.unit
77
+ let baseAmount = quoteToBase(tick, quoteAmount, true)
78
+ if (TAKER_DEFAULT_POLICY.usesQuote) {
79
+ quoteAmount =
80
+ quoteAmount - TAKER_DEFAULT_POLICY.calculateFee(quoteAmount, false)
81
+ } else {
82
+ baseAmount =
83
+ baseAmount + TAKER_DEFAULT_POLICY.calculateFee(baseAmount, false)
84
+ }
85
+ if (quoteAmount === 0n) {
86
+ break
87
+ }
88
+
89
+ takenQuoteAmount += quoteAmount
90
+ spendBaseAmount += baseAmount
91
+ if (amountOut <= takenQuoteAmount) {
92
+ break
93
+ }
94
+ index++
95
+ tick = ticks[index]!
96
+ }
97
+ return {
98
+ takenQuoteAmount,
99
+ spendBaseAmount,
100
+ }
101
+ }
102
+
103
+ spend = ({
104
+ limitPrice,
105
+ amountIn, // base
106
+ }: {
107
+ limitPrice: bigint
108
+ amountIn: bigint
109
+ }) => {
110
+ let takenQuoteAmount = 0n
111
+ let spendBaseAmount = 0n
112
+ if (this.depths.length === 0) {
113
+ return {
114
+ takenQuoteAmount,
115
+ spendBaseAmount,
116
+ }
117
+ }
118
+
119
+ const ticks = this.depths
120
+ .sort((a, b) => Number(b.tick) - Number(a.tick))
121
+ .map((depth) => depth.tick)
122
+ let index = 0
123
+ let tick = ticks[index]!
124
+ while (spendBaseAmount <= amountIn && tick > -8388608n) {
125
+ if (limitPrice > toPrice(tick)) {
126
+ break
127
+ }
128
+ let maxAmount = TAKER_DEFAULT_POLICY.usesQuote
129
+ ? amountIn - spendBaseAmount
130
+ : TAKER_DEFAULT_POLICY.calculateOriginalAmount(
131
+ amountIn - spendBaseAmount,
132
+ false,
133
+ )
134
+ maxAmount = baseToQuote(tick, maxAmount, false) / this.unit
135
+
136
+ if (maxAmount === 0n) {
137
+ break
138
+ }
139
+ const currentDepth = this.depths.find((depth) => depth.tick === tick)!
140
+ let quoteAmount =
141
+ (currentDepth.rawAmount > maxAmount
142
+ ? maxAmount
143
+ : currentDepth.rawAmount) * this.unit
144
+ let baseAmount = quoteToBase(tick, quoteAmount, true)
145
+ if (TAKER_DEFAULT_POLICY.usesQuote) {
146
+ quoteAmount =
147
+ quoteAmount - TAKER_DEFAULT_POLICY.calculateFee(quoteAmount, false)
148
+ } else {
149
+ baseAmount =
150
+ baseAmount + TAKER_DEFAULT_POLICY.calculateFee(baseAmount, false)
151
+ }
152
+ if (baseAmount === 0n) {
153
+ break
154
+ }
155
+
156
+ takenQuoteAmount += quoteAmount
157
+ spendBaseAmount += baseAmount
158
+ index++
159
+ tick = ticks[index]!
160
+ }
161
+ return {
162
+ takenQuoteAmount,
163
+ spendBaseAmount,
164
+ }
165
+ }
166
+ }
@@ -0,0 +1,6 @@
1
+ export type Currency = {
2
+ address: `0x${string}`
3
+ name: string
4
+ symbol: string
5
+ decimals: number
6
+ }
@@ -0,0 +1,11 @@
1
+ export type RawDepth = {
2
+ bookId: string
3
+ unit: bigint
4
+ tick: bigint
5
+ rawAmount: bigint
6
+ }
7
+
8
+ export type Depth = {
9
+ price: number
10
+ baseAmount: bigint
11
+ }
@@ -0,0 +1,51 @@
1
+ import { divide } from '../utils/math'
2
+
3
+ export class FeePolicy {
4
+ static readonly MAX_FEE_RATE = 500000n
5
+ static readonly MIN_FEE_RATE = -500000n
6
+ static readonly RATE_MASK = 0x7fffffn
7
+ readonly RATE_PRECISION = 10n ** 6n
8
+ public usesQuote: boolean
9
+ public value: bigint
10
+ public rate: bigint
11
+
12
+ constructor(usesQuote: boolean, rate: bigint) {
13
+ this.usesQuote = usesQuote
14
+ this.rate = rate
15
+
16
+ if (rate > FeePolicy.MAX_FEE_RATE || rate < FeePolicy.MIN_FEE_RATE) {
17
+ throw new Error('InvalidFeePolicy')
18
+ }
19
+ const mask = usesQuote ? 1n << 23n : 0n
20
+ this.value = (rate + FeePolicy.MAX_FEE_RATE) | mask
21
+ }
22
+
23
+ public static from(value: bigint): FeePolicy {
24
+ const usesQuote = value >> 23n > 0n
25
+ return new FeePolicy(usesQuote, FeePolicy.getRateFromValue(value))
26
+ }
27
+
28
+ private static getRateFromValue = (value: bigint): bigint => {
29
+ return (value & FeePolicy.RATE_MASK) - FeePolicy.MAX_FEE_RATE
30
+ }
31
+
32
+ public calculateFee = (amount: bigint, reverseRounding: boolean): bigint => {
33
+ const positive = this.rate > 0n
34
+ const absRate = positive ? this.rate : -this.rate
35
+ const absFee = divide(
36
+ amount * absRate,
37
+ this.RATE_PRECISION,
38
+ reverseRounding ? !positive : positive,
39
+ )
40
+ return positive ? absFee : -absFee
41
+ }
42
+
43
+ public calculateOriginalAmount = (
44
+ amount: bigint,
45
+ reverseFee: boolean,
46
+ ): bigint => {
47
+ const positive = this.rate > 0
48
+ const divider = this.RATE_PRECISION + (reverseFee ? -this.rate : this.rate)
49
+ return divide(amount * this.RATE_PRECISION, divider, positive)
50
+ }
51
+ }
@@ -0,0 +1,320 @@
1
+ import { isAddressEqual } from 'viem'
2
+
3
+ import { getMarketId } from '../utils/market'
4
+ import { CHAIN_IDS } from '../constants/chain'
5
+ import { invertPrice, toPrice } from '../utils/tick'
6
+ import { formatPrice } from '../utils/prices'
7
+ import { MAKER_DEFAULT_POLICY, TAKER_DEFAULT_POLICY } from '../constants/fee'
8
+ import { divide } from '../utils/math'
9
+ import { baseToQuote, quoteToBase } from '../utils/decimals'
10
+
11
+ import { Book } from './book'
12
+ import type { Currency } from './currency'
13
+ import type { Depth, RawDepth } from './depth'
14
+
15
+ export class Market {
16
+ readonly makerFee = (Number(MAKER_DEFAULT_POLICY.rate) * 100) / 1e6
17
+ readonly takerFee = (Number(TAKER_DEFAULT_POLICY.rate) * 100) / 1e6
18
+
19
+ id: string
20
+ quote: Currency
21
+ base: Currency
22
+ bids: Depth[]
23
+ bidBookOpen: boolean
24
+ asks: Depth[]
25
+ askBookOpen: boolean
26
+ private books: Book[]
27
+
28
+ constructor({
29
+ chainId,
30
+ tokens,
31
+ books,
32
+ }: {
33
+ chainId: CHAIN_IDS
34
+ tokens: [Currency, Currency]
35
+ books: Book[]
36
+ }) {
37
+ const { marketId, quoteTokenAddress, baseTokenAddress } = getMarketId(
38
+ chainId,
39
+ tokens.map((token) => token.address),
40
+ )
41
+ this.id = marketId
42
+ this.quote = tokens.find((token) =>
43
+ isAddressEqual(token.address, quoteTokenAddress!),
44
+ )!
45
+ this.base = tokens.find((token) =>
46
+ isAddressEqual(token.address, baseTokenAddress!),
47
+ )!
48
+
49
+ this.bids = books
50
+ .filter((book) => isAddressEqual(book.quote.address, this.quote.address))
51
+ .flatMap((book) => book.depths)
52
+ .map(
53
+ (depth) =>
54
+ ({
55
+ price: formatPrice(
56
+ toPrice(depth.tick),
57
+ this.quote.decimals,
58
+ this.base.decimals,
59
+ ),
60
+ baseAmount: quoteToBase(
61
+ depth.tick,
62
+ depth.rawAmount * depth.unit,
63
+ false,
64
+ ),
65
+ }) as Depth,
66
+ )
67
+ this.bidBookOpen =
68
+ books.filter((book) =>
69
+ isAddressEqual(book.quote.address, this.quote.address),
70
+ ).length > 0
71
+ this.asks = books
72
+ .filter((book) => isAddressEqual(book.quote.address, this.base.address))
73
+ .flatMap((book) => book.depths)
74
+ .map((depth) => {
75
+ const price = invertPrice(toPrice(depth.tick))
76
+ const readablePrice = formatPrice(
77
+ price,
78
+ this.quote.decimals,
79
+ this.base.decimals,
80
+ )
81
+ const baseAmount = depth.rawAmount * depth.unit
82
+ return {
83
+ price: readablePrice,
84
+ baseAmount,
85
+ } as Depth
86
+ })
87
+ this.askBookOpen =
88
+ books.filter((book) =>
89
+ isAddressEqual(book.quote.address, this.base.address),
90
+ ).length > 0
91
+ this.books = books
92
+ }
93
+
94
+ take = ({
95
+ takeQuote,
96
+ limitPrice,
97
+ amountOut, // quote if takeQuote, base otherwise
98
+ }: {
99
+ takeQuote: boolean
100
+ limitPrice: bigint
101
+ amountOut: bigint
102
+ }) => {
103
+ if (takeQuote) {
104
+ const bidDepths = this.books
105
+ .filter((book) =>
106
+ isAddressEqual(book.quote.address, this.quote.address),
107
+ )
108
+ .flatMap((book) => book.depths)
109
+ return this.takeInner({ depths: bidDepths, limitPrice, amountOut })
110
+ } else {
111
+ const askDepths = this.books
112
+ .filter((book) => isAddressEqual(book.quote.address, this.base.address))
113
+ .flatMap((book) => book.depths)
114
+ return this.takeInner({
115
+ depths: askDepths,
116
+ limitPrice: invertPrice(limitPrice),
117
+ amountOut,
118
+ })
119
+ }
120
+ }
121
+
122
+ spend = ({
123
+ spendBase,
124
+ limitPrice,
125
+ amountIn, // base if spendBase, quote otherwise
126
+ }: {
127
+ spendBase: boolean
128
+ limitPrice: bigint
129
+ amountIn: bigint
130
+ }) => {
131
+ if (spendBase) {
132
+ const bidDepths = this.books
133
+ .filter((book) =>
134
+ isAddressEqual(book.quote.address, this.quote.address),
135
+ )
136
+ .flatMap((book) => book.depths)
137
+ return this.spendInner({ depths: bidDepths, limitPrice, amountIn })
138
+ } else {
139
+ const askDepths = this.books
140
+ .filter((book) => isAddressEqual(book.quote.address, this.base.address))
141
+ .flatMap((book) => book.depths)
142
+ return this.spendInner({
143
+ depths: askDepths,
144
+ limitPrice: invertPrice(limitPrice),
145
+ amountIn,
146
+ })
147
+ }
148
+ }
149
+
150
+ private takeInner = ({
151
+ depths, // only bid orders
152
+ limitPrice,
153
+ amountOut, // quote
154
+ }: {
155
+ depths: RawDepth[]
156
+ limitPrice: bigint
157
+ amountOut: bigint
158
+ }) => {
159
+ if (depths.length === 0) {
160
+ return {}
161
+ }
162
+ const takeResult: {
163
+ [key in string]: {
164
+ takenAmount: bigint
165
+ spendAmount: bigint
166
+ }
167
+ } = {}
168
+ for (const depth of depths) {
169
+ if (!takeResult[depth.bookId]) {
170
+ takeResult[depth.bookId] = {
171
+ takenAmount: 0n,
172
+ spendAmount: 0n,
173
+ }
174
+ }
175
+ }
176
+ let totalTakenQuoteAmount = 0n
177
+
178
+ const ticks = depths
179
+ .sort((a, b) => Number(b.tick) - Number(a.tick))
180
+ .map((depth) => depth.tick)
181
+ let index = 0
182
+ let tick = ticks[index]!
183
+ while (tick > -8388608n) {
184
+ if (limitPrice > toPrice(tick)) {
185
+ break
186
+ }
187
+ const currentDepth = depths.find((depth) => depth.tick === tick)!
188
+ const currentBook = this.books.find(
189
+ (book) => book.id === BigInt(currentDepth.bookId),
190
+ )!
191
+ let maxAmount = TAKER_DEFAULT_POLICY.usesQuote
192
+ ? TAKER_DEFAULT_POLICY.calculateOriginalAmount(
193
+ amountOut - totalTakenQuoteAmount,
194
+ true,
195
+ )
196
+ : amountOut - totalTakenQuoteAmount
197
+ maxAmount = divide(maxAmount, currentBook.unit, true)
198
+
199
+ if (maxAmount === 0n) {
200
+ break
201
+ }
202
+ let quoteAmount =
203
+ (currentDepth.rawAmount > maxAmount
204
+ ? maxAmount
205
+ : currentDepth.rawAmount) * currentBook.unit
206
+ let baseAmount = quoteToBase(tick, quoteAmount, true)
207
+ if (TAKER_DEFAULT_POLICY.usesQuote) {
208
+ quoteAmount =
209
+ quoteAmount - TAKER_DEFAULT_POLICY.calculateFee(quoteAmount, false)
210
+ } else {
211
+ baseAmount =
212
+ baseAmount + TAKER_DEFAULT_POLICY.calculateFee(baseAmount, false)
213
+ }
214
+ if (quoteAmount === 0n) {
215
+ break
216
+ }
217
+
218
+ takeResult[currentDepth.bookId]!.takenAmount += quoteAmount
219
+ takeResult[currentDepth.bookId]!.spendAmount += baseAmount
220
+ totalTakenQuoteAmount += quoteAmount
221
+ if (amountOut <= totalTakenQuoteAmount) {
222
+ break
223
+ }
224
+ if (ticks.length === index + 1) {
225
+ break
226
+ }
227
+ index++
228
+ tick = ticks[index]!
229
+ }
230
+ return Object.fromEntries(
231
+ Object.entries(takeResult).filter(
232
+ ([, value]) => value.spendAmount > 0 && value.takenAmount > 0,
233
+ ),
234
+ )
235
+ }
236
+
237
+ private spendInner = ({
238
+ depths, // only bid orders
239
+ limitPrice,
240
+ amountIn, // base
241
+ }: {
242
+ depths: RawDepth[]
243
+ limitPrice: bigint
244
+ amountIn: bigint
245
+ }) => {
246
+ if (depths.length === 0) {
247
+ return {}
248
+ }
249
+ const spendResult: {
250
+ [key in string]: {
251
+ takenAmount: bigint
252
+ spendAmount: bigint
253
+ }
254
+ } = {}
255
+ for (const depth of depths) {
256
+ if (!spendResult[depth.bookId]) {
257
+ spendResult[depth.bookId] = {
258
+ takenAmount: 0n,
259
+ spendAmount: 0n,
260
+ }
261
+ }
262
+ }
263
+ let totalSpendBaseAmount = 0n
264
+
265
+ const ticks = depths
266
+ .sort((a, b) => Number(b.tick) - Number(a.tick))
267
+ .map((depth) => depth.tick)
268
+ let index = 0
269
+ let tick = ticks[index]!
270
+ while (totalSpendBaseAmount <= amountIn && tick > -8388608n) {
271
+ if (limitPrice > toPrice(tick)) {
272
+ break
273
+ }
274
+ const currentDepth = depths.find((depth) => depth.tick === tick)!
275
+ const currentBook = this.books.find(
276
+ (book) => book.id === BigInt(currentDepth.bookId),
277
+ )!
278
+ let maxAmount = TAKER_DEFAULT_POLICY.usesQuote
279
+ ? amountIn - totalSpendBaseAmount
280
+ : TAKER_DEFAULT_POLICY.calculateOriginalAmount(
281
+ amountIn - totalSpendBaseAmount,
282
+ false,
283
+ )
284
+ maxAmount = baseToQuote(tick, maxAmount, false) / currentBook.unit
285
+
286
+ if (maxAmount === 0n) {
287
+ break
288
+ }
289
+ let quoteAmount =
290
+ (currentDepth.rawAmount > maxAmount
291
+ ? maxAmount
292
+ : currentDepth.rawAmount) * currentBook.unit
293
+ let baseAmount = quoteToBase(tick, quoteAmount, true)
294
+ if (TAKER_DEFAULT_POLICY.usesQuote) {
295
+ quoteAmount =
296
+ quoteAmount - TAKER_DEFAULT_POLICY.calculateFee(quoteAmount, false)
297
+ } else {
298
+ baseAmount =
299
+ baseAmount + TAKER_DEFAULT_POLICY.calculateFee(baseAmount, false)
300
+ }
301
+ if (baseAmount === 0n) {
302
+ break
303
+ }
304
+
305
+ spendResult[currentDepth.bookId]!.takenAmount += quoteAmount
306
+ spendResult[currentDepth.bookId]!.spendAmount += baseAmount
307
+ totalSpendBaseAmount += baseAmount
308
+ if (ticks.length === index + 1) {
309
+ break
310
+ }
311
+ index++
312
+ tick = ticks[index]!
313
+ }
314
+ return Object.fromEntries(
315
+ Object.entries(spendResult).filter(
316
+ ([, value]) => value.spendAmount > 0 && value.takenAmount > 0,
317
+ ),
318
+ )
319
+ }
320
+ }
@@ -0,0 +1,16 @@
1
+ import { Currency } from './currency'
2
+
3
+ export type OpenOrder = {
4
+ id: string
5
+ isBid: boolean
6
+ inputCurrency: Currency
7
+ outputCurrency: Currency
8
+ txHash: `0x${string}`
9
+ createdAt: number
10
+ price: number
11
+ amount: { currency: Currency; value: string }
12
+ filled: { currency: Currency; value: string }
13
+ claimed: { currency: Currency; value: string }
14
+ claimable: { currency: Currency; value: string }
15
+ cancelable: boolean
16
+ }