@indigo-labs/indigo-sdk 0.2.42 → 0.3.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 (201) hide show
  1. package/.github/workflows/ci.yml +4 -2
  2. package/dist/index.d.mts +3008 -2194
  3. package/dist/index.d.ts +3008 -2194
  4. package/dist/index.js +9827 -6194
  5. package/dist/index.mjs +8591 -4809
  6. package/package.json +14 -3
  7. package/src/contracts/cdp/helpers.ts +68 -72
  8. package/src/contracts/cdp/scripts.ts +50 -13
  9. package/src/contracts/cdp/transactions.ts +831 -545
  10. package/src/contracts/cdp/types-new.ts +256 -0
  11. package/src/contracts/cdp/types.ts +26 -144
  12. package/src/contracts/cdp-creator/scripts.ts +15 -9
  13. package/src/contracts/cdp-creator/types-new.ts +50 -0
  14. package/src/contracts/cdp-creator/types.ts +5 -31
  15. package/src/contracts/collector/scripts.ts +1 -1
  16. package/src/contracts/collector/transactions.ts +23 -13
  17. package/src/contracts/collector/types-new.ts +17 -0
  18. package/src/contracts/execute/scripts.ts +19 -10
  19. package/src/contracts/execute/types-new.ts +44 -0
  20. package/src/contracts/execute/types.ts +5 -38
  21. package/src/contracts/gov/helpers.ts +187 -51
  22. package/src/contracts/gov/scripts.ts +17 -10
  23. package/src/contracts/gov/transactions.ts +599 -271
  24. package/src/contracts/gov/types-new.ts +253 -100
  25. package/src/contracts/gov/types.ts +4 -71
  26. package/src/contracts/iasset/helpers.ts +172 -0
  27. package/src/contracts/iasset/scripts.ts +38 -0
  28. package/src/contracts/iasset/types.ts +154 -0
  29. package/src/contracts/initialize/actions.ts +768 -0
  30. package/src/contracts/initialize/helpers.ts +611 -36
  31. package/src/contracts/initialize/types.ts +102 -28
  32. package/src/contracts/interest-collection/helpers.ts +19 -0
  33. package/src/contracts/interest-collection/scripts.ts +44 -0
  34. package/src/contracts/interest-collection/transactions.ts +436 -0
  35. package/src/contracts/interest-collection/types-new.ts +50 -0
  36. package/src/contracts/interest-collection/types.ts +26 -0
  37. package/src/contracts/interest-oracle/helpers.ts +2 -30
  38. package/src/contracts/interest-oracle/scripts.ts +1 -1
  39. package/src/contracts/interest-oracle/transactions.ts +21 -16
  40. package/src/contracts/interest-oracle/types-new.ts +32 -0
  41. package/src/contracts/interest-oracle/types.ts +1 -40
  42. package/src/contracts/one-shot/transactions.ts +1 -2
  43. package/src/contracts/poll/helpers.ts +5 -23
  44. package/src/contracts/poll/scripts.ts +12 -13
  45. package/src/contracts/poll/types-poll-manager.ts +1 -19
  46. package/src/contracts/poll/types-poll-new.ts +170 -0
  47. package/src/contracts/poll/types-poll-shard.ts +2 -24
  48. package/src/contracts/price-oracle/helpers.ts +1 -4
  49. package/src/contracts/price-oracle/scripts.ts +3 -8
  50. package/src/contracts/price-oracle/transactions.ts +32 -25
  51. package/src/contracts/price-oracle/types-new.ts +50 -0
  52. package/src/contracts/price-oracle/types.ts +2 -36
  53. package/src/contracts/pyth-feed/helpers.ts +58 -0
  54. package/src/contracts/pyth-feed/scripts.ts +15 -0
  55. package/src/contracts/pyth-feed/types.ts +181 -0
  56. package/src/contracts/rob/helpers.ts +405 -0
  57. package/src/contracts/rob/scripts.ts +35 -0
  58. package/src/contracts/rob/transactions.ts +410 -0
  59. package/src/contracts/rob/types-new.ts +128 -0
  60. package/src/contracts/rob/types.ts +16 -0
  61. package/src/contracts/rob-leverage/helpers.ts +424 -0
  62. package/src/contracts/{leverage → rob-leverage}/transactions.ts +68 -48
  63. package/src/contracts/stability-pool/helpers.ts +714 -230
  64. package/src/contracts/stability-pool/scripts.ts +20 -15
  65. package/src/contracts/stability-pool/transactions.ts +625 -493
  66. package/src/contracts/stability-pool/types-new.ts +237 -100
  67. package/src/contracts/stability-pool/types.ts +5 -22
  68. package/src/contracts/stableswap/helpers.ts +22 -0
  69. package/src/contracts/stableswap/scripts.ts +37 -0
  70. package/src/contracts/stableswap/transactions.ts +647 -0
  71. package/src/contracts/stableswap/types-new.ts +131 -0
  72. package/src/contracts/stableswap/types.ts +17 -0
  73. package/src/contracts/staking/helpers.ts +49 -34
  74. package/src/contracts/staking/scripts.ts +1 -1
  75. package/src/contracts/staking/transactions.ts +85 -130
  76. package/src/contracts/staking/types-new.ts +60 -28
  77. package/src/contracts/staking/types.ts +1 -28
  78. package/src/contracts/treasury/helpers.ts +21 -0
  79. package/src/contracts/treasury/scripts.ts +16 -26
  80. package/src/contracts/treasury/transactions.ts +256 -27
  81. package/src/contracts/treasury/types-new.ts +69 -0
  82. package/src/contracts/treasury/types.ts +2 -43
  83. package/src/contracts/version-registry/scripts.ts +2 -2
  84. package/src/contracts/version-registry/types-new.ts +6 -7
  85. package/src/index.ts +37 -20
  86. package/src/scripts/auth-token-policy.ts +3 -2
  87. package/src/scripts/iasset-policy.ts +3 -2
  88. package/src/types/evolution-schema-options.ts +3 -3
  89. package/src/types/generic.ts +17 -89
  90. package/src/types/multisig.ts +48 -0
  91. package/src/types/on-chain-decimal.ts +14 -7
  92. package/src/types/rational.ts +61 -0
  93. package/src/types/system-params.ts +237 -41
  94. package/src/utils/array-utils.ts +70 -1
  95. package/src/utils/bigint-utils.ts +12 -0
  96. package/src/utils/indigo-helpers.ts +8 -10
  97. package/src/utils/lucid-utils.ts +47 -40
  98. package/src/utils/oracle-helpers.ts +62 -0
  99. package/src/utils/pyth/decode.ts +223 -0
  100. package/src/utils/pyth/encode.ts +262 -0
  101. package/src/utils/pyth/index.ts +14 -0
  102. package/src/utils/pyth/types.ts +87 -0
  103. package/src/validators/always-succeed-validator.ts +6 -0
  104. package/src/validators/cdp-creator-validator.ts +2 -2
  105. package/src/validators/cdp-redeem-validator.ts +7 -0
  106. package/src/validators/cdp-validator.ts +2 -2
  107. package/src/validators/collector-validator.ts +2 -2
  108. package/src/validators/execute-validator.ts +2 -2
  109. package/src/validators/governance-validator.ts +2 -2
  110. package/src/validators/iasset-validator.ts +7 -0
  111. package/src/validators/interest-collection-validator.ts +7 -0
  112. package/src/validators/interest-oracle-validator.ts +2 -2
  113. package/src/validators/poll-manager-validator.ts +2 -2
  114. package/src/validators/poll-shard-validator.ts +2 -2
  115. package/src/validators/price-oracle-validator.ts +7 -0
  116. package/src/validators/pyth-feed-validator.ts +7 -0
  117. package/src/validators/rob-validator.ts +7 -0
  118. package/src/validators/stability-pool-validator.ts +2 -2
  119. package/src/validators/stableswap-validator.ts +7 -0
  120. package/src/validators/staking-validator.ts +2 -2
  121. package/src/validators/treasury-validator.ts +2 -2
  122. package/src/validators/version-record-policy.ts +2 -2
  123. package/src/validators/version-registry-validator.ts +2 -2
  124. package/tests/always-succeed/script.ts +7 -0
  125. package/tests/bigint-utils.test.ts +41 -0
  126. package/tests/cdp/actions.ts +611 -0
  127. package/tests/cdp/cdp-helpers.ts +55 -0
  128. package/tests/cdp/cdp-queries.ts +440 -0
  129. package/tests/cdp/cdp.test.ts +6087 -0
  130. package/tests/cdp/transactions-mutated.ts +1729 -0
  131. package/tests/data/system-params.json +177 -34
  132. package/tests/datums.test.ts +209 -210
  133. package/tests/endpoints/initialize.ts +68 -0
  134. package/tests/endpoints/interest-collector.ts +37 -0
  135. package/tests/endpoints/treasury.ts +70 -0
  136. package/tests/gov/actions.ts +406 -0
  137. package/tests/gov/gov.test.ts +4450 -0
  138. package/tests/{queries → gov}/governance-queries.ts +6 -3
  139. package/tests/hash-checks.test.ts +38 -11
  140. package/tests/indigo-test-helpers.ts +100 -0
  141. package/tests/initialize.test.ts +61 -9
  142. package/tests/interest-collection/interest-collection.test.ts +892 -0
  143. package/tests/interest-collection/interest-collector-queries.ts +49 -0
  144. package/tests/interest-collection/transactions-mutated.ts +260 -0
  145. package/tests/interest-oracle.test.ts +43 -35
  146. package/tests/mock/assets-mock.ts +234 -23
  147. package/tests/mock/protocol-params-mock.ts +21 -0
  148. package/tests/price-oracle/actions.ts +163 -0
  149. package/tests/price-oracle/price-oracle-queries.ts +12 -0
  150. package/tests/price-oracle/price-oracle.test.ts +240 -0
  151. package/tests/price-oracle/transactions-mutated.ts +62 -0
  152. package/tests/pyth/endpoints.ts +96 -0
  153. package/tests/pyth/helpers.ts +37 -0
  154. package/tests/pyth/pyth-encoding.test.ts +376 -0
  155. package/tests/pyth/pyth-indigo.test.ts +509 -0
  156. package/tests/pyth/pyth.test.ts +300 -0
  157. package/tests/queries/execute-queries.ts +6 -5
  158. package/tests/queries/iasset-queries.ts +175 -5
  159. package/tests/queries/interest-oracle-queries.ts +4 -2
  160. package/tests/queries/poll-queries.ts +8 -9
  161. package/tests/queries/stability-pool-queries.ts +95 -48
  162. package/tests/queries/staking-queries.ts +4 -2
  163. package/tests/queries/treasury-queries.ts +80 -5
  164. package/tests/rob/actions.ts +58 -0
  165. package/tests/{lrp-leverage.test.ts → rob/rob-leverage.test.ts} +393 -296
  166. package/tests/rob/rob-queries.ts +95 -0
  167. package/tests/rob/rob.test.ts +3762 -0
  168. package/tests/rob/transactions-mutated.ts +853 -0
  169. package/tests/script-size.test.ts +240 -0
  170. package/tests/setup.ts +135 -0
  171. package/tests/stability-pool/actions.ts +210 -0
  172. package/tests/stability-pool.test.ts +5469 -666
  173. package/tests/stableswap/stableswap-actions.ts +84 -0
  174. package/tests/stableswap/stableswap-queries.ts +89 -0
  175. package/tests/stableswap/stableswap.test.ts +3891 -0
  176. package/tests/stableswap/transactions-mutated.ts +348 -0
  177. package/tests/staking.test.ts +82 -99
  178. package/tests/test-helpers.ts +58 -11
  179. package/tests/treasury.test.ts +242 -0
  180. package/tests/utils/asserts.ts +74 -0
  181. package/tests/utils/benchmark-utils.ts +81 -0
  182. package/tests/utils/index.ts +122 -4
  183. package/tsconfig.json +9 -1
  184. package/vitest.config.ts +3 -1
  185. package/src/contracts/collector/types.ts +0 -16
  186. package/src/contracts/initialize/transactions.ts +0 -891
  187. package/src/contracts/leverage/helpers.ts +0 -424
  188. package/src/contracts/lrp/helpers.ts +0 -294
  189. package/src/contracts/lrp/scripts.ts +0 -27
  190. package/src/contracts/lrp/transactions.ts +0 -250
  191. package/src/contracts/lrp/types.ts +0 -131
  192. package/src/contracts/poll/types-poll.ts +0 -88
  193. package/src/contracts/vesting/helpers.ts +0 -218
  194. package/src/utils/value-helpers.ts +0 -37
  195. package/src/validators/lrp-validator.ts +0 -7
  196. package/tests/cdp.test.ts +0 -1528
  197. package/tests/gov.test.ts +0 -2011
  198. package/tests/lrp.test.ts +0 -673
  199. package/tests/queries/cdp-queries.ts +0 -220
  200. package/tests/queries/lrp-queries.ts +0 -76
  201. package/tests/queries/price-oracle-queries.ts +0 -10
@@ -0,0 +1,376 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import {
3
+ decodePriceUpdate,
4
+ decodePythMessage,
5
+ encodePriceUpdate,
6
+ encodePythMessage,
7
+ encodeSignedPythMessage,
8
+ PRICE_UPDATE_MAGIC,
9
+ SOLANA_FORMAT_MAGIC,
10
+ type PriceUpdate,
11
+ type PythMessageParts,
12
+ } from '../../src/utils/pyth';
13
+
14
+ /** Generate a 32-byte Ed25519 secret key for tests. */
15
+ function randomSecretKey(): Uint8Array {
16
+ return crypto.getRandomValues(new Uint8Array(32));
17
+ }
18
+
19
+ describe('Pyth > Encoding', () => {
20
+ test('encodePriceUpdate uses Aiken price_update format (magic + timestamp + channel u8 + feeds_len u8 + property-list feeds)', () => {
21
+ const timestampUs = 1000000n;
22
+ const update: PriceUpdate = {
23
+ timestampUs: timestampUs.toString(),
24
+ channelId: 0,
25
+ priceFeeds: [
26
+ {
27
+ priceFeedId: 1,
28
+ exponent: -8,
29
+ price: '50000000',
30
+ feedUpdateTimestamp: Number(timestampUs),
31
+ },
32
+ {
33
+ priceFeedId: 2,
34
+ exponent: 0,
35
+ price: '-100',
36
+ feedUpdateTimestamp: Number(timestampUs + 1n),
37
+ },
38
+ ],
39
+ };
40
+ const payload = encodePriceUpdate(update);
41
+ expect(Array.from(payload.subarray(0, 4))).toEqual(
42
+ Array.from(PRICE_UPDATE_MAGIC),
43
+ );
44
+ // magic(4) + timestamp(8) + channel(1) + feeds_len(1) + 2 feeds
45
+ // For this particular update, each feed encodes 13 properties:
46
+ // Price, BestBidPrice, BestAskPrice, PublisherCount, Exponent, Confidence,
47
+ // FundingRate, FundingTimestamp, FundingRateInterval, MarketSession,
48
+ // EmaPrice, EmaConfidence, FeedUpdateTimestamp.
49
+ // One feed layout:
50
+ // - feed_id u32 (4)
51
+ // - properties_len u8 (1)
52
+ // - properties bytes:
53
+ // Price: 1 (id) + 8 (i64)
54
+ // BestBidPrice: 1 (id) + 8 (i64)
55
+ // BestAskPrice: 1 (id) + 8 (i64)
56
+ // PublisherCount: 1 (id) + 2 (u16)
57
+ // Exponent: 1 (id) + 2 (i16)
58
+ // Confidence: 1 (id) + 8 (i64)
59
+ // FundingRate: 1 (id) + 1 (none tag)
60
+ // FundingTimestamp: 1 (id) + 1 (none tag)
61
+ // FundingRateInterval:1 (id) + 1 (none tag)
62
+ // MarketSession: 1 (id) + 2 (u16)
63
+ // EmaPrice: 1 (id) + 8 (i64)
64
+ // EmaConfidence: 1 (id) + 8 (i64)
65
+ // FeedUpdateTimestamp:1 (id) + 1 (some tag) + 8 (u64)
66
+ const feedBytes =
67
+ 4 + // feed_id
68
+ 1 + // properties_len
69
+ (1 + 8) + // Price
70
+ (1 + 8) + // BestBidPrice
71
+ (1 + 8) + // BestAskPrice
72
+ (1 + 2) + // PublisherCount
73
+ (1 + 2) + // Exponent
74
+ (1 + 8) + // Confidence
75
+ (1 + 1) + // FundingRate (None)
76
+ (1 + 1) + // FundingTimestamp (None)
77
+ (1 + 1) + // FundingRateInterval (None)
78
+ (1 + 2) + // MarketSession
79
+ (1 + 8) + // EmaPrice
80
+ (1 + 8) + // EmaConfidence
81
+ (1 + 1 + 8); // FeedUpdateTimestamp (Some + u64)
82
+ expect(payload.length).toBe(4 + 8 + 1 + 1 + 2 * feedBytes);
83
+ });
84
+
85
+ test('encodeSignedPythMessage returns Pyth wire format (magic + signature + key + size + payload)', async () => {
86
+ const secretKey = randomSecretKey();
87
+ const timestampUs = 2000000n;
88
+ const update: PriceUpdate = {
89
+ timestampUs: timestampUs.toString(),
90
+ channelId: 0,
91
+ priceFeeds: [
92
+ {
93
+ priceFeedId: 42,
94
+ exponent: -9,
95
+ price: '12345',
96
+ feedUpdateTimestamp: Number(timestampUs),
97
+ },
98
+ ],
99
+ };
100
+ const message = await encodeSignedPythMessage(update, secretKey);
101
+ const payload = encodePriceUpdate(update);
102
+ expect(message.length).toBe(4 + 64 + 32 + 2 + payload.length);
103
+ expect(Array.from(message.subarray(0, 4))).toEqual(
104
+ Array.from(SOLANA_FORMAT_MAGIC),
105
+ );
106
+ });
107
+
108
+ test('encodePythMessage produces correct layout (magic + sig + key + u16 len + payload)', async () => {
109
+ const secretKey = randomSecretKey();
110
+ const noble = await import('@noble/ed25519');
111
+ const publicKey = await noble.getPublicKeyAsync(secretKey);
112
+ const timestampUs = 1n;
113
+ const update: PriceUpdate = {
114
+ timestampUs: timestampUs.toString(),
115
+ channelId: 0,
116
+ priceFeeds: [
117
+ {
118
+ priceFeedId: 0,
119
+ exponent: 0,
120
+ price: '0',
121
+ feedUpdateTimestamp: Number(timestampUs),
122
+ },
123
+ ],
124
+ };
125
+ const payload = encodePriceUpdate(update);
126
+ const signature = await noble.signAsync(payload, secretKey);
127
+ const parts: PythMessageParts = { signature, publicKey, payload };
128
+ const message = encodePythMessage(parts);
129
+ expect(message.length).toBe(4 + 64 + 32 + 2 + payload.length);
130
+ expect(Array.from(message.subarray(0, 4))).toEqual(
131
+ Array.from(SOLANA_FORMAT_MAGIC),
132
+ );
133
+ });
134
+
135
+ test('encodePriceUpdate supports full Feed properties', () => {
136
+ const timestampUs = 1772613309000000n;
137
+ const update: PriceUpdate = {
138
+ timestampUs: timestampUs.toString(),
139
+ channelId: 3,
140
+ priceFeeds: [
141
+ {
142
+ priceFeedId: 1,
143
+ price: '6950870030930',
144
+ bestBidPrice: '6950841224625',
145
+ bestAskPrice: '6950918797952',
146
+ exponent: -8,
147
+ marketSession: 'Regular',
148
+ emaPrice: '6891705600000',
149
+ emaConfidence: 6891641300000,
150
+ feedUpdateTimestamp: Number(timestampUs),
151
+ },
152
+ ],
153
+ };
154
+ const payload = encodePriceUpdate(update);
155
+ expect(Array.from(payload.subarray(0, 4))).toEqual(
156
+ Array.from(PRICE_UPDATE_MAGIC),
157
+ );
158
+ expect(payload.length).toBeGreaterThan(4 + 8 + 1 + 1);
159
+ });
160
+
161
+ // Encoding counterpart of Aiken parses_update() test.
162
+ // Same subscription: priceFeedIds [1, 2, 112], properties [price, bestBidPrice, bestAskPrice,
163
+ // exponent, fundingRate, fundingTimestamp, fundingRateInterval, marketSession, emaPrice,
164
+ // emaConfidence, feedUpdateTimestamp], channel fixed_rate@200ms → channel_id 3.
165
+ // Parsed values from streamUpdated.timestampUs and priceFeeds.
166
+ test('encodePriceUpdate matches parses_update subscription (three feeds, full properties)', () => {
167
+ const timestampUs = 1772613309000000n;
168
+ const update: PriceUpdate = {
169
+ timestampUs: timestampUs.toString(),
170
+ channelId: 3,
171
+ priceFeeds: [
172
+ {
173
+ priceFeedId: 1,
174
+ price: '6950870030930',
175
+ bestBidPrice: '6950841224625',
176
+ bestAskPrice: '6950918797952',
177
+ exponent: -8,
178
+ marketSession: 'Regular',
179
+ emaPrice: '6891705600000',
180
+ emaConfidence: 6891641300000,
181
+ feedUpdateTimestamp: Number(timestampUs),
182
+ },
183
+ {
184
+ priceFeedId: 2,
185
+ price: '201300331620',
186
+ bestBidPrice: '201299371656',
187
+ bestAskPrice: '201307527050',
188
+ exponent: -8,
189
+ marketSession: 'Regular',
190
+ emaPrice: '199516364000',
191
+ emaConfidence: 199513381000,
192
+ feedUpdateTimestamp: Number(timestampUs),
193
+ },
194
+ {
195
+ priceFeedId: 112,
196
+ price: '6946927951449',
197
+ exponent: -12,
198
+ fundingRate: 2520,
199
+ fundingTimestamp: 1772613308916265,
200
+ fundingRateInterval: 28800000000,
201
+ marketSession: 'Regular',
202
+ feedUpdateTimestamp: Number(timestampUs),
203
+ },
204
+ ],
205
+ };
206
+
207
+ const payload = encodePriceUpdate(update);
208
+
209
+ expect(Array.from(payload.subarray(0, 4))).toEqual(
210
+ Array.from(PRICE_UPDATE_MAGIC),
211
+ );
212
+
213
+ const view = new DataView(
214
+ payload.buffer,
215
+ payload.byteOffset,
216
+ payload.byteLength,
217
+ );
218
+ const encodedTimestamp = view.getBigUint64(4, true);
219
+ expect(encodedTimestamp).toBe(timestampUs);
220
+
221
+ expect(payload[12]).toBe(3); // channel_id
222
+ expect(payload[13]).toBe(3); // feeds_len
223
+
224
+ // Feed IDs (u32 LE) appear in payload: 1 = 01 00 00 00, 2 = 02 00 00 00, 112 = 70 00 00 00
225
+ const payloadBytes = Array.from(payload);
226
+ const feed1Le = [1, 0, 0, 0];
227
+ const feed2Le = [2, 0, 0, 0];
228
+ const feed112Le = [112, 0, 0, 0];
229
+ const findU32Le = (arr: number[], val: number[]) => {
230
+ for (let i = 0; i <= arr.length - 4; i++) {
231
+ if (
232
+ arr[i] === val[0] &&
233
+ arr[i + 1] === val[1] &&
234
+ arr[i + 2] === val[2] &&
235
+ arr[i + 3] === val[3]
236
+ )
237
+ return i;
238
+ }
239
+ return -1;
240
+ };
241
+ const idx1 = findU32Le(payloadBytes, feed1Le);
242
+ const idx2 = findU32Le(payloadBytes, feed2Le);
243
+ const idx112 = findU32Le(payloadBytes, feed112Le);
244
+ expect(idx1).toBeGreaterThanOrEqual(14);
245
+ expect(idx2).toBeGreaterThan(idx1);
246
+ expect(idx112).toBeGreaterThan(idx2);
247
+ });
248
+ });
249
+
250
+ describe('Pyth > Decoding', () => {
251
+ test('decodePythMessage recovers parts from encodePythMessage', async () => {
252
+ const secretKey = randomSecretKey();
253
+ const noble = await import('@noble/ed25519');
254
+ const publicKey = await noble.getPublicKeyAsync(secretKey);
255
+ const timestampUs = 3000000n;
256
+ const update: PriceUpdate = {
257
+ timestampUs: timestampUs.toString(),
258
+ channelId: 1,
259
+ priceFeeds: [
260
+ {
261
+ priceFeedId: 99,
262
+ exponent: -6,
263
+ price: '42',
264
+ feedUpdateTimestamp: Number(timestampUs),
265
+ },
266
+ ],
267
+ };
268
+ const payload = encodePriceUpdate(update);
269
+ const signature = await noble.signAsync(payload, secretKey);
270
+ const parts: PythMessageParts = { signature, publicKey, payload };
271
+ const message = encodePythMessage(parts);
272
+ const decoded = decodePythMessage(message);
273
+ expect(decoded.payload.length).toBe(payload.length);
274
+ expect(Array.from(decoded.signature)).toEqual(Array.from(signature));
275
+ expect(Array.from(decoded.publicKey)).toEqual(Array.from(publicKey));
276
+ expect(Array.from(decoded.payload)).toEqual(Array.from(payload));
277
+ });
278
+
279
+ test('decodePriceUpdate round-trips encodePriceUpdate (binary identity)', () => {
280
+ const timestampUs = 1000000n;
281
+ const update: PriceUpdate = {
282
+ timestampUs: timestampUs.toString(),
283
+ channelId: 0,
284
+ priceFeeds: [
285
+ {
286
+ priceFeedId: 1,
287
+ exponent: -8,
288
+ price: '50000000',
289
+ feedUpdateTimestamp: Number(timestampUs),
290
+ },
291
+ {
292
+ priceFeedId: 2,
293
+ exponent: 0,
294
+ price: '-100',
295
+ feedUpdateTimestamp: Number(timestampUs + 1n),
296
+ },
297
+ ],
298
+ };
299
+ const encoded = encodePriceUpdate(update);
300
+ const decoded = decodePriceUpdate(encoded);
301
+ const again = encodePriceUpdate(decoded);
302
+ expect(Array.from(again)).toEqual(Array.from(encoded));
303
+ });
304
+
305
+ test('decodePriceUpdate round-trips parses_update-style subscription payload', () => {
306
+ const timestampUs = 1772613309000000n;
307
+ const update: PriceUpdate = {
308
+ timestampUs: timestampUs.toString(),
309
+ channelId: 3,
310
+ priceFeeds: [
311
+ {
312
+ priceFeedId: 1,
313
+ price: '6950870030930',
314
+ bestBidPrice: '6950841224625',
315
+ bestAskPrice: '6950918797952',
316
+ exponent: -8,
317
+ marketSession: 'Regular',
318
+ emaPrice: '6891705600000',
319
+ emaConfidence: 6891641300000,
320
+ feedUpdateTimestamp: Number(timestampUs),
321
+ },
322
+ {
323
+ priceFeedId: 2,
324
+ price: '201300331620',
325
+ bestBidPrice: '201299371656',
326
+ bestAskPrice: '201307527050',
327
+ exponent: -8,
328
+ marketSession: 'Regular',
329
+ emaPrice: '199516364000',
330
+ emaConfidence: 199513381000,
331
+ feedUpdateTimestamp: Number(timestampUs),
332
+ },
333
+ {
334
+ priceFeedId: 112,
335
+ price: '6946927951449',
336
+ exponent: -12,
337
+ fundingRate: 2520,
338
+ fundingTimestamp: 1772613308916265,
339
+ fundingRateInterval: 28800000000,
340
+ marketSession: 'Regular',
341
+ feedUpdateTimestamp: Number(timestampUs),
342
+ },
343
+ ],
344
+ };
345
+ const encoded = encodePriceUpdate(update);
346
+ const decoded = decodePriceUpdate(encoded);
347
+ expect(decoded.timestampUs).toBe(update.timestampUs);
348
+ expect(decoded.channelId).toBe(update.channelId);
349
+ expect(decoded.priceFeeds.length).toBe(3);
350
+ const again = encodePriceUpdate(decoded);
351
+ expect(Array.from(again)).toEqual(Array.from(encoded));
352
+ });
353
+
354
+ test('encodeSignedPythMessage wire: decodePythMessage payload round-trips through decodePriceUpdate', async () => {
355
+ const secretKey = randomSecretKey();
356
+ const timestampUs = 4000000n;
357
+ const update: PriceUpdate = {
358
+ timestampUs: timestampUs.toString(),
359
+ channelId: 0,
360
+ priceFeeds: [
361
+ {
362
+ priceFeedId: 7,
363
+ exponent: -4,
364
+ price: '1',
365
+ feedUpdateTimestamp: Number(timestampUs),
366
+ },
367
+ ],
368
+ };
369
+ const wire = await encodeSignedPythMessage(update, secretKey);
370
+ const parts = decodePythMessage(wire);
371
+ const payloadRoundTrip = encodePriceUpdate(
372
+ decodePriceUpdate(parts.payload),
373
+ );
374
+ expect(Array.from(payloadRoundTrip)).toEqual(Array.from(parts.payload));
375
+ });
376
+ });