ox 0.9.16 → 0.10.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 (216) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +4 -4
  3. package/TxEnvelope/package.json +6 -0
  4. package/TxEnvelopeEip1559/package.json +6 -0
  5. package/TxEnvelopeEip2930/package.json +6 -0
  6. package/TxEnvelopeEip4844/package.json +6 -0
  7. package/TxEnvelopeEip7702/package.json +6 -0
  8. package/TxEnvelopeLegacy/package.json +6 -0
  9. package/_cjs/core/{TransactionEnvelope.js → TxEnvelope.js} +1 -1
  10. package/_cjs/core/TxEnvelope.js.map +1 -0
  11. package/_cjs/core/{TransactionEnvelopeEip1559.js → TxEnvelopeEip1559.js} +2 -2
  12. package/_cjs/core/TxEnvelopeEip1559.js.map +1 -0
  13. package/_cjs/core/{TransactionEnvelopeEip2930.js → TxEnvelopeEip2930.js} +2 -2
  14. package/_cjs/core/TxEnvelopeEip2930.js.map +1 -0
  15. package/_cjs/core/{TransactionEnvelopeEip4844.js → TxEnvelopeEip4844.js} +4 -4
  16. package/_cjs/core/TxEnvelopeEip4844.js.map +1 -0
  17. package/_cjs/core/{TransactionEnvelopeEip7702.js → TxEnvelopeEip7702.js} +4 -4
  18. package/_cjs/core/TxEnvelopeEip7702.js.map +1 -0
  19. package/_cjs/core/{TransactionEnvelopeLegacy.js → TxEnvelopeLegacy.js} +2 -2
  20. package/_cjs/core/TxEnvelopeLegacy.js.map +1 -0
  21. package/_cjs/core/WebAuthnP256.js +1 -1
  22. package/_cjs/core/WebAuthnP256.js.map +1 -1
  23. package/_cjs/erc8021/Attribution.js +36 -6
  24. package/_cjs/erc8021/Attribution.js.map +1 -1
  25. package/_cjs/index.docs.js +1 -0
  26. package/_cjs/index.docs.js.map +1 -1
  27. package/_cjs/index.js +7 -7
  28. package/_cjs/index.js.map +1 -1
  29. package/_cjs/tempo/AuthorizationTempo.js +101 -0
  30. package/_cjs/tempo/AuthorizationTempo.js.map +1 -0
  31. package/_cjs/tempo/KeyAuthorization.js +123 -0
  32. package/_cjs/tempo/KeyAuthorization.js.map +1 -0
  33. package/_cjs/tempo/PoolId.js +10 -0
  34. package/_cjs/tempo/PoolId.js.map +1 -0
  35. package/_cjs/tempo/SignatureEnvelope.js +394 -0
  36. package/_cjs/tempo/SignatureEnvelope.js.map +1 -0
  37. package/_cjs/tempo/Tick.js +77 -0
  38. package/_cjs/tempo/Tick.js.map +1 -0
  39. package/_cjs/tempo/TokenId.js +28 -0
  40. package/_cjs/tempo/TokenId.js.map +1 -0
  41. package/_cjs/tempo/TokenRole.js +26 -0
  42. package/_cjs/tempo/TokenRole.js.map +1 -0
  43. package/_cjs/tempo/Transaction.js +80 -0
  44. package/_cjs/tempo/Transaction.js.map +1 -0
  45. package/_cjs/tempo/TransactionReceipt.js +26 -0
  46. package/_cjs/tempo/TransactionReceipt.js.map +1 -0
  47. package/_cjs/tempo/TransactionRequest.js +53 -0
  48. package/_cjs/tempo/TransactionRequest.js.map +1 -0
  49. package/_cjs/tempo/TxEnvelopeTempo.js +267 -0
  50. package/_cjs/tempo/TxEnvelopeTempo.js.map +1 -0
  51. package/_cjs/tempo/index.js +15 -0
  52. package/_cjs/tempo/index.js.map +1 -0
  53. package/_cjs/version.js +1 -1
  54. package/_esm/core/Blobs.js +8 -8
  55. package/_esm/core/{TransactionEnvelope.js → TxEnvelope.js} +11 -11
  56. package/_esm/core/TxEnvelope.js.map +1 -0
  57. package/_esm/core/{TransactionEnvelopeEip1559.js → TxEnvelopeEip1559.js} +42 -42
  58. package/_esm/core/TxEnvelopeEip1559.js.map +1 -0
  59. package/_esm/core/{TransactionEnvelopeEip2930.js → TxEnvelopeEip2930.js} +43 -43
  60. package/_esm/core/TxEnvelopeEip2930.js.map +1 -0
  61. package/_esm/core/{TransactionEnvelopeEip4844.js → TxEnvelopeEip4844.js} +42 -42
  62. package/_esm/core/TxEnvelopeEip4844.js.map +1 -0
  63. package/_esm/core/{TransactionEnvelopeEip7702.js → TxEnvelopeEip7702.js} +40 -40
  64. package/_esm/core/TxEnvelopeEip7702.js.map +1 -0
  65. package/_esm/core/{TransactionEnvelopeLegacy.js → TxEnvelopeLegacy.js} +42 -42
  66. package/_esm/core/TxEnvelopeLegacy.js.map +1 -0
  67. package/_esm/core/WebAuthnP256.js +1 -1
  68. package/_esm/core/WebAuthnP256.js.map +1 -1
  69. package/_esm/erc8021/Attribution.js +58 -13
  70. package/_esm/erc8021/Attribution.js.map +1 -1
  71. package/_esm/index.docs.js +1 -0
  72. package/_esm/index.docs.js.map +1 -1
  73. package/_esm/index.js +192 -192
  74. package/_esm/index.js.map +1 -1
  75. package/_esm/tempo/AuthorizationTempo.js +664 -0
  76. package/_esm/tempo/AuthorizationTempo.js.map +1 -0
  77. package/_esm/tempo/KeyAuthorization.js +426 -0
  78. package/_esm/tempo/KeyAuthorization.js.map +1 -0
  79. package/_esm/tempo/PoolId.js +28 -0
  80. package/_esm/tempo/PoolId.js.map +1 -0
  81. package/_esm/tempo/SignatureEnvelope.js +660 -0
  82. package/_esm/tempo/SignatureEnvelope.js.map +1 -0
  83. package/_esm/tempo/Tick.js +147 -0
  84. package/_esm/tempo/Tick.js.map +1 -0
  85. package/_esm/tempo/TokenId.js +71 -0
  86. package/_esm/tempo/TokenId.js.map +1 -0
  87. package/_esm/tempo/TokenRole.js +40 -0
  88. package/_esm/tempo/TokenRole.js.map +1 -0
  89. package/_esm/tempo/Transaction.js +167 -0
  90. package/_esm/tempo/Transaction.js.map +1 -0
  91. package/_esm/tempo/TransactionReceipt.js +138 -0
  92. package/_esm/tempo/TransactionReceipt.js.map +1 -0
  93. package/_esm/tempo/TransactionRequest.js +99 -0
  94. package/_esm/tempo/TransactionRequest.js.map +1 -0
  95. package/_esm/tempo/TxEnvelopeTempo.js +607 -0
  96. package/_esm/tempo/TxEnvelopeTempo.js.map +1 -0
  97. package/_esm/tempo/index.js +298 -0
  98. package/_esm/tempo/index.js.map +1 -0
  99. package/_esm/version.js +1 -1
  100. package/_types/core/Blobs.d.ts +8 -8
  101. package/_types/core/{TransactionEnvelope.d.ts → TxEnvelope.d.ts} +11 -11
  102. package/_types/core/TxEnvelope.d.ts.map +1 -0
  103. package/_types/core/{TransactionEnvelopeEip1559.d.ts → TxEnvelopeEip1559.d.ts} +54 -54
  104. package/_types/core/TxEnvelopeEip1559.d.ts.map +1 -0
  105. package/_types/core/{TransactionEnvelopeEip2930.d.ts → TxEnvelopeEip2930.d.ts} +55 -55
  106. package/_types/core/TxEnvelopeEip2930.d.ts.map +1 -0
  107. package/_types/core/{TransactionEnvelopeEip4844.d.ts → TxEnvelopeEip4844.d.ts} +54 -54
  108. package/_types/core/TxEnvelopeEip4844.d.ts.map +1 -0
  109. package/_types/core/{TransactionEnvelopeEip7702.d.ts → TxEnvelopeEip7702.d.ts} +49 -49
  110. package/_types/core/TxEnvelopeEip7702.d.ts.map +1 -0
  111. package/_types/core/{TransactionEnvelopeLegacy.d.ts → TxEnvelopeLegacy.d.ts} +54 -54
  112. package/_types/core/TxEnvelopeLegacy.d.ts.map +1 -0
  113. package/_types/core/WebAuthnP256.d.ts +1 -1
  114. package/_types/core/WebAuthnP256.d.ts.map +1 -1
  115. package/_types/erc8021/Attribution.d.ts +20 -6
  116. package/_types/erc8021/Attribution.d.ts.map +1 -1
  117. package/_types/index.d.ts +192 -192
  118. package/_types/index.d.ts.map +1 -1
  119. package/_types/index.docs.d.ts +1 -0
  120. package/_types/index.docs.d.ts.map +1 -1
  121. package/_types/tempo/AuthorizationTempo.d.ts +688 -0
  122. package/_types/tempo/AuthorizationTempo.d.ts.map +1 -0
  123. package/_types/tempo/KeyAuthorization.d.ts +437 -0
  124. package/_types/tempo/KeyAuthorization.d.ts.map +1 -0
  125. package/_types/tempo/PoolId.d.ts +33 -0
  126. package/_types/tempo/PoolId.d.ts.map +1 -0
  127. package/_types/tempo/SignatureEnvelope.d.ts +438 -0
  128. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -0
  129. package/_types/tempo/Tick.d.ts +120 -0
  130. package/_types/tempo/Tick.d.ts.map +1 -0
  131. package/_types/tempo/TokenId.d.ts +55 -0
  132. package/_types/tempo/TokenId.d.ts.map +1 -0
  133. package/_types/tempo/TokenRole.d.ts +29 -0
  134. package/_types/tempo/TokenRole.d.ts.map +1 -0
  135. package/_types/tempo/Transaction.d.ts +208 -0
  136. package/_types/tempo/Transaction.d.ts.map +1 -0
  137. package/_types/tempo/TransactionReceipt.d.ts +165 -0
  138. package/_types/tempo/TransactionReceipt.d.ts.map +1 -0
  139. package/_types/tempo/TransactionRequest.d.ts +89 -0
  140. package/_types/tempo/TransactionRequest.d.ts.map +1 -0
  141. package/_types/tempo/TxEnvelopeTempo.d.ts +551 -0
  142. package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -0
  143. package/_types/tempo/index.d.ts +300 -0
  144. package/_types/tempo/index.d.ts.map +1 -0
  145. package/_types/version.d.ts +1 -1
  146. package/core/Blobs.ts +8 -8
  147. package/core/{TransactionEnvelope.ts → TxEnvelope.ts} +10 -10
  148. package/core/{TransactionEnvelopeEip1559.ts → TxEnvelopeEip1559.ts} +60 -69
  149. package/core/{TransactionEnvelopeEip2930.ts → TxEnvelopeEip2930.ts} +61 -72
  150. package/core/{TransactionEnvelopeEip4844.ts → TxEnvelopeEip4844.ts} +62 -71
  151. package/core/{TransactionEnvelopeEip7702.ts → TxEnvelopeEip7702.ts} +58 -67
  152. package/core/{TransactionEnvelopeLegacy.ts → TxEnvelopeLegacy.ts} +59 -68
  153. package/core/WebAuthnP256.ts +3 -1
  154. package/erc8021/Attribution.ts +77 -15
  155. package/index.docs.ts +1 -0
  156. package/index.ts +192 -195
  157. package/package.json +91 -31
  158. package/tempo/AuthorizationTempo/package.json +6 -0
  159. package/tempo/AuthorizationTempo.test.ts +1293 -0
  160. package/tempo/AuthorizationTempo.ts +884 -0
  161. package/tempo/KeyAuthorization/package.json +6 -0
  162. package/tempo/KeyAuthorization.test.ts +1373 -0
  163. package/tempo/KeyAuthorization.ts +622 -0
  164. package/tempo/PoolId/package.json +6 -0
  165. package/tempo/PoolId.test.ts +33 -0
  166. package/tempo/PoolId.ts +42 -0
  167. package/tempo/SignatureEnvelope/package.json +6 -0
  168. package/tempo/SignatureEnvelope.test.ts +1877 -0
  169. package/tempo/SignatureEnvelope.ts +973 -0
  170. package/tempo/Tick/package.json +6 -0
  171. package/tempo/Tick.test.ts +281 -0
  172. package/tempo/Tick.ts +186 -0
  173. package/tempo/TokenId/package.json +6 -0
  174. package/tempo/TokenId.test.ts +40 -0
  175. package/tempo/TokenId.ts +80 -0
  176. package/tempo/TokenRole/package.json +6 -0
  177. package/tempo/TokenRole.test.ts +16 -0
  178. package/tempo/TokenRole.ts +45 -0
  179. package/tempo/Transaction/package.json +6 -0
  180. package/tempo/Transaction.test.ts +523 -0
  181. package/tempo/Transaction.ts +339 -0
  182. package/tempo/TransactionReceipt/package.json +6 -0
  183. package/tempo/TransactionReceipt.ts +200 -0
  184. package/tempo/TransactionRequest/package.json +6 -0
  185. package/tempo/TransactionRequest.ts +160 -0
  186. package/tempo/TxEnvelopeTempo/package.json +6 -0
  187. package/tempo/TxEnvelopeTempo.test.ts +1371 -0
  188. package/tempo/TxEnvelopeTempo.ts +972 -0
  189. package/tempo/e2e.test.ts +1387 -0
  190. package/tempo/index.ts +308 -0
  191. package/tempo/package.json +6 -0
  192. package/version.ts +1 -1
  193. package/TransactionEnvelope/package.json +0 -6
  194. package/TransactionEnvelopeEip1559/package.json +0 -6
  195. package/TransactionEnvelopeEip2930/package.json +0 -6
  196. package/TransactionEnvelopeEip4844/package.json +0 -6
  197. package/TransactionEnvelopeEip7702/package.json +0 -6
  198. package/TransactionEnvelopeLegacy/package.json +0 -6
  199. package/_cjs/core/TransactionEnvelope.js.map +0 -1
  200. package/_cjs/core/TransactionEnvelopeEip1559.js.map +0 -1
  201. package/_cjs/core/TransactionEnvelopeEip2930.js.map +0 -1
  202. package/_cjs/core/TransactionEnvelopeEip4844.js.map +0 -1
  203. package/_cjs/core/TransactionEnvelopeEip7702.js.map +0 -1
  204. package/_cjs/core/TransactionEnvelopeLegacy.js.map +0 -1
  205. package/_esm/core/TransactionEnvelope.js.map +0 -1
  206. package/_esm/core/TransactionEnvelopeEip1559.js.map +0 -1
  207. package/_esm/core/TransactionEnvelopeEip2930.js.map +0 -1
  208. package/_esm/core/TransactionEnvelopeEip4844.js.map +0 -1
  209. package/_esm/core/TransactionEnvelopeEip7702.js.map +0 -1
  210. package/_esm/core/TransactionEnvelopeLegacy.js.map +0 -1
  211. package/_types/core/TransactionEnvelope.d.ts.map +0 -1
  212. package/_types/core/TransactionEnvelopeEip1559.d.ts.map +0 -1
  213. package/_types/core/TransactionEnvelopeEip2930.d.ts.map +0 -1
  214. package/_types/core/TransactionEnvelopeEip4844.d.ts.map +0 -1
  215. package/_types/core/TransactionEnvelopeEip7702.d.ts.map +0 -1
  216. package/_types/core/TransactionEnvelopeLegacy.d.ts.map +0 -1
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/Tick.d.ts",
4
+ "main": "../../_cjs/tempo/Tick.js",
5
+ "module": "../../_esm/tempo/Tick.js"
6
+ }
@@ -0,0 +1,281 @@
1
+ import { Tick } from 'ox/tempo'
2
+ import { describe, expect, test } from 'vitest'
3
+
4
+ describe('toPrice', () => {
5
+ test('converts tick 0 to price 1', () => {
6
+ expect(Tick.toPrice(0)).toBe('1')
7
+ })
8
+
9
+ test('converts positive ticks correctly', () => {
10
+ expect(Tick.toPrice(100)).toBe('1.001')
11
+ expect(Tick.toPrice(1000)).toBe('1.01')
12
+ expect(Tick.toPrice(2000)).toBe('1.02')
13
+ })
14
+
15
+ test('converts negative ticks correctly', () => {
16
+ expect(Tick.toPrice(-100)).toBe('0.999')
17
+ expect(Tick.toPrice(-1000)).toBe('0.99')
18
+ expect(Tick.toPrice(-2000)).toBe('0.98')
19
+ })
20
+
21
+ test('handles boundary values', () => {
22
+ expect(Tick.toPrice(Tick.minTick)).toBe('0.98')
23
+ expect(Tick.toPrice(Tick.maxTick)).toBe('1.02')
24
+ })
25
+
26
+ test('preserves exact 5 decimal precision', () => {
27
+ expect(Tick.toPrice(1)).toBe('1.00001')
28
+ expect(Tick.toPrice(-1)).toBe('0.99999')
29
+ expect(Tick.toPrice(1234)).toBe('1.01234')
30
+ })
31
+
32
+ test('throws error when tick is below minimum', () => {
33
+ expect(() => Tick.toPrice(-2001)).toThrow(Tick.TickOutOfBoundsError)
34
+ expect(() => Tick.toPrice(-2001)).toThrow('Tick -2001 is out of bounds.')
35
+ expect(() => Tick.toPrice(-3000)).toThrow(Tick.TickOutOfBoundsError)
36
+ })
37
+
38
+ test('throws error when tick is above maximum', () => {
39
+ expect(() => Tick.toPrice(2001)).toThrow(Tick.TickOutOfBoundsError)
40
+ expect(() => Tick.toPrice(2001)).toThrow('Tick 2001 is out of bounds.')
41
+ expect(() => Tick.toPrice(3000)).toThrow(Tick.TickOutOfBoundsError)
42
+ })
43
+
44
+ test('handles edge cases near bounds', () => {
45
+ expect(() => Tick.toPrice(-2000)).not.toThrow()
46
+ expect(() => Tick.toPrice(2000)).not.toThrow()
47
+ })
48
+ })
49
+
50
+ describe('fromPrice', () => {
51
+ test('converts price 1.0 to tick 0', () => {
52
+ expect(Tick.fromPrice('1.0')).toBe(0)
53
+ expect(Tick.fromPrice('1.00000')).toBe(0)
54
+ })
55
+
56
+ test('converts prices above 1.0 correctly', () => {
57
+ expect(Tick.fromPrice('1.001')).toBe(100)
58
+ expect(Tick.fromPrice('1.01')).toBe(1000)
59
+ expect(Tick.fromPrice('1.02')).toBe(2000)
60
+ })
61
+
62
+ test('converts prices below 1.0 correctly', () => {
63
+ expect(Tick.fromPrice('0.999')).toBe(-100)
64
+ expect(Tick.fromPrice('0.99')).toBe(-1000)
65
+ expect(Tick.fromPrice('0.98')).toBe(-2000)
66
+ })
67
+
68
+ test('handles boundary values', () => {
69
+ expect(Tick.fromPrice('0.98')).toBe(Tick.minTick)
70
+ expect(Tick.fromPrice('1.02')).toBe(Tick.maxTick)
71
+ })
72
+
73
+ test('handles different decimal precisions', () => {
74
+ expect(Tick.fromPrice('1.00001')).toBe(1)
75
+ expect(Tick.fromPrice('1.0001')).toBe(10)
76
+ expect(Tick.fromPrice('1.001')).toBe(100)
77
+ expect(Tick.fromPrice('0.99999')).toBe(-1)
78
+ })
79
+
80
+ test('truncates beyond 5 decimal places', () => {
81
+ // Should truncate, not round, extra decimals
82
+ expect(Tick.fromPrice('1.000019')).toBe(1) // Takes first 5: 1.00001
83
+ expect(Tick.fromPrice('1.000015')).toBe(1) // Takes first 5: 1.00001
84
+ })
85
+
86
+ test('validates string format', () => {
87
+ expect(() => Tick.fromPrice('abc')).toThrow(Tick.InvalidPriceFormatError)
88
+ expect(() => Tick.fromPrice('abc')).toThrow('Invalid price format')
89
+ expect(() => Tick.fromPrice('1.2.3')).toThrow(Tick.InvalidPriceFormatError)
90
+ expect(() => Tick.fromPrice('')).toThrow(Tick.InvalidPriceFormatError)
91
+ })
92
+
93
+ test('throws error when price results in tick below minimum', () => {
94
+ expect(() => Tick.fromPrice('0.979')).toThrow(Tick.PriceOutOfBoundsError)
95
+ expect(() => Tick.fromPrice('0.979')).toThrow(
96
+ 'Price "0.979" results in tick -2100 which is out of bounds.',
97
+ )
98
+ expect(() => Tick.fromPrice('0.5')).toThrow(Tick.PriceOutOfBoundsError)
99
+ })
100
+
101
+ test('throws error when price results in tick above maximum', () => {
102
+ expect(() => Tick.fromPrice('1.021')).toThrow(Tick.PriceOutOfBoundsError)
103
+ expect(() => Tick.fromPrice('1.021')).toThrow(
104
+ 'Price "1.021" results in tick 2100 which is out of bounds.',
105
+ )
106
+ expect(() => Tick.fromPrice('1.5')).toThrow(Tick.PriceOutOfBoundsError)
107
+ })
108
+
109
+ test('handles edge cases near bounds', () => {
110
+ expect(() => Tick.fromPrice('0.98')).not.toThrow()
111
+ expect(() => Tick.fromPrice('1.02')).not.toThrow()
112
+ })
113
+
114
+ test('handles whitespace', () => {
115
+ expect(Tick.fromPrice(' 1.0 ')).toBe(0)
116
+ expect(Tick.fromPrice(' 1.001 ')).toBe(100)
117
+ })
118
+ })
119
+
120
+ describe('round-trip conversions', () => {
121
+ test('tick -> price -> tick preserves tick values exactly', () => {
122
+ const ticks = [-2000, -1000, -100, -1, 0, 1, 100, 1000, 2000]
123
+ for (const tick of ticks) {
124
+ const price = Tick.toPrice(tick)
125
+ const roundTripTick = Tick.fromPrice(price)
126
+ expect(roundTripTick).toBe(tick)
127
+ }
128
+ })
129
+
130
+ test('price -> tick -> price preserves price strings exactly', () => {
131
+ const prices = ['0.98', '0.99', '1', '1.01', '1.02']
132
+ for (const price of prices) {
133
+ const tick = Tick.fromPrice(price)
134
+ const roundTripPrice = Tick.toPrice(tick)
135
+ expect(roundTripPrice).toBe(price)
136
+ }
137
+ })
138
+
139
+ test('handles arbitrary precision in input strings', () => {
140
+ // Shorter decimal strings should round-trip correctly
141
+ expect(Tick.toPrice(Tick.fromPrice('1.0'))).toBe('1')
142
+ expect(Tick.toPrice(Tick.fromPrice('0.999'))).toBe('0.999')
143
+
144
+ // Longer decimal strings get truncated to 5 decimals
145
+ expect(Tick.toPrice(Tick.fromPrice('1.000019999'))).toBe('1.00001')
146
+ })
147
+ })
148
+
149
+ describe('error handling', () => {
150
+ test('InvalidPriceFormatError is catchable and has correct properties', () => {
151
+ try {
152
+ Tick.fromPrice('invalid')
153
+ expect.fail('Should have thrown')
154
+ } catch (error) {
155
+ expect(error).toBeInstanceOf(Tick.InvalidPriceFormatError)
156
+ expect(error).toHaveProperty('name', 'Tick.InvalidPriceFormatError')
157
+ expect((error as Error).message).toContain('Invalid price format')
158
+ expect((error as Error).message).toContain('invalid')
159
+ expect((error as Error).message).toContain(
160
+ 'Price must be a decimal number string',
161
+ )
162
+ }
163
+ })
164
+
165
+ test('TickOutOfBoundsError is catchable and has correct properties', () => {
166
+ try {
167
+ Tick.toPrice(-2001)
168
+ expect.fail('Should have thrown')
169
+ } catch (error) {
170
+ expect(error).toBeInstanceOf(Tick.TickOutOfBoundsError)
171
+ expect(error).toHaveProperty('name', 'Tick.TickOutOfBoundsError')
172
+ // BaseError appends metaMessages to the message
173
+ expect((error as Error).message).toContain('Tick -2001 is out of bounds.')
174
+ expect((error as Error).message).toContain(
175
+ 'Tick must be between -2000 and 2000.',
176
+ )
177
+ }
178
+ })
179
+
180
+ test('PriceOutOfBoundsError is catchable and has correct properties', () => {
181
+ try {
182
+ Tick.fromPrice('0.979')
183
+ expect.fail('Should have thrown')
184
+ } catch (error) {
185
+ expect(error).toBeInstanceOf(Tick.PriceOutOfBoundsError)
186
+ expect(error).toHaveProperty('name', 'Tick.PriceOutOfBoundsError')
187
+ expect((error as Error).message).toContain(
188
+ 'Price "0.979" results in tick -2100 which is out of bounds.',
189
+ )
190
+ expect((error as Error).message).toContain(
191
+ 'Tick must be between -2000 and 2000.',
192
+ )
193
+ }
194
+ })
195
+
196
+ test('can distinguish between error types', () => {
197
+ try {
198
+ Tick.fromPrice('invalid')
199
+ } catch (error) {
200
+ if (error instanceof Tick.InvalidPriceFormatError) {
201
+ expect(true).toBe(true) // Successfully caught as InvalidPriceFormatError
202
+ } else {
203
+ expect.fail('Should be InvalidPriceFormatError')
204
+ }
205
+ }
206
+
207
+ try {
208
+ Tick.toPrice(-2001)
209
+ } catch (error) {
210
+ if (error instanceof Tick.TickOutOfBoundsError) {
211
+ expect(true).toBe(true) // Successfully caught as TickOutOfBoundsError
212
+ } else {
213
+ expect.fail('Should be TickOutOfBoundsError')
214
+ }
215
+ }
216
+
217
+ try {
218
+ Tick.fromPrice('0.979')
219
+ } catch (error) {
220
+ if (error instanceof Tick.PriceOutOfBoundsError) {
221
+ expect(true).toBe(true) // Successfully caught as PriceOutOfBoundsError
222
+ } else {
223
+ expect.fail('Should be PriceOutOfBoundsError')
224
+ }
225
+ }
226
+ })
227
+ })
228
+
229
+ describe('precision and edge cases', () => {
230
+ test('handles very small price increments', () => {
231
+ // 0.1 bps = 0.001% = 0.00001 in decimal
232
+ expect(Tick.fromPrice('1.00001')).toBe(1)
233
+ expect(Tick.fromPrice('1.00002')).toBe(2)
234
+ })
235
+
236
+ test('price scale maintains exact 5 decimal precision', () => {
237
+ // The price scale is 100,000 which gives us exactly 5 decimal places
238
+ const price0 = Tick.toPrice(0)
239
+ const price1 = Tick.toPrice(1)
240
+ expect(price0).toBe('1')
241
+ expect(price1).toBe('1.00001')
242
+
243
+ // Verify exact difference
244
+ const diff = Number(price1) - Number(price0)
245
+ expect(diff).toBeCloseTo(0.00001, 10)
246
+ })
247
+
248
+ test('symmetric around 1.0', () => {
249
+ // +100 ticks should have same magnitude as -100 ticks from 1.0
250
+ const priceUp = Tick.toPrice(100)
251
+ const priceDown = Tick.toPrice(-100)
252
+ expect(priceUp).toBe('1.001')
253
+ expect(priceDown).toBe('0.999')
254
+
255
+ const upDiff = Number(priceUp) - 1.0
256
+ const downDiff = 1.0 - Number(priceDown)
257
+ expect(upDiff).toBeCloseTo(downDiff, 10)
258
+ })
259
+
260
+ test('validates 2% range', () => {
261
+ // At minimum tick, price should be exactly 2% below 1.0
262
+ const minPrice = Tick.toPrice(Tick.minTick)
263
+ expect(minPrice).toBe('0.98')
264
+ expect((1.0 - Number(minPrice)) / 1.0).toBeCloseTo(0.02, 10)
265
+
266
+ // At maximum tick, price should be exactly 2% above 1.0
267
+ const maxPrice = Tick.toPrice(Tick.maxTick)
268
+ expect(maxPrice).toBe('1.02')
269
+ expect((Number(maxPrice) - 1.0) / 1.0).toBeCloseTo(0.02, 10)
270
+ })
271
+
272
+ test('no floating point precision errors', () => {
273
+ // String-based parsing should eliminate float precision issues
274
+ expect(Tick.fromPrice('0.99999')).toBe(-1)
275
+ expect(Tick.fromPrice('1.00001')).toBe(1)
276
+
277
+ // These would potentially fail with float arithmetic
278
+ expect(Tick.fromPrice('0.98001')).toBe(-1999)
279
+ expect(Tick.fromPrice('1.01999')).toBe(1999)
280
+ })
281
+ })
package/tempo/Tick.ts ADDED
@@ -0,0 +1,186 @@
1
+ import * as Errors from '../core/Errors.js'
2
+
3
+ /**
4
+ * Minimum allowed tick value (-2% from peg).
5
+ *
6
+ * [Stablecoin DEX Pricing](https://docs.tempo.xyz/protocol/exchange/spec#key-concepts)
7
+ */
8
+ export const minTick = -2000
9
+
10
+ /**
11
+ * Maximum allowed tick value (+2% from peg).
12
+ *
13
+ * [Stablecoin DEX Pricing](https://docs.tempo.xyz/protocol/exchange/spec#key-concepts)
14
+ */
15
+ export const maxTick = 2000
16
+
17
+ /**
18
+ * Price scaling factor (5 decimal places for 0.1 bps precision).
19
+ *
20
+ * The DEX uses a tick-based pricing system where `price = PRICE_SCALE + tick`.
21
+ * Orders must be placed at ticks divisible by `TICK_SPACING = 10` (1 bp grid).
22
+ *
23
+ * [Stablecoin DEX Pricing](https://docs.tempo.xyz/protocol/exchange/spec#key-concepts)
24
+ */
25
+ export const priceScale = 100_000
26
+
27
+ /**
28
+ * Tick type.
29
+ */
30
+ export type Tick = number
31
+
32
+ /**
33
+ * Converts a tick to a price string.
34
+ *
35
+ * [Stablecoin DEX Pricing](https://docs.tempo.xyz/protocol/exchange/spec#key-concepts)
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { Tick } from 'ox/tempo'
40
+ *
41
+ * // Tick 0 = price of 1.0
42
+ * const price1 = Tick.toPrice(0) // "1"
43
+ *
44
+ * // Tick 100 = price of 1.001 (0.1% higher)
45
+ * const price2 = Tick.toPrice(100) // "1.001"
46
+ *
47
+ * // Tick -100 = price of 0.999 (0.1% lower)
48
+ * const price3 = Tick.toPrice(-100) // "0.999"
49
+ * ```
50
+ *
51
+ * @param tick - The tick value (range: -2000 to +2000).
52
+ * @returns The price as a string with exact decimal representation.
53
+ * @throws `TickOutOfBoundsError` If tick is out of bounds.
54
+ */
55
+ export function toPrice(tick: toPrice.Tick): toPrice.ReturnType {
56
+ if (tick < minTick || tick > maxTick) {
57
+ throw new TickOutOfBoundsError({ tick })
58
+ }
59
+ // Use integer arithmetic to avoid floating point errors
60
+ const price = priceScale + tick
61
+ const whole = Math.floor(price / priceScale)
62
+
63
+ let decimal = (price % priceScale).toString().padStart(5, '0')
64
+ decimal = decimal.replace(/0+$/, '')
65
+
66
+ if (decimal.length === 0) return whole.toString()
67
+ return `${whole}.${decimal}`
68
+ }
69
+
70
+ export declare namespace toPrice {
71
+ export type Tick = number
72
+ export type ReturnType = string
73
+ }
74
+
75
+ /**
76
+ * Converts a price string to a tick.
77
+ *
78
+ * [Stablecoin DEX Pricing](https://docs.tempo.xyz/protocol/exchange/spec#key-concepts)
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * import { Tick } from 'ox/tempo'
83
+ *
84
+ * // Price of 1.0 = tick 0
85
+ * const tick1 = Tick.fromPrice('1.0') // 0
86
+ * const tick2 = Tick.fromPrice('1.00000') // 0
87
+ *
88
+ * // Price of 1.001 = tick 100
89
+ * const tick3 = Tick.fromPrice('1.001') // 100
90
+ *
91
+ * // Price of 0.999 = tick -100
92
+ * const tick4 = Tick.fromPrice('0.999') // -100
93
+ * ```
94
+ *
95
+ * @param price - The price as a string (e.g., "1.001", "0.999").
96
+ * @returns The tick value.
97
+ */
98
+ export function fromPrice(price: fromPrice.Price): fromPrice.ReturnType {
99
+ const priceStr = price.trim()
100
+ if (!/^-?\d+(\.\d+)?$/.test(priceStr))
101
+ throw new InvalidPriceFormatError({ price })
102
+
103
+ // Parse price using string manipulation to avoid float precision issues
104
+ const [w, d = '0'] = priceStr.split('.')
105
+ const whole = BigInt(w!)
106
+
107
+ // Pad or truncate decimal to exactly 5 digits
108
+ const decimal = BigInt(d.padEnd(5, '0').slice(0, 5))
109
+
110
+ // Calculate price
111
+ const priceInt = whole * BigInt(priceScale) + decimal
112
+
113
+ // Calculate tick
114
+ const tick = Number(priceInt - BigInt(priceScale))
115
+
116
+ if (tick < minTick || tick > maxTick)
117
+ throw new PriceOutOfBoundsError({ price, tick })
118
+
119
+ return tick
120
+ }
121
+
122
+ export declare namespace fromPrice {
123
+ export type Price = string
124
+ export type ReturnType = number
125
+ }
126
+
127
+ /**
128
+ * Error thrown when a tick value is out of the allowed bounds.
129
+ */
130
+ export class TickOutOfBoundsError extends Errors.BaseError {
131
+ override readonly name = 'Tick.TickOutOfBoundsError'
132
+
133
+ constructor(options: TickOutOfBoundsError.Options) {
134
+ super(`Tick ${options.tick} is out of bounds.`, {
135
+ metaMessages: [`Tick must be between ${minTick} and ${maxTick}.`],
136
+ })
137
+ }
138
+ }
139
+
140
+ export declare namespace TickOutOfBoundsError {
141
+ export type Options = {
142
+ tick: number
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Error thrown when a price string has an invalid format.
148
+ */
149
+ export class InvalidPriceFormatError extends Errors.BaseError {
150
+ override readonly name = 'Tick.InvalidPriceFormatError'
151
+
152
+ constructor(options: InvalidPriceFormatError.Options) {
153
+ super(`Invalid price format: "${options.price}".`, {
154
+ metaMessages: ['Price must be a decimal number string (e.g., "1.001").'],
155
+ })
156
+ }
157
+ }
158
+
159
+ export declare namespace InvalidPriceFormatError {
160
+ export type Options = {
161
+ price: string
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Error thrown when a price string results in an out-of-bounds tick.
167
+ */
168
+ export class PriceOutOfBoundsError extends Errors.BaseError {
169
+ override readonly name = 'Tick.PriceOutOfBoundsError'
170
+
171
+ constructor(options: PriceOutOfBoundsError.Options) {
172
+ super(
173
+ `Price "${options.price}" results in tick ${options.tick} which is out of bounds.`,
174
+ {
175
+ metaMessages: [`Tick must be between ${minTick} and ${maxTick}.`],
176
+ },
177
+ )
178
+ }
179
+ }
180
+
181
+ export declare namespace PriceOutOfBoundsError {
182
+ export type Options = {
183
+ price: string
184
+ tick: number
185
+ }
186
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/TokenId.d.ts",
4
+ "main": "../../_cjs/tempo/TokenId.js",
5
+ "module": "../../_esm/tempo/TokenId.js"
6
+ }
@@ -0,0 +1,40 @@
1
+ import { TokenId } from 'ox/tempo'
2
+ import { expect, test } from 'vitest'
3
+
4
+ test('from', () => {
5
+ expect(TokenId.from(0n)).toBe(0n)
6
+ expect(TokenId.from(0x1)).toBe(1n)
7
+ expect(TokenId.from(0xdef)).toBe(0xdefn)
8
+ expect(TokenId.from('0x20c0000000000000000000000000000000000000')).toBe(0n)
9
+ expect(TokenId.from('0x20c0000000000000000000000000000000000001')).toBe(1n)
10
+ expect(TokenId.from('0x20c0000000000000000000000000000000000def')).toBe(
11
+ 0xdefn,
12
+ )
13
+ })
14
+
15
+ test('fromAddress', () => {
16
+ expect(
17
+ TokenId.fromAddress('0x20c0000000000000000000000000000000000000'),
18
+ ).toBe(0n)
19
+ expect(
20
+ TokenId.fromAddress('0x20c0000000000000000000000000000000000001'),
21
+ ).toBe(1n)
22
+ expect(
23
+ TokenId.fromAddress('0x20c0000000000000000000000000000000000def'),
24
+ ).toBe(0xdefn)
25
+ })
26
+
27
+ test('toAddress', () => {
28
+ expect(TokenId.toAddress(0n)).toBe(
29
+ '0x20c0000000000000000000000000000000000000',
30
+ )
31
+ expect(TokenId.toAddress('0x20c0000000000000000000000000000000000000')).toBe(
32
+ '0x20c0000000000000000000000000000000000000',
33
+ )
34
+ expect(TokenId.toAddress(1n)).toBe(
35
+ '0x20c0000000000000000000000000000000000001',
36
+ )
37
+ expect(TokenId.toAddress(0xdefn)).toBe(
38
+ '0x20c0000000000000000000000000000000000def',
39
+ )
40
+ })
@@ -0,0 +1,80 @@
1
+ import * as Address from '../core/Address.js'
2
+ import * as Hex from '../core/Hex.js'
3
+
4
+ const tip20Prefix = '0x20c0'
5
+
6
+ export type TokenId = bigint
7
+ export type TokenIdOrAddress = TokenId | Address.Address
8
+
9
+ /**
10
+ * Converts a token ID or address to a token ID.
11
+ *
12
+ * TIP-20 is Tempo's native token standard for stablecoins with deterministic addresses
13
+ * derived from sequential token IDs (prefix `0x20c0`).
14
+ *
15
+ * [TIP-20 Token Standard](https://docs.tempo.xyz/protocol/tip20/overview)
16
+ *
17
+ * @example
18
+ * ```ts twoslash
19
+ * import { TokenId } from 'ox/tempo'
20
+ *
21
+ * const tokenId = TokenId.from(1n)
22
+ * ```
23
+ *
24
+ * @param tokenIdOrAddress - The token ID or address.
25
+ * @returns The token ID.
26
+ */
27
+ export function from(tokenIdOrAddress: TokenIdOrAddress | number): TokenId {
28
+ if (
29
+ typeof tokenIdOrAddress === 'bigint' ||
30
+ typeof tokenIdOrAddress === 'number'
31
+ )
32
+ return BigInt(tokenIdOrAddress)
33
+ return fromAddress(tokenIdOrAddress)
34
+ }
35
+
36
+ /**
37
+ * Converts a TIP-20 token address to a token ID.
38
+ *
39
+ * [TIP-20 Token Standard](https://docs.tempo.xyz/protocol/tip20/overview)
40
+ *
41
+ * @example
42
+ * ```ts twoslash
43
+ * import { TokenId } from 'ox/tempo'
44
+ *
45
+ * const tokenId = TokenId.fromAddress('0x20c00000000000000000000000000000000000000001')
46
+ * ```
47
+ *
48
+ * @param address - The token address.
49
+ * @returns The token ID.
50
+ */
51
+ export function fromAddress(address: Address.Address): TokenId {
52
+ if (!address.toLowerCase().startsWith(tip20Prefix))
53
+ throw new Error('invalid tip20 address.')
54
+ return Hex.toBigInt(Hex.slice(address, tip20Prefix.length))
55
+ }
56
+
57
+ /**
58
+ * Converts a TIP-20 token ID to an address.
59
+ *
60
+ * [TIP-20 Token Standard](https://docs.tempo.xyz/protocol/tip20/overview)
61
+ *
62
+ * @example
63
+ * ```ts twoslash
64
+ * import { TokenId } from 'ox/tempo'
65
+ *
66
+ * const address = TokenId.toAddress(1n)
67
+ * ```
68
+ *
69
+ * @param tokenId - The token ID.
70
+ * @returns The address.
71
+ */
72
+ export function toAddress(tokenId: TokenIdOrAddress): Address.Address {
73
+ if (typeof tokenId === 'string') {
74
+ Address.assert(tokenId)
75
+ return tokenId
76
+ }
77
+
78
+ const tokenIdHex = Hex.fromNumber(tokenId, { size: 18 })
79
+ return Hex.concat(tip20Prefix, tokenIdHex)
80
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/TokenRole.d.ts",
4
+ "main": "../../_cjs/tempo/TokenRole.js",
5
+ "module": "../../_esm/tempo/TokenRole.js"
6
+ }
@@ -0,0 +1,16 @@
1
+ import { Hash, Hex } from 'ox'
2
+ import { TokenRole } from 'ox/tempo'
3
+ import { expect, test } from 'vitest'
4
+
5
+ test('serialize', () => {
6
+ TokenRole.roles.forEach((role) => {
7
+ if (role === 'defaultAdmin')
8
+ expect(TokenRole.serialize(role)).toBe(
9
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
10
+ )
11
+ else
12
+ expect(TokenRole.serialize(role)).toBe(
13
+ Hash.keccak256(Hex.fromString(TokenRole.toPreHashed[role])),
14
+ )
15
+ })
16
+ })
@@ -0,0 +1,45 @@
1
+ import * as Hash from '../core/Hash.js'
2
+ import * as Hex from '../core/Hex.js'
3
+
4
+ export const roles = [
5
+ 'defaultAdmin',
6
+ 'pause',
7
+ 'unpause',
8
+ 'issuer',
9
+ 'burnBlocked',
10
+ ] as const
11
+ export type TokenRole = (typeof roles)[number]
12
+
13
+ export const toPreHashed = {
14
+ defaultAdmin: 'DEFAULT_ADMIN_ROLE',
15
+ pause: 'PAUSE_ROLE',
16
+ unpause: 'UNPAUSE_ROLE',
17
+ issuer: 'ISSUER_ROLE',
18
+ burnBlocked: 'BURN_BLOCKED_ROLE',
19
+ } as const satisfies Record<TokenRole, string>
20
+
21
+ /**
22
+ * Serializes a token role to its keccak256 hash representation.
23
+ *
24
+ * TIP-20 includes a built-in RBAC system with roles like `ISSUER_ROLE` (mint/burn),
25
+ * `PAUSE_ROLE`/`UNPAUSE_ROLE` (emergency controls), and `BURN_BLOCKED_ROLE` (compliance).
26
+ *
27
+ * [TIP-20 RBAC](https://docs.tempo.xyz/protocol/tip20/overview#role-based-access-control-rbac)
28
+ *
29
+ * @example
30
+ * ```ts twoslash
31
+ * import { TokenRole } from 'ox/tempo'
32
+ *
33
+ * const hash = TokenRole.serialize('issuer')
34
+ * ```
35
+ *
36
+ * @param role - The token role to serialize.
37
+ * @returns The keccak256 hash of the role.
38
+ */
39
+ export function serialize(role: TokenRole) {
40
+ if (role === 'defaultAdmin')
41
+ return '0x0000000000000000000000000000000000000000000000000000000000000000'
42
+ return Hash.keccak256(
43
+ Hex.fromString(toPreHashed[role as keyof typeof toPreHashed] ?? role),
44
+ )
45
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/Transaction.d.ts",
4
+ "main": "../../_cjs/tempo/Transaction.js",
5
+ "module": "../../_esm/tempo/Transaction.js"
6
+ }