@indigo-labs/indigo-sdk 0.2.42 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/.github/workflows/ci.yml +4 -2
  2. package/README.md +88 -15
  3. package/dist/index.d.mts +3012 -2194
  4. package/dist/index.d.ts +3012 -2194
  5. package/dist/index.js +9849 -6198
  6. package/dist/index.mjs +8733 -4933
  7. package/package.json +14 -3
  8. package/src/contracts/cdp/helpers.ts +68 -72
  9. package/src/contracts/cdp/scripts.ts +50 -13
  10. package/src/contracts/cdp/transactions.ts +841 -546
  11. package/src/contracts/cdp/types-new.ts +256 -0
  12. package/src/contracts/cdp/types.ts +26 -144
  13. package/src/contracts/cdp-creator/scripts.ts +15 -9
  14. package/src/contracts/cdp-creator/types-new.ts +50 -0
  15. package/src/contracts/cdp-creator/types.ts +5 -31
  16. package/src/contracts/collector/scripts.ts +1 -1
  17. package/src/contracts/collector/transactions.ts +23 -13
  18. package/src/contracts/collector/types-new.ts +17 -0
  19. package/src/contracts/execute/scripts.ts +19 -10
  20. package/src/contracts/execute/types-new.ts +44 -0
  21. package/src/contracts/execute/types.ts +5 -38
  22. package/src/contracts/gov/helpers.ts +187 -51
  23. package/src/contracts/gov/scripts.ts +17 -10
  24. package/src/contracts/gov/transactions.ts +599 -271
  25. package/src/contracts/gov/types-new.ts +253 -100
  26. package/src/contracts/gov/types.ts +4 -71
  27. package/src/contracts/iasset/helpers.ts +172 -0
  28. package/src/contracts/iasset/scripts.ts +38 -0
  29. package/src/contracts/iasset/types.ts +154 -0
  30. package/src/contracts/initialize/actions.ts +768 -0
  31. package/src/contracts/initialize/helpers.ts +611 -36
  32. package/src/contracts/initialize/types.ts +102 -28
  33. package/src/contracts/interest-collection/helpers.ts +19 -0
  34. package/src/contracts/interest-collection/scripts.ts +44 -0
  35. package/src/contracts/interest-collection/transactions.ts +436 -0
  36. package/src/contracts/interest-collection/types-new.ts +50 -0
  37. package/src/contracts/interest-collection/types.ts +26 -0
  38. package/src/contracts/interest-oracle/helpers.ts +2 -30
  39. package/src/contracts/interest-oracle/scripts.ts +1 -1
  40. package/src/contracts/interest-oracle/transactions.ts +21 -16
  41. package/src/contracts/interest-oracle/types-new.ts +32 -0
  42. package/src/contracts/interest-oracle/types.ts +1 -40
  43. package/src/contracts/one-shot/transactions.ts +1 -2
  44. package/src/contracts/poll/helpers.ts +5 -23
  45. package/src/contracts/poll/scripts.ts +12 -13
  46. package/src/contracts/poll/types-poll-manager.ts +1 -19
  47. package/src/contracts/poll/types-poll-new.ts +170 -0
  48. package/src/contracts/poll/types-poll-shard.ts +2 -24
  49. package/src/contracts/price-oracle/helpers.ts +1 -4
  50. package/src/contracts/price-oracle/scripts.ts +3 -8
  51. package/src/contracts/price-oracle/transactions.ts +32 -25
  52. package/src/contracts/price-oracle/types-new.ts +50 -0
  53. package/src/contracts/price-oracle/types.ts +2 -36
  54. package/src/contracts/pyth-feed/helpers.ts +58 -0
  55. package/src/contracts/pyth-feed/scripts.ts +15 -0
  56. package/src/contracts/pyth-feed/types.ts +181 -0
  57. package/src/contracts/rob/helpers.ts +405 -0
  58. package/src/contracts/rob/scripts.ts +35 -0
  59. package/src/contracts/rob/transactions.ts +410 -0
  60. package/src/contracts/rob/types-new.ts +128 -0
  61. package/src/contracts/rob/types.ts +16 -0
  62. package/src/contracts/rob-leverage/helpers.ts +424 -0
  63. package/src/contracts/{leverage → rob-leverage}/transactions.ts +68 -48
  64. package/src/contracts/stability-pool/helpers.ts +714 -230
  65. package/src/contracts/stability-pool/scripts.ts +20 -15
  66. package/src/contracts/stability-pool/transactions.ts +628 -496
  67. package/src/contracts/stability-pool/types-new.ts +247 -100
  68. package/src/contracts/stability-pool/types.ts +5 -22
  69. package/src/contracts/stableswap/helpers.ts +22 -0
  70. package/src/contracts/stableswap/scripts.ts +37 -0
  71. package/src/contracts/stableswap/transactions.ts +647 -0
  72. package/src/contracts/stableswap/types-new.ts +131 -0
  73. package/src/contracts/stableswap/types.ts +17 -0
  74. package/src/contracts/staking/helpers.ts +49 -34
  75. package/src/contracts/staking/scripts.ts +1 -1
  76. package/src/contracts/staking/transactions.ts +85 -130
  77. package/src/contracts/staking/types-new.ts +60 -28
  78. package/src/contracts/staking/types.ts +1 -28
  79. package/src/contracts/treasury/helpers.ts +21 -0
  80. package/src/contracts/treasury/scripts.ts +16 -26
  81. package/src/contracts/treasury/transactions.ts +256 -27
  82. package/src/contracts/treasury/types-new.ts +69 -0
  83. package/src/contracts/treasury/types.ts +2 -43
  84. package/src/contracts/version-registry/scripts.ts +2 -2
  85. package/src/contracts/version-registry/types-new.ts +6 -7
  86. package/src/index.ts +37 -20
  87. package/src/scripts/auth-token-policy.ts +3 -2
  88. package/src/scripts/iasset-policy.ts +3 -2
  89. package/src/types/evolution-schema-options.ts +3 -3
  90. package/src/types/generic.ts +17 -89
  91. package/src/types/multisig.ts +48 -0
  92. package/src/types/on-chain-decimal.ts +14 -7
  93. package/src/types/rational.ts +61 -0
  94. package/src/types/system-params.ts +237 -41
  95. package/src/utils/array-utils.ts +70 -1
  96. package/src/utils/bigint-utils.ts +12 -0
  97. package/src/utils/indigo-helpers.ts +8 -10
  98. package/src/utils/lucid-utils.ts +47 -40
  99. package/src/utils/oracle-helpers.ts +62 -0
  100. package/src/utils/pyth/decode.ts +223 -0
  101. package/src/utils/pyth/encode.ts +262 -0
  102. package/src/utils/pyth/index.ts +14 -0
  103. package/src/utils/pyth/types.ts +87 -0
  104. package/src/validators/always-succeed-validator.ts +6 -0
  105. package/src/validators/cdp-creator-validator.ts +2 -2
  106. package/src/validators/cdp-redeem-validator.ts +7 -0
  107. package/src/validators/cdp-validator.ts +2 -2
  108. package/src/validators/collector-validator.ts +2 -2
  109. package/src/validators/execute-validator.ts +2 -2
  110. package/src/validators/governance-validator.ts +2 -2
  111. package/src/validators/iasset-validator.ts +7 -0
  112. package/src/validators/interest-collection-validator.ts +7 -0
  113. package/src/validators/interest-oracle-validator.ts +2 -2
  114. package/src/validators/poll-manager-validator.ts +2 -2
  115. package/src/validators/poll-shard-validator.ts +2 -2
  116. package/src/validators/price-oracle-validator.ts +7 -0
  117. package/src/validators/pyth-feed-validator.ts +7 -0
  118. package/src/validators/rob-validator.ts +7 -0
  119. package/src/validators/stability-pool-validator.ts +2 -2
  120. package/src/validators/stableswap-validator.ts +7 -0
  121. package/src/validators/staking-validator.ts +2 -2
  122. package/src/validators/treasury-validator.ts +2 -2
  123. package/src/validators/version-record-policy.ts +2 -2
  124. package/src/validators/version-registry-validator.ts +2 -2
  125. package/tests/always-succeed/script.ts +7 -0
  126. package/tests/bigint-utils.test.ts +41 -0
  127. package/tests/cdp/actions.ts +610 -0
  128. package/tests/cdp/cdp-helpers.ts +55 -0
  129. package/tests/cdp/cdp-queries.ts +440 -0
  130. package/tests/cdp/cdp.test.ts +6087 -0
  131. package/tests/cdp/transactions-mutated.ts +1729 -0
  132. package/tests/data/system-params.json +177 -34
  133. package/tests/datums.test.ts +209 -210
  134. package/tests/endpoints/initialize.ts +68 -0
  135. package/tests/endpoints/interest-collector.ts +37 -0
  136. package/tests/endpoints/treasury.ts +70 -0
  137. package/tests/gov/actions.ts +406 -0
  138. package/tests/gov/gov.test.ts +4450 -0
  139. package/tests/{queries → gov}/governance-queries.ts +6 -3
  140. package/tests/hash-checks.test.ts +38 -11
  141. package/tests/indigo-test-helpers.ts +100 -0
  142. package/tests/initialize.test.ts +61 -9
  143. package/tests/interest-collection/interest-collection.test.ts +892 -0
  144. package/tests/interest-collection/interest-collector-queries.ts +49 -0
  145. package/tests/interest-collection/transactions-mutated.ts +260 -0
  146. package/tests/interest-oracle.test.ts +43 -35
  147. package/tests/mock/assets-mock.ts +234 -23
  148. package/tests/mock/protocol-params-mock.ts +21 -0
  149. package/tests/price-oracle/actions.ts +163 -0
  150. package/tests/price-oracle/price-oracle-queries.ts +12 -0
  151. package/tests/price-oracle/price-oracle.test.ts +240 -0
  152. package/tests/price-oracle/transactions-mutated.ts +62 -0
  153. package/tests/pyth/endpoints.ts +96 -0
  154. package/tests/pyth/helpers.ts +37 -0
  155. package/tests/pyth/pyth-encoding.test.ts +376 -0
  156. package/tests/pyth/pyth-indigo.test.ts +509 -0
  157. package/tests/pyth/pyth.test.ts +300 -0
  158. package/tests/queries/execute-queries.ts +6 -5
  159. package/tests/queries/iasset-queries.ts +175 -5
  160. package/tests/queries/interest-oracle-queries.ts +4 -2
  161. package/tests/queries/poll-queries.ts +8 -9
  162. package/tests/queries/stability-pool-queries.ts +95 -48
  163. package/tests/queries/staking-queries.ts +4 -2
  164. package/tests/queries/treasury-queries.ts +80 -5
  165. package/tests/rob/actions.ts +58 -0
  166. package/tests/{lrp-leverage.test.ts → rob/rob-leverage.test.ts} +393 -296
  167. package/tests/rob/rob-queries.ts +95 -0
  168. package/tests/rob/rob.test.ts +3762 -0
  169. package/tests/rob/transactions-mutated.ts +853 -0
  170. package/tests/script-size.test.ts +240 -0
  171. package/tests/setup.ts +135 -0
  172. package/tests/stability-pool/actions.ts +220 -0
  173. package/tests/stability-pool.test.ts +6121 -667
  174. package/tests/stableswap/stableswap-actions.ts +84 -0
  175. package/tests/stableswap/stableswap-queries.ts +89 -0
  176. package/tests/stableswap/stableswap.test.ts +3891 -0
  177. package/tests/stableswap/transactions-mutated.ts +348 -0
  178. package/tests/staking.test.ts +82 -99
  179. package/tests/test-helpers.ts +58 -11
  180. package/tests/treasury.test.ts +242 -0
  181. package/tests/utils/asserts.ts +74 -0
  182. package/tests/utils/benchmark-utils.ts +81 -0
  183. package/tests/utils/index.ts +122 -4
  184. package/tsconfig.json +9 -1
  185. package/vitest.config.ts +3 -1
  186. package/src/contracts/collector/types.ts +0 -16
  187. package/src/contracts/initialize/transactions.ts +0 -891
  188. package/src/contracts/leverage/helpers.ts +0 -424
  189. package/src/contracts/lrp/helpers.ts +0 -294
  190. package/src/contracts/lrp/scripts.ts +0 -27
  191. package/src/contracts/lrp/transactions.ts +0 -250
  192. package/src/contracts/lrp/types.ts +0 -131
  193. package/src/contracts/poll/types-poll.ts +0 -88
  194. package/src/contracts/vesting/helpers.ts +0 -218
  195. package/src/utils/value-helpers.ts +0 -37
  196. package/src/validators/lrp-validator.ts +0 -7
  197. package/tests/cdp.test.ts +0 -1528
  198. package/tests/gov.test.ts +0 -2011
  199. package/tests/lrp.test.ts +0 -673
  200. package/tests/queries/cdp-queries.ts +0 -220
  201. package/tests/queries/lrp-queries.ts +0 -76
  202. 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
+ });