@indigo-labs/indigo-sdk 0.2.41 → 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 -495
  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,62 @@
1
+ import { match, P } from 'ts-pattern';
2
+ import { IAssetPriceInfo } from '../contracts/iasset/types';
3
+ import { Rational } from '../types/rational';
4
+ import { LucidEvolution, OutRef, UTxO } from '@lucid-evolution/lucid';
5
+ import { adjustPriceToDecimals } from '../contracts/cdp/helpers';
6
+ import {
7
+ AssetClass,
8
+ getInlineDatumOrThrow,
9
+ matchSingle,
10
+ } from '@3rd-eye-labs/cardano-offchain-common';
11
+ import { parsePriceOracleDatum } from '../contracts/price-oracle/types-new';
12
+ import { derivePythPrice } from '../contracts/pyth-feed/helpers';
13
+ import {
14
+ fromSysParamsDerivedPythPrice,
15
+ getPythFeedConfig,
16
+ PythConfig,
17
+ } from '../types/system-params';
18
+
19
+ export async function retrieveAdjustedPrice(
20
+ iasset: Uint8Array<ArrayBufferLike>,
21
+ collateralAsset: AssetClass,
22
+ priceInfo: IAssetPriceInfo,
23
+ extraDecimals: bigint,
24
+ priceOracleOref: OutRef | undefined,
25
+ pythMessage: string | undefined,
26
+ pythConfig: PythConfig,
27
+ lucid: LucidEvolution,
28
+ ): Promise<[Rational, UTxO | undefined]> {
29
+ const [oraclePrice, maybePriceOracleUtxo] = await match(priceInfo)
30
+ .with({ OracleNft: P.any }, async () => {
31
+ if (!priceOracleOref) throw new Error('Missing price oracle');
32
+ const priceOracleUtxo = matchSingle(
33
+ await lucid.utxosByOutRef([priceOracleOref]),
34
+ (_) => new Error('Expected a single price oracle UTXO'),
35
+ );
36
+ const priceOracleDatum = parsePriceOracleDatum(
37
+ getInlineDatumOrThrow(priceOracleUtxo),
38
+ );
39
+ return [priceOracleDatum.price, priceOracleUtxo];
40
+ })
41
+ .with({ Delisted: P.select() }, (price) => [price.price, undefined])
42
+ .with({ DeferredValidation: P.select() }, () => {
43
+ if (!pythMessage) throw new Error('Missing Pyth message');
44
+
45
+ return [
46
+ derivePythPrice(
47
+ fromSysParamsDerivedPythPrice(
48
+ getPythFeedConfig(pythConfig, iasset, collateralAsset).params
49
+ .config,
50
+ ),
51
+ pythMessage,
52
+ ),
53
+ undefined,
54
+ ];
55
+ })
56
+ .exhaustive();
57
+
58
+ return [
59
+ adjustPriceToDecimals(extraDecimals, oraclePrice as Rational),
60
+ maybePriceOracleUtxo as UTxO | undefined,
61
+ ];
62
+ }
@@ -0,0 +1,223 @@
1
+ import type { PriceUpdate, PythMessageParts } from './types.js';
2
+ import type { ParsedFeedPayload } from '@pythnetwork/pyth-lazer-sdk';
3
+ import {
4
+ MARKET_SESSION_ORDER,
5
+ PRICE_UPDATE_MAGIC,
6
+ PROP_ID,
7
+ SOLANA_FORMAT_MAGIC,
8
+ } from './types.js';
9
+
10
+ function expectMagic(
11
+ buf: Uint8Array,
12
+ offset: number,
13
+ magic: Uint8Array,
14
+ label: string,
15
+ ): void {
16
+ for (let i = 0; i < magic.length; i++) {
17
+ if (buf[offset + i] !== magic[i]) {
18
+ throw new Error(`Invalid ${label} magic byte at ${i}`);
19
+ }
20
+ }
21
+ }
22
+
23
+ function bigintToSdkNumber(v: bigint): number {
24
+ if (
25
+ v > BigInt(Number.MAX_SAFE_INTEGER) ||
26
+ v < BigInt(Number.MIN_SAFE_INTEGER)
27
+ ) {
28
+ throw new RangeError('Scalar does not fit in a safe integer');
29
+ }
30
+ return Number(v);
31
+ }
32
+
33
+ function decodeFeedPayload(
34
+ buf: Uint8Array,
35
+ view: DataView,
36
+ start: number,
37
+ ): { feed: ParsedFeedPayload; next: number } {
38
+ let o = start;
39
+ if (o + 5 > buf.length) throw new RangeError('Truncated feed header');
40
+ const priceFeedId = view.getUint32(o, true);
41
+ o += 4;
42
+ const nProps = buf[o];
43
+ o += 1;
44
+ const feed: ParsedFeedPayload = { priceFeedId };
45
+ for (let i = 0; i < nProps; i++) {
46
+ if (o >= buf.length) throw new RangeError('Truncated feed property header');
47
+ const propId = buf[o];
48
+ o += 1;
49
+ switch (propId) {
50
+ case PROP_ID.Price: {
51
+ const v = view.getBigInt64(o, true);
52
+ o += 8;
53
+ if (v !== 0n) feed.price = v.toString();
54
+ break;
55
+ }
56
+ case PROP_ID.BestBidPrice: {
57
+ const v = view.getBigInt64(o, true);
58
+ o += 8;
59
+ if (v !== 0n) feed.bestBidPrice = v.toString();
60
+ break;
61
+ }
62
+ case PROP_ID.BestAskPrice: {
63
+ const v = view.getBigInt64(o, true);
64
+ o += 8;
65
+ if (v !== 0n) feed.bestAskPrice = v.toString();
66
+ break;
67
+ }
68
+ case PROP_ID.PublisherCount: {
69
+ feed.publisherCount = view.getUint16(o, true);
70
+ o += 2;
71
+ break;
72
+ }
73
+ case PROP_ID.Exponent: {
74
+ feed.exponent = view.getInt16(o, true);
75
+ o += 2;
76
+ break;
77
+ }
78
+ case PROP_ID.Confidence: {
79
+ const v = view.getBigInt64(o, true);
80
+ o += 8;
81
+ if (v !== 0n) feed.confidence = bigintToSdkNumber(v);
82
+ break;
83
+ }
84
+ case PROP_ID.FundingRate: {
85
+ const tag = buf[o];
86
+ o += 1;
87
+ if (tag === 1) {
88
+ const v = view.getBigInt64(o, true);
89
+ o += 8;
90
+ feed.fundingRate = bigintToSdkNumber(v);
91
+ } else if (tag !== 0) {
92
+ throw new RangeError('Invalid FundingRate option tag');
93
+ }
94
+ break;
95
+ }
96
+ case PROP_ID.FundingTimestamp: {
97
+ const tag = buf[o];
98
+ o += 1;
99
+ if (tag === 1) {
100
+ const v = view.getBigUint64(o, true);
101
+ o += 8;
102
+ feed.fundingTimestamp = bigintToSdkNumber(v);
103
+ } else if (tag !== 0) {
104
+ throw new RangeError('Invalid FundingTimestamp option tag');
105
+ }
106
+ break;
107
+ }
108
+ case PROP_ID.FundingRateInterval: {
109
+ const tag = buf[o];
110
+ o += 1;
111
+ if (tag === 1) {
112
+ const v = view.getBigUint64(o, true);
113
+ o += 8;
114
+ feed.fundingRateInterval = bigintToSdkNumber(v);
115
+ } else if (tag !== 0) {
116
+ throw new RangeError('Invalid FundingRateInterval option tag');
117
+ }
118
+ break;
119
+ }
120
+ case PROP_ID.MarketSession: {
121
+ const idx = view.getUint16(o, true);
122
+ o += 2;
123
+ const session = MARKET_SESSION_ORDER[idx];
124
+ if (!session) throw new RangeError('Invalid marketSession index');
125
+ feed.marketSession = session;
126
+ break;
127
+ }
128
+ case PROP_ID.EmaPrice: {
129
+ const v = view.getBigInt64(o, true);
130
+ o += 8;
131
+ if (v !== 0n) feed.emaPrice = v.toString();
132
+ break;
133
+ }
134
+ case PROP_ID.EmaConfidence: {
135
+ const v = view.getBigInt64(o, true);
136
+ o += 8;
137
+ if (v !== 0n) feed.emaConfidence = bigintToSdkNumber(v);
138
+ break;
139
+ }
140
+ case PROP_ID.FeedUpdateTimestamp: {
141
+ const tag = buf[o];
142
+ o += 1;
143
+ if (tag === 1) {
144
+ const v = view.getBigUint64(o, true);
145
+ o += 8;
146
+ feed.feedUpdateTimestamp = bigintToSdkNumber(v);
147
+ } else if (tag !== 0) {
148
+ throw new RangeError('Invalid FeedUpdateTimestamp option tag');
149
+ }
150
+ break;
151
+ }
152
+ default:
153
+ throw new RangeError(`Unknown feed property id: ${propId}`);
154
+ }
155
+ }
156
+ return { feed, next: o };
157
+ }
158
+
159
+ /**
160
+ * Decode a PriceUpdate payload produced by {@link encodePriceUpdate}.
161
+ */
162
+ export function decodePriceUpdate(payload: Uint8Array): PriceUpdate {
163
+ const min = 4 + 8 + 1 + 1;
164
+ if (payload.length < min) {
165
+ throw new RangeError('Price update payload too short');
166
+ }
167
+ expectMagic(payload, 0, PRICE_UPDATE_MAGIC, 'price update');
168
+ const view = new DataView(
169
+ payload.buffer,
170
+ payload.byteOffset,
171
+ payload.byteLength,
172
+ );
173
+ let o = 4;
174
+ const timestampUs = view.getBigUint64(o, true);
175
+ o += 8;
176
+ const channelId = payload[o];
177
+ o += 1;
178
+ const feedsLen = payload[o];
179
+ o += 1;
180
+ const priceFeeds: ParsedFeedPayload[] = [];
181
+ for (let f = 0; f < feedsLen; f++) {
182
+ const decoded = decodeFeedPayload(payload, view, o);
183
+ priceFeeds.push(decoded.feed);
184
+ o = decoded.next;
185
+ }
186
+ if (o !== payload.length) {
187
+ throw new RangeError('Trailing bytes in price update payload');
188
+ }
189
+ return {
190
+ timestampUs: timestampUs.toString(),
191
+ channelId,
192
+ priceFeeds,
193
+ };
194
+ }
195
+
196
+ const PYTH_MESSAGE_HEADER_LEN = 4 + 64 + 32 + 2;
197
+
198
+ /**
199
+ * Decode a Solana-format Pyth message produced by {@link encodePythMessage}.
200
+ */
201
+ export function decodePythMessage(message: Uint8Array): PythMessageParts {
202
+ if (message.length < PYTH_MESSAGE_HEADER_LEN) {
203
+ throw new RangeError('Pyth message too short');
204
+ }
205
+ expectMagic(message, 0, SOLANA_FORMAT_MAGIC, 'Solana format');
206
+ const view = new DataView(
207
+ message.buffer,
208
+ message.byteOffset,
209
+ message.byteLength,
210
+ );
211
+ const payloadLen = view.getUint16(100, true);
212
+ const total = PYTH_MESSAGE_HEADER_LEN + payloadLen;
213
+ if (message.length !== total) {
214
+ throw new RangeError(
215
+ `Pyth message length mismatch: expected ${total} bytes, got ${message.length}`,
216
+ );
217
+ }
218
+ return {
219
+ signature: message.subarray(4, 68).slice(),
220
+ publicKey: message.subarray(68, 100).slice(),
221
+ payload: message.subarray(102, total).slice(),
222
+ };
223
+ }
@@ -0,0 +1,262 @@
1
+ import type {
2
+ Feed,
3
+ MarketSession,
4
+ PriceUpdate,
5
+ PythMessageParts,
6
+ } from './types.js';
7
+ import type { ParsedFeedPayload } from '@pythnetwork/pyth-lazer-sdk';
8
+ import {
9
+ MARKET_SESSION_ORDER,
10
+ PRICE_UPDATE_MAGIC,
11
+ PROP_ID,
12
+ SOLANA_FORMAT_MAGIC,
13
+ } from './types.js';
14
+
15
+ export { PRICE_UPDATE_MAGIC, SOLANA_FORMAT_MAGIC } from './types.js';
16
+
17
+ function u8(b: number): Uint8Array {
18
+ if (b < 0 || b > 255) throw new RangeError('u8 out of range');
19
+ return new Uint8Array([b & 0xff]);
20
+ }
21
+
22
+ function u16Le(n: number): Uint8Array {
23
+ if (n < 0 || n > 0xffff) throw new RangeError('u16 out of range');
24
+ const buf = new Uint8Array(2);
25
+ new DataView(buf.buffer).setUint16(0, n, true);
26
+ return buf;
27
+ }
28
+
29
+ function u32Le(n: number): Uint8Array {
30
+ if (n < 0 || n > 0xffff_ffff) throw new RangeError('u32 out of range');
31
+ const buf = new Uint8Array(4);
32
+ new DataView(buf.buffer).setUint32(0, n, true);
33
+ return buf;
34
+ }
35
+
36
+ function u64Le(n: bigint): Uint8Array {
37
+ const buf = new Uint8Array(8);
38
+ const view = new DataView(buf.buffer);
39
+ const lo = Number(n & 0xffff_ffff_ffff_ffffn);
40
+ const hi = Number((n >> 32n) & 0xffff_ffffn);
41
+ view.setUint32(0, lo & 0xffff_ffff, true);
42
+ view.setUint32(4, hi, true);
43
+ return buf;
44
+ }
45
+
46
+ function i16Le(n: number): Uint8Array {
47
+ const buf = new Uint8Array(2);
48
+ new DataView(buf.buffer).setInt16(0, n, true);
49
+ return buf;
50
+ }
51
+
52
+ function i64Le(n: bigint): Uint8Array {
53
+ const buf = new Uint8Array(8);
54
+ const view = new DataView(buf.buffer);
55
+ const lo = Number(n & 0xffff_ffff_ffff_ffffn);
56
+ const hi = Number(Number(n >> 32n) & 0xffff_ffff);
57
+ view.setUint32(0, lo & 0xffff_ffff, true);
58
+ view.setUint32(4, hi, true);
59
+ return buf;
60
+ }
61
+
62
+ function concat(...chunks: Uint8Array[]): Uint8Array {
63
+ const total = chunks.reduce((s, c) => s + c.length, 0);
64
+ const out = new Uint8Array(total);
65
+ let off = 0;
66
+ for (const c of chunks) {
67
+ out.set(c, off);
68
+ off += c.length;
69
+ }
70
+ return out;
71
+ }
72
+
73
+ /** Encode a single feed property (property_id byte + value bytes). */
74
+ function encodeFeedProperty(id: number, value: Uint8Array): Uint8Array {
75
+ return concat(u8(id), value);
76
+ }
77
+
78
+ function toFeed(parsed: ParsedFeedPayload): Feed {
79
+ const toBigInt = (v: string | number | undefined): bigint | null =>
80
+ v === undefined ? null : BigInt(v);
81
+
82
+ const toBigIntOptional = (v: string | number | undefined): bigint | null =>
83
+ v === undefined ? null : BigInt(v);
84
+
85
+ return {
86
+ feedId: parsed.priceFeedId,
87
+ price: parsed.price === undefined ? null : BigInt(parsed.price),
88
+ bestBidPrice: toBigIntOptional(parsed.bestBidPrice),
89
+ bestAskPrice: toBigIntOptional(parsed.bestAskPrice),
90
+ publisherCount: parsed.publisherCount ?? 0,
91
+ exponent: parsed.exponent ?? 0,
92
+ confidence: toBigInt(parsed.confidence),
93
+ fundingRate: toBigInt(parsed.fundingRate),
94
+ fundingTimestamp: toBigInt(parsed.fundingTimestamp),
95
+ fundingRateInterval: toBigInt(parsed.fundingRateInterval),
96
+ marketSession: (parsed.marketSession as MarketSession) ?? 'Regular',
97
+ emaPrice: toBigIntOptional(parsed.emaPrice),
98
+ emaConfidence: toBigInt(parsed.emaConfidence),
99
+ feedUpdateTimestamp: toBigInt(parsed.feedUpdateTimestamp),
100
+ };
101
+ }
102
+
103
+ /** Encode feed properties that are defined, in the order expected by the parser. */
104
+ function encodeFeed(feed: Feed): Uint8Array {
105
+ const parts: Uint8Array[] = [];
106
+
107
+ if (feed.price !== undefined) {
108
+ const v = feed.price === null ? 0n : feed.price;
109
+ parts.push(encodeFeedProperty(PROP_ID.Price, i64Le(v)));
110
+ }
111
+ if (feed.bestBidPrice !== undefined) {
112
+ const v = feed.bestBidPrice === null ? 0n : feed.bestBidPrice;
113
+ parts.push(encodeFeedProperty(PROP_ID.BestBidPrice, i64Le(v)));
114
+ }
115
+ if (feed.bestAskPrice !== undefined) {
116
+ const v = feed.bestAskPrice === null ? 0n : feed.bestAskPrice;
117
+ parts.push(encodeFeedProperty(PROP_ID.BestAskPrice, i64Le(v)));
118
+ }
119
+ if (feed.publisherCount !== undefined) {
120
+ parts.push(
121
+ encodeFeedProperty(PROP_ID.PublisherCount, u16Le(feed.publisherCount)),
122
+ );
123
+ }
124
+ if (feed.exponent !== undefined) {
125
+ parts.push(encodeFeedProperty(PROP_ID.Exponent, i16Le(feed.exponent)));
126
+ }
127
+ if (feed.confidence !== undefined) {
128
+ const v = feed.confidence === null ? 0n : feed.confidence;
129
+ parts.push(encodeFeedProperty(PROP_ID.Confidence, i64Le(v)));
130
+ }
131
+ if (feed.fundingRate !== undefined) {
132
+ if (feed.fundingRate === null) {
133
+ parts.push(encodeFeedProperty(PROP_ID.FundingRate, u8(0)));
134
+ } else {
135
+ parts.push(
136
+ encodeFeedProperty(
137
+ PROP_ID.FundingRate,
138
+ concat(u8(1), i64Le(feed.fundingRate)),
139
+ ),
140
+ );
141
+ }
142
+ }
143
+ if (feed.fundingTimestamp !== undefined) {
144
+ if (feed.fundingTimestamp === null) {
145
+ parts.push(encodeFeedProperty(PROP_ID.FundingTimestamp, u8(0)));
146
+ } else {
147
+ parts.push(
148
+ encodeFeedProperty(
149
+ PROP_ID.FundingTimestamp,
150
+ concat(u8(1), u64Le(feed.fundingTimestamp)),
151
+ ),
152
+ );
153
+ }
154
+ }
155
+ if (feed.fundingRateInterval !== undefined) {
156
+ if (feed.fundingRateInterval === null) {
157
+ parts.push(encodeFeedProperty(PROP_ID.FundingRateInterval, u8(0)));
158
+ } else {
159
+ parts.push(
160
+ encodeFeedProperty(
161
+ PROP_ID.FundingRateInterval,
162
+ concat(u8(1), u64Le(feed.fundingRateInterval)),
163
+ ),
164
+ );
165
+ }
166
+ }
167
+ if (feed.marketSession !== undefined) {
168
+ const idx = MARKET_SESSION_ORDER.indexOf(feed.marketSession);
169
+ if (idx < 0) throw new RangeError('Invalid marketSession');
170
+ parts.push(encodeFeedProperty(PROP_ID.MarketSession, u16Le(idx)));
171
+ }
172
+ if (feed.emaPrice !== undefined) {
173
+ const v = feed.emaPrice === null ? 0n : feed.emaPrice;
174
+ parts.push(encodeFeedProperty(PROP_ID.EmaPrice, i64Le(v)));
175
+ }
176
+ if (feed.emaConfidence !== undefined) {
177
+ const v = feed.emaConfidence === null ? 0n : feed.emaConfidence;
178
+ parts.push(encodeFeedProperty(PROP_ID.EmaConfidence, i64Le(v)));
179
+ }
180
+ if (feed.feedUpdateTimestamp !== undefined) {
181
+ if (feed.feedUpdateTimestamp === null) {
182
+ parts.push(encodeFeedProperty(PROP_ID.FeedUpdateTimestamp, u8(0)));
183
+ } else {
184
+ parts.push(
185
+ encodeFeedProperty(
186
+ PROP_ID.FeedUpdateTimestamp,
187
+ concat(u8(1), u64Le(feed.feedUpdateTimestamp)),
188
+ ),
189
+ );
190
+ }
191
+ }
192
+
193
+ const properties = concat(...parts);
194
+ if (parts.length > 255)
195
+ throw new RangeError('Feed has more than 255 properties');
196
+ return concat(
197
+ u32Le(feed.feedId),
198
+ u8(parts.length), // number of properties (parser.repeat count), not byte length
199
+ properties,
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Encode a PriceUpdate to the binary payload format consumed by the on-chain parser.
205
+ * This is the payload that goes inside a PythMessage.
206
+ */
207
+ export function encodePriceUpdate(update: PriceUpdate): Uint8Array {
208
+ if (update.priceFeeds.length > 255) {
209
+ throw new RangeError('At most 255 feeds allowed');
210
+ }
211
+ const feeds: Feed[] = update.priceFeeds.map(toFeed);
212
+ const feedBytes = feeds.map(encodeFeed);
213
+ return concat(
214
+ PRICE_UPDATE_MAGIC,
215
+ u64Le(BigInt(update.timestampUs)),
216
+ u8(update.channelId),
217
+ u8(feeds.length),
218
+ ...feedBytes,
219
+ );
220
+ }
221
+
222
+ /**
223
+ * Encode a full Pyth message (Solana format): magic + signature + key + payload length + payload.
224
+ * Use this when you already have a signed payload (e.g. from a Pyth API or after signing locally).
225
+ */
226
+ export function encodePythMessage(parts: PythMessageParts): Uint8Array {
227
+ if (parts.signature.length !== 64) {
228
+ throw new RangeError('signature must be 64 bytes');
229
+ }
230
+ if (parts.publicKey.length !== 32) {
231
+ throw new RangeError('publicKey must be 32 bytes');
232
+ }
233
+ if (parts.payload.length > 0xffff) {
234
+ throw new RangeError('payload length exceeds u16 max');
235
+ }
236
+ return concat(
237
+ SOLANA_FORMAT_MAGIC,
238
+ parts.signature,
239
+ parts.publicKey,
240
+ u16Le(parts.payload.length),
241
+ parts.payload,
242
+ );
243
+ }
244
+
245
+ /**
246
+ * Encode a PythMessage whose payload is a PriceUpdate.
247
+ * Signs the payload with the given secret key (32-byte Ed25519 seed).
248
+ * Returns the full message bytes (hex-friendly).
249
+ */
250
+ export async function encodeSignedPythMessage(
251
+ update: PriceUpdate,
252
+ secretKey: Uint8Array,
253
+ ): Promise<Uint8Array> {
254
+ if (secretKey.length !== 32) {
255
+ throw new RangeError('secretKey must be 32 bytes (Ed25519 seed)');
256
+ }
257
+ const noble = await import('@noble/ed25519');
258
+ const payload = encodePriceUpdate(update);
259
+ const publicKey = await noble.getPublicKeyAsync(secretKey);
260
+ const signature = await noble.signAsync(payload, secretKey);
261
+ return encodePythMessage({ signature, publicKey, payload });
262
+ }
@@ -0,0 +1,14 @@
1
+ export type {
2
+ Feed,
3
+ MarketSession,
4
+ PriceUpdate,
5
+ PythMessageParts,
6
+ } from './types.js';
7
+ export { decodePriceUpdate, decodePythMessage } from './decode.js';
8
+ export {
9
+ encodePriceUpdate,
10
+ encodePythMessage,
11
+ encodeSignedPythMessage,
12
+ PRICE_UPDATE_MAGIC,
13
+ SOLANA_FORMAT_MAGIC,
14
+ } from './encode.js';
@@ -0,0 +1,87 @@
1
+ import type { ParsedPayload } from '@pythnetwork/pyth-lazer-sdk';
2
+
3
+ /**
4
+ * Types mirroring the Aiken Pyth parser (lib/pyth).
5
+ * Use bigint for values that may exceed Number safe integer (2^53 - 1).
6
+ */
7
+
8
+ export type MarketSession =
9
+ | 'Regular'
10
+ | 'PreMarket'
11
+ | 'PostMarket'
12
+ | 'OverNight'
13
+ | 'Closed';
14
+
15
+ /** Price-update payload magic (4 bytes). Must match Aiken price_update_magic_le. */
16
+ export const PRICE_UPDATE_MAGIC = new Uint8Array([0x75, 0xd3, 0xc7, 0x93]);
17
+ /** Pyth message (Solana format) magic (4 bytes). Must match Aiken solana_format_magic_le. */
18
+ export const SOLANA_FORMAT_MAGIC = new Uint8Array([0xb9, 0x01, 0x1a, 0x82]);
19
+
20
+ // Property IDs (must match lib/pyth/pyth.ak feed_property)
21
+ export const PROP_ID = {
22
+ Price: 0,
23
+ BestBidPrice: 1,
24
+ BestAskPrice: 2,
25
+ PublisherCount: 3,
26
+ Exponent: 4,
27
+ Confidence: 5,
28
+ FundingRate: 6,
29
+ FundingTimestamp: 7,
30
+ FundingRateInterval: 8,
31
+ MarketSession: 9,
32
+ EmaPrice: 10,
33
+ EmaConfidence: 11,
34
+ FeedUpdateTimestamp: 12,
35
+ } as const;
36
+
37
+ export const MARKET_SESSION_ORDER: MarketSession[] = [
38
+ 'Regular',
39
+ 'PreMarket',
40
+ 'PostMarket',
41
+ 'OverNight',
42
+ 'Closed',
43
+ ];
44
+
45
+ /**
46
+ * Optional<T> = undefined (property not present) | null (explicit None) | T (value)
47
+ * Matches Aiken Option<Option<T>> for feed properties.
48
+ */
49
+ export interface Feed {
50
+ feedId: number; // u32
51
+ price?: bigint | null; // 0 encodes as None
52
+ bestBidPrice?: bigint | null;
53
+ bestAskPrice?: bigint | null;
54
+ publisherCount?: number; // u16
55
+ exponent?: number; // i16
56
+ confidence?: bigint | null;
57
+ fundingRate?: bigint | null;
58
+ fundingTimestamp?: bigint | null; // u64
59
+ fundingRateInterval?: bigint | null; // u64
60
+ marketSession?: MarketSession;
61
+ emaPrice?: bigint | null;
62
+ emaConfidence?: bigint | null;
63
+ feedUpdateTimestamp?: bigint | null; // u64
64
+ }
65
+
66
+ /**
67
+ * Price update payload based on the ParsedPayload returned by pyth-lazer-sdk,
68
+ * extended with the channel id used by the on-chain parser.
69
+ */
70
+ export interface PriceUpdate extends ParsedPayload {
71
+ /** Channel id (u8) */
72
+ channelId: number;
73
+ }
74
+
75
+ /**
76
+ * Components of a Pyth message (Solana format).
77
+ * To produce a valid on-chain message, payload should be a PriceUpdate payload
78
+ * and signature must be Ed25519 over payload with the given public key.
79
+ */
80
+ export interface PythMessageParts {
81
+ /** Ed25519 signature (64 bytes) */
82
+ signature: Uint8Array;
83
+ /** Ed25519 public key (32 bytes) */
84
+ publicKey: Uint8Array;
85
+ /** Payload bytes (e.g. encoded PriceUpdate) */
86
+ payload: Uint8Array;
87
+ }
@@ -0,0 +1,6 @@
1
+ // Automatically generated by the sdk-export.sh script in aiken smart contracts repository
2
+ export const _alwaysSucceedValidator = {
3
+ type: 'PlutusScriptV3',
4
+ description: 'Generated by Aiken',
5
+ cborHex: '525101010023259800a518a4d136564004ae69',
6
+ };