@symmetry-hq/sdk 1.0.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 (121) hide show
  1. package/dist/src/constants.d.ts +23 -0
  2. package/dist/src/constants.js +38 -0
  3. package/dist/src/index.d.ts +804 -0
  4. package/dist/src/index.js +2097 -0
  5. package/dist/src/instructions/automation/auction.d.ts +6 -0
  6. package/dist/src/instructions/automation/auction.js +40 -0
  7. package/dist/src/instructions/automation/claimBounty.d.ts +12 -0
  8. package/dist/src/instructions/automation/claimBounty.js +44 -0
  9. package/dist/src/instructions/automation/flashSwap.d.ts +21 -0
  10. package/dist/src/instructions/automation/flashSwap.js +74 -0
  11. package/dist/src/instructions/automation/priceUpdate.d.ts +19 -0
  12. package/dist/src/instructions/automation/priceUpdate.js +89 -0
  13. package/dist/src/instructions/automation/rebalanceIntent.d.ts +32 -0
  14. package/dist/src/instructions/automation/rebalanceIntent.js +117 -0
  15. package/dist/src/instructions/automation/rebalanceSwap.d.ts +11 -0
  16. package/dist/src/instructions/automation/rebalanceSwap.js +42 -0
  17. package/dist/src/instructions/management/addBounty.d.ts +7 -0
  18. package/dist/src/instructions/management/addBounty.js +41 -0
  19. package/dist/src/instructions/management/admin.d.ts +9 -0
  20. package/dist/src/instructions/management/admin.js +53 -0
  21. package/dist/src/instructions/management/claimFees.d.ts +15 -0
  22. package/dist/src/instructions/management/claimFees.js +95 -0
  23. package/dist/src/instructions/management/createBasket.d.ts +21 -0
  24. package/dist/src/instructions/management/createBasket.js +98 -0
  25. package/dist/src/instructions/management/edit.d.ts +51 -0
  26. package/dist/src/instructions/management/edit.js +477 -0
  27. package/dist/src/instructions/management/luts.d.ts +30 -0
  28. package/dist/src/instructions/management/luts.js +99 -0
  29. package/dist/src/instructions/pda.d.ts +25 -0
  30. package/dist/src/instructions/pda.js +128 -0
  31. package/dist/src/instructions/user/deposit.d.ts +20 -0
  32. package/dist/src/instructions/user/deposit.js +100 -0
  33. package/dist/src/instructions/user/withdraw.d.ts +8 -0
  34. package/dist/src/instructions/user/withdraw.js +36 -0
  35. package/dist/src/jup.d.ts +49 -0
  36. package/dist/src/jup.js +80 -0
  37. package/dist/src/keeperMonitor.d.ts +52 -0
  38. package/dist/src/keeperMonitor.js +624 -0
  39. package/dist/src/layouts/basket.d.ts +191 -0
  40. package/dist/src/layouts/basket.js +51 -0
  41. package/dist/src/layouts/config.d.ts +281 -0
  42. package/dist/src/layouts/config.js +237 -0
  43. package/dist/src/layouts/fraction.d.ts +20 -0
  44. package/dist/src/layouts/fraction.js +164 -0
  45. package/dist/src/layouts/intents/bounty.d.ts +18 -0
  46. package/dist/src/layouts/intents/bounty.js +19 -0
  47. package/dist/src/layouts/intents/intent.d.ts +209 -0
  48. package/dist/src/layouts/intents/intent.js +97 -0
  49. package/dist/src/layouts/intents/rebalanceIntent.d.ts +212 -0
  50. package/dist/src/layouts/intents/rebalanceIntent.js +94 -0
  51. package/dist/src/layouts/lookupTable.d.ts +7 -0
  52. package/dist/src/layouts/lookupTable.js +10 -0
  53. package/dist/src/layouts/oracle.d.ts +63 -0
  54. package/dist/src/layouts/oracle.js +96 -0
  55. package/dist/src/states/basket.d.ts +14 -0
  56. package/dist/src/states/basket.js +479 -0
  57. package/dist/src/states/config.d.ts +3 -0
  58. package/dist/src/states/config.js +71 -0
  59. package/dist/src/states/intents/intent.d.ts +10 -0
  60. package/dist/src/states/intents/intent.js +316 -0
  61. package/dist/src/states/intents/rebalanceIntent.d.ts +42 -0
  62. package/dist/src/states/intents/rebalanceIntent.js +680 -0
  63. package/dist/src/states/oracles/constants.d.ts +9 -0
  64. package/dist/src/states/oracles/constants.js +15 -0
  65. package/dist/src/states/oracles/oracle.d.ts +24 -0
  66. package/dist/src/states/oracles/oracle.js +168 -0
  67. package/dist/src/states/oracles/pythOracle.d.ts +132 -0
  68. package/dist/src/states/oracles/pythOracle.js +609 -0
  69. package/dist/src/states/oracles/raydiumClmmOracle.d.ts +184 -0
  70. package/dist/src/states/oracles/raydiumClmmOracle.js +843 -0
  71. package/dist/src/states/oracles/raydiumCpmmOracle.d.ts +120 -0
  72. package/dist/src/states/oracles/raydiumCpmmOracle.js +540 -0
  73. package/dist/src/states/oracles/switchboardOracle.d.ts +0 -0
  74. package/dist/src/states/oracles/switchboardOracle.js +1 -0
  75. package/dist/src/states/withdrawBasketFees.d.ts +10 -0
  76. package/dist/src/states/withdrawBasketFees.js +154 -0
  77. package/dist/src/txUtils.d.ts +65 -0
  78. package/dist/src/txUtils.js +306 -0
  79. package/dist/test.d.ts +1 -0
  80. package/dist/test.js +561 -0
  81. package/package.json +31 -0
  82. package/src/constants.ts +40 -0
  83. package/src/index.ts +2431 -0
  84. package/src/instructions/automation/auction.ts +55 -0
  85. package/src/instructions/automation/claimBounty.ts +69 -0
  86. package/src/instructions/automation/flashSwap.ts +104 -0
  87. package/src/instructions/automation/priceUpdate.ts +117 -0
  88. package/src/instructions/automation/rebalanceIntent.ts +181 -0
  89. package/src/instructions/management/addBounty.ts +55 -0
  90. package/src/instructions/management/admin.ts +72 -0
  91. package/src/instructions/management/claimFees.ts +129 -0
  92. package/src/instructions/management/createBasket.ts +138 -0
  93. package/src/instructions/management/edit.ts +602 -0
  94. package/src/instructions/management/luts.ts +157 -0
  95. package/src/instructions/pda.ts +151 -0
  96. package/src/instructions/user/deposit.ts +143 -0
  97. package/src/instructions/user/withdraw.ts +53 -0
  98. package/src/jup.ts +113 -0
  99. package/src/keeperMonitor.ts +585 -0
  100. package/src/layouts/basket.ts +233 -0
  101. package/src/layouts/config.ts +576 -0
  102. package/src/layouts/fraction.ts +164 -0
  103. package/src/layouts/intents/bounty.ts +35 -0
  104. package/src/layouts/intents/intent.ts +324 -0
  105. package/src/layouts/intents/rebalanceIntent.ts +306 -0
  106. package/src/layouts/lookupTable.ts +14 -0
  107. package/src/layouts/oracle.ts +157 -0
  108. package/src/states/basket.ts +527 -0
  109. package/src/states/config.ts +62 -0
  110. package/src/states/intents/intent.ts +311 -0
  111. package/src/states/intents/rebalanceIntent.ts +751 -0
  112. package/src/states/oracles/constants.ts +13 -0
  113. package/src/states/oracles/oracle.ts +212 -0
  114. package/src/states/oracles/pythOracle.ts +874 -0
  115. package/src/states/oracles/raydiumClmmOracle.ts +1193 -0
  116. package/src/states/oracles/raydiumCpmmOracle.ts +784 -0
  117. package/src/states/oracles/switchboardOracle.ts +0 -0
  118. package/src/states/withdrawBasketFees.ts +160 -0
  119. package/src/txUtils.ts +424 -0
  120. package/test.ts +609 -0
  121. package/tsconfig.json +101 -0
@@ -0,0 +1,609 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.PythOracle = exports.PythState = exports.PriceFeedMessage = exports.TIP_ACCOUNTS = void 0;
16
+ exports.parseVerificationLevel = parseVerificationLevel;
17
+ exports.getRandomTipAccount = getRandomTipAccount;
18
+ exports.buildJitoTipInstruction = buildJitoTipInstruction;
19
+ exports.getPythPriceFeedAccountAddress = getPythPriceFeedAccountAddress;
20
+ exports.parsePriceFeedMessage = parsePriceFeedMessage;
21
+ exports.parseAccumulatorUpdateData = parseAccumulatorUpdateData;
22
+ exports.getSizeOfTransaction = getSizeOfTransaction;
23
+ exports.getSizeOfCompressedU16 = getSizeOfCompressedU16;
24
+ exports.fetchFeedIdsFromAccounts = fetchFeedIdsFromAccounts;
25
+ exports.buildPythPriceFeedUpdateIxs = buildPythPriceFeedUpdateIxs;
26
+ const decimal_js_1 = __importDefault(require("decimal.js"));
27
+ const bn_js_1 = __importDefault(require("bn.js"));
28
+ const hermes_client_1 = require("@pythnetwork/hermes-client");
29
+ const web3_js_1 = require("@solana/web3.js");
30
+ const constants_1 = require("../../constants");
31
+ const constants_2 = require("./constants");
32
+ const oracle_1 = require("./oracle");
33
+ function parseVerificationLevel(buf, offset) {
34
+ const discr = buf.readUInt8(offset);
35
+ if (discr === 0) { // Partial
36
+ const numSignatures = buf.readUInt8(offset + 1);
37
+ return { level: { kind: 'Partial', numSignatures }, size: 2 };
38
+ }
39
+ else if (discr === 1) { // Full
40
+ return { level: { kind: 'Full' }, size: 1 };
41
+ }
42
+ else {
43
+ throw new Error(`Unknown verification level: ${discr}`);
44
+ }
45
+ }
46
+ // Pyth program IDs (mainnet)
47
+ const DEFAULT_RECEIVER_PROGRAM_ID = new web3_js_1.PublicKey("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ");
48
+ const DEFAULT_PUSH_ORACLE_PROGRAM_ID = new web3_js_1.PublicKey("pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT");
49
+ const DEFAULT_WORMHOLE_PROGRAM_ID = new web3_js_1.PublicKey("HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ");
50
+ // Constants from Pyth SDK vaa.js
51
+ const VAA_START = 46;
52
+ const VAA_SPLIT_INDEX = 721;
53
+ const VAA_SIGNATURE_SIZE = 66;
54
+ const DEFAULT_REDUCED_GUARDIAN_SET_SIZE = 5;
55
+ // Compute budget constants
56
+ const VERIFY_ENCODED_VAA_COMPUTE_BUDGET = 350000;
57
+ const UPDATE_PRICE_FEED_COMPUTE_BUDGET = 55000;
58
+ const INIT_ENCODED_VAA_COMPUTE_BUDGET = 3000;
59
+ const WRITE_ENCODED_VAA_COMPUTE_BUDGET = 3000;
60
+ const CLOSE_ENCODED_VAA_COMPUTE_BUDGET = 30000;
61
+ // Pre-computed discriminators
62
+ const DISCRIMINATORS = {
63
+ // Wormhole instructions
64
+ initEncodedVaa: Buffer.from([209, 193, 173, 25, 91, 202, 181, 218]),
65
+ writeEncodedVaa: Buffer.from([199, 208, 110, 177, 150, 76, 118, 42]),
66
+ verifyEncodedVaaV1: Buffer.from([103, 56, 177, 229, 240, 103, 68, 73]),
67
+ closeEncodedVaa: Buffer.from([48, 221, 174, 198, 231, 7, 152, 38]),
68
+ // Pyth Push Oracle instructions
69
+ updatePriceFeed: Buffer.from([28, 9, 93, 150, 86, 153, 188, 115]),
70
+ };
71
+ // Jito tip accounts (copied from pyth solana-utils)
72
+ exports.TIP_ACCOUNTS = [
73
+ "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
74
+ "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
75
+ "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
76
+ "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
77
+ "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
78
+ "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
79
+ "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
80
+ "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
81
+ ];
82
+ function getRandomTipAccount() {
83
+ const idx = Math.floor(Math.random() * exports.TIP_ACCOUNTS.length);
84
+ return new web3_js_1.PublicKey(exports.TIP_ACCOUNTS[idx]);
85
+ }
86
+ function buildJitoTipInstruction(payer, lamports) {
87
+ return web3_js_1.SystemProgram.transfer({
88
+ fromPubkey: payer,
89
+ toPubkey: getRandomTipAccount(),
90
+ lamports,
91
+ });
92
+ }
93
+ /**
94
+ * Get a random treasury ID (0-255) - same as Pyth SDK's getRandomTreasuryId()
95
+ */
96
+ function getRandomTreasuryId() {
97
+ return Math.floor(Math.random() * 256);
98
+ }
99
+ /**
100
+ * Derives the Pyth price feed account address
101
+ */
102
+ function getPythPriceFeedAccountAddress(shardId, priceFeedId, pushOracleProgramId = DEFAULT_PUSH_ORACLE_PROGRAM_ID) {
103
+ let feedIdBuffer;
104
+ if (typeof priceFeedId === "string") {
105
+ const hexString = priceFeedId.startsWith("0x")
106
+ ? priceFeedId.slice(2)
107
+ : priceFeedId;
108
+ feedIdBuffer = Buffer.from(hexString, "hex");
109
+ }
110
+ else {
111
+ feedIdBuffer = priceFeedId;
112
+ }
113
+ if (feedIdBuffer.length !== 32) {
114
+ throw new Error("Feed ID should be 32 bytes long");
115
+ }
116
+ const shardBuffer = Buffer.alloc(2);
117
+ shardBuffer.writeUint16LE(shardId, 0);
118
+ return web3_js_1.PublicKey.findProgramAddressSync([shardBuffer, feedIdBuffer], pushOracleProgramId)[0];
119
+ }
120
+ /**
121
+ * Get Treasury PDA for Pyth Receiver
122
+ */
123
+ function getTreasuryPda(treasuryId, programId = DEFAULT_RECEIVER_PROGRAM_ID) {
124
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("treasury"), Buffer.from([treasuryId])], programId)[0];
125
+ }
126
+ /**
127
+ * Get Config PDA for Pyth Receiver
128
+ */
129
+ function getConfigPda(programId = DEFAULT_RECEIVER_PROGRAM_ID) {
130
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("config")], programId)[0];
131
+ }
132
+ /**
133
+ * Get Guardian Set PDA for Wormhole
134
+ */
135
+ function getGuardianSetPda(guardianSetIndex, programId = DEFAULT_WORMHOLE_PROGRAM_ID) {
136
+ const indexBuffer = Buffer.alloc(4);
137
+ indexBuffer.writeUInt32BE(guardianSetIndex, 0);
138
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("GuardianSet"), indexBuffer], programId)[0];
139
+ }
140
+ /**
141
+ * Get guardian set index from VAA (offset 1, big-endian u32)
142
+ */
143
+ function getGuardianSetIndex(vaa) {
144
+ return vaa.readUInt32BE(1);
145
+ }
146
+ /**
147
+ * Serialize Borsh Vec<u8> (4-byte LE length prefix + data)
148
+ */
149
+ function serializeBorshBytes(data) {
150
+ const lenBuffer = Buffer.alloc(4);
151
+ lenBuffer.writeUInt32LE(data.length, 0);
152
+ return Buffer.concat([lenBuffer, data]);
153
+ }
154
+ /**
155
+ * Serialize Borsh Vec<[u8; 20]> for merkle proof
156
+ */
157
+ function serializeBorshProof(proof) {
158
+ const lenBuffer = Buffer.alloc(4);
159
+ lenBuffer.writeUInt32LE(proof.length, 0);
160
+ return Buffer.concat([lenBuffer, ...proof]);
161
+ }
162
+ /**
163
+ * Build initEncodedVaa instruction
164
+ */
165
+ function buildInitEncodedVaaInstruction(writeAuthority, encodedVaa, wormholeProgramId = DEFAULT_WORMHOLE_PROGRAM_ID) {
166
+ return new web3_js_1.TransactionInstruction({
167
+ keys: [
168
+ { pubkey: writeAuthority, isSigner: true, isWritable: false },
169
+ { pubkey: encodedVaa, isSigner: false, isWritable: true },
170
+ ],
171
+ programId: wormholeProgramId,
172
+ data: DISCRIMINATORS.initEncodedVaa,
173
+ });
174
+ }
175
+ /**
176
+ * Build writeEncodedVaa instruction
177
+ */
178
+ function buildWriteEncodedVaaInstruction(writeAuthority, draftVaa, index, data, wormholeProgramId = DEFAULT_WORMHOLE_PROGRAM_ID) {
179
+ // Serialize args: index (u32 LE) + data (Borsh Vec<u8>)
180
+ const indexBuffer = Buffer.alloc(4);
181
+ indexBuffer.writeUInt32LE(index, 0);
182
+ const instructionData = Buffer.concat([
183
+ DISCRIMINATORS.writeEncodedVaa,
184
+ indexBuffer,
185
+ serializeBorshBytes(data),
186
+ ]);
187
+ return new web3_js_1.TransactionInstruction({
188
+ keys: [
189
+ { pubkey: writeAuthority, isSigner: true, isWritable: false },
190
+ { pubkey: draftVaa, isSigner: false, isWritable: true },
191
+ ],
192
+ programId: wormholeProgramId,
193
+ data: instructionData,
194
+ });
195
+ }
196
+ /**
197
+ * Build verifyEncodedVaaV1 instruction
198
+ */
199
+ function buildVerifyEncodedVaaV1Instruction(writeAuthority, draftVaa, guardianSetIndex, wormholeProgramId = DEFAULT_WORMHOLE_PROGRAM_ID) {
200
+ return new web3_js_1.TransactionInstruction({
201
+ keys: [
202
+ { pubkey: writeAuthority, isSigner: true, isWritable: false },
203
+ { pubkey: draftVaa, isSigner: false, isWritable: true },
204
+ { pubkey: getGuardianSetPda(guardianSetIndex, wormholeProgramId), isSigner: false, isWritable: false },
205
+ ],
206
+ programId: wormholeProgramId,
207
+ data: DISCRIMINATORS.verifyEncodedVaaV1,
208
+ });
209
+ }
210
+ /**
211
+ * Build closeEncodedVaa instruction
212
+ */
213
+ function buildCloseEncodedVaaInstruction(writeAuthority, encodedVaa, wormholeProgramId = DEFAULT_WORMHOLE_PROGRAM_ID) {
214
+ return new web3_js_1.TransactionInstruction({
215
+ keys: [
216
+ { pubkey: writeAuthority, isSigner: true, isWritable: true },
217
+ { pubkey: encodedVaa, isSigner: false, isWritable: true },
218
+ ],
219
+ programId: wormholeProgramId,
220
+ data: DISCRIMINATORS.closeEncodedVaa,
221
+ });
222
+ }
223
+ /**
224
+ * Build updatePriceFeed instruction for Pyth Push Oracle
225
+ */
226
+ function buildUpdatePriceFeedInstruction(params) {
227
+ const { payer, encodedVaa, priceFeedAccount, treasuryId, shardId, feedId, merklePriceUpdate, receiverProgramId = DEFAULT_RECEIVER_PROGRAM_ID, pushOracleProgramId = DEFAULT_PUSH_ORACLE_PROGRAM_ID, } = params;
228
+ const shardBuffer = Buffer.alloc(2);
229
+ shardBuffer.writeUint16LE(shardId, 0);
230
+ const data = Buffer.concat([
231
+ DISCRIMINATORS.updatePriceFeed,
232
+ // MerklePriceUpdate
233
+ serializeBorshBytes(merklePriceUpdate.message),
234
+ serializeBorshProof(merklePriceUpdate.proof),
235
+ // treasuryId (u8)
236
+ Buffer.from([treasuryId]),
237
+ // shardId (u16 LE)
238
+ shardBuffer,
239
+ // feedId ([u8; 32])
240
+ feedId,
241
+ ]);
242
+ const keys = [
243
+ { pubkey: payer, isSigner: true, isWritable: true },
244
+ { pubkey: receiverProgramId, isSigner: false, isWritable: false },
245
+ { pubkey: encodedVaa, isSigner: false, isWritable: false },
246
+ { pubkey: getConfigPda(receiverProgramId), isSigner: false, isWritable: false },
247
+ { pubkey: getTreasuryPda(treasuryId, receiverProgramId), isSigner: false, isWritable: true },
248
+ { pubkey: priceFeedAccount, isSigner: false, isWritable: true },
249
+ { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
250
+ ];
251
+ return new web3_js_1.TransactionInstruction({
252
+ keys,
253
+ programId: pushOracleProgramId,
254
+ data,
255
+ });
256
+ }
257
+ /**
258
+ * Convert proof from number[][] to Buffer[]
259
+ */
260
+ function convertProof(proof) {
261
+ return proof.map(p => Buffer.from(p));
262
+ }
263
+ function parsePriceFeedMessage(message) {
264
+ let cursor = 0;
265
+ const variant = message.readUInt8(cursor);
266
+ cursor += 1;
267
+ if (variant !== 0)
268
+ throw new Error('Not a price feed message');
269
+ const feedId = message.subarray(cursor, cursor + 32);
270
+ cursor += 32;
271
+ const price = message.subarray(cursor, cursor + 8);
272
+ cursor += 8;
273
+ const confidence = message.subarray(cursor, cursor + 8);
274
+ cursor += 8;
275
+ const exponent = message.readInt32BE(cursor);
276
+ cursor += 4;
277
+ const publishTime = message.subarray(cursor, cursor + 8);
278
+ cursor += 8;
279
+ const prevPublishTime = message.subarray(cursor, cursor + 8);
280
+ cursor += 8;
281
+ const emaPrice = message.subarray(cursor, cursor + 8);
282
+ cursor += 8;
283
+ const emaConf = message.subarray(cursor, cursor + 8);
284
+ return { feedId, price, confidence, exponent, publishTime, prevPublishTime, emaPrice, emaConf };
285
+ }
286
+ /** parsing of accumulator update data (VAA + updates) */
287
+ function parseAccumulatorUpdateData(data) {
288
+ const ACC_MAGIC = '504e4155';
289
+ if (data.toString('hex').slice(0, 8) !== ACC_MAGIC || data[4] !== 1 || data[5] !== 0) {
290
+ throw new Error('Invalid accumulator message');
291
+ }
292
+ let cursor = 6;
293
+ const trailingPayloadSize = data.readUInt8(cursor);
294
+ cursor += 1 + trailingPayloadSize;
295
+ cursor += 1;
296
+ const vaaSize = data.readUInt16BE(cursor);
297
+ cursor += 2;
298
+ const vaa = data.subarray(cursor, cursor + vaaSize);
299
+ cursor += vaaSize;
300
+ const numUpdates = data.readUInt8(cursor);
301
+ cursor += 1;
302
+ const updates = [];
303
+ const HASH_SIZE = 20;
304
+ for (let i = 0; i < numUpdates; i++) {
305
+ const messageSize = data.readUInt16BE(cursor);
306
+ cursor += 2;
307
+ const message = data.subarray(cursor, cursor + messageSize);
308
+ cursor += messageSize;
309
+ const numProofs = data.readUInt8(cursor);
310
+ cursor += 1;
311
+ const proof = [];
312
+ for (let j = 0; j < numProofs; j++) {
313
+ proof.push(Array.from(data.subarray(cursor, cursor + HASH_SIZE)));
314
+ cursor += HASH_SIZE;
315
+ }
316
+ updates.push({ message, proof });
317
+ }
318
+ if (cursor !== data.length)
319
+ throw new Error("Didn't reach end of message");
320
+ return { vaa, updates };
321
+ }
322
+ /**
323
+ * Get the size of a transaction that would contain the provided array of instructions
324
+ */
325
+ function getSizeOfTransaction(instructions, versionedTransaction = true, addressLookupTable) {
326
+ const programs = new Set();
327
+ const signers = new Set();
328
+ let accounts = new Set();
329
+ instructions.map((ix) => {
330
+ programs.add(ix.programId.toBase58());
331
+ accounts.add(ix.programId.toBase58());
332
+ ix.keys.map((key) => {
333
+ if (key.isSigner) {
334
+ signers.add(key.pubkey.toBase58());
335
+ }
336
+ accounts.add(key.pubkey.toBase58());
337
+ });
338
+ });
339
+ const instruction_sizes = instructions
340
+ .map((ix) => 1 +
341
+ getSizeOfCompressedU16(ix.keys.length) +
342
+ ix.keys.length +
343
+ getSizeOfCompressedU16(ix.data.length) +
344
+ ix.data.length)
345
+ .reduce((a, b) => a + b, 0);
346
+ let numberOfAddressLookups = 0;
347
+ if (addressLookupTable) {
348
+ const lookupTableAddresses = addressLookupTable.state.addresses.map((address) => address.toBase58());
349
+ const totalNumberOfAccounts = accounts.size;
350
+ accounts = new Set([...accounts].filter((account) => !lookupTableAddresses.includes(account)));
351
+ accounts = new Set([...accounts, ...programs, ...signers]);
352
+ numberOfAddressLookups = totalNumberOfAccounts - accounts.size;
353
+ }
354
+ return (getSizeOfCompressedU16(signers.size) +
355
+ signers.size * 64 +
356
+ 3 +
357
+ getSizeOfCompressedU16(accounts.size) +
358
+ 32 * accounts.size +
359
+ 32 +
360
+ getSizeOfCompressedU16(instructions.length) +
361
+ instruction_sizes +
362
+ (versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) +
363
+ (versionedTransaction && addressLookupTable ? 32 : 0) +
364
+ (versionedTransaction && addressLookupTable ? 2 : 0) +
365
+ numberOfAddressLookups);
366
+ }
367
+ /** Get the size of n in bytes when serialized as a CompressedU16 */
368
+ function getSizeOfCompressedU16(n) {
369
+ return 1 + Number(n >= 128) + Number(n >= 16384);
370
+ }
371
+ const ENCODED_VAA_RENT_EXEMPTION = 7836960;
372
+ /**
373
+ * Fetch Pyth price accounts from RPC and extract feed IDs
374
+ */
375
+ function fetchFeedIdsFromAccounts(connection, priceAccounts) {
376
+ return __awaiter(this, void 0, void 0, function* () {
377
+ const accountInfos = yield connection.getMultipleAccountsInfo(priceAccounts, "confirmed");
378
+ const feedIds = [];
379
+ const feedIdToAccount = new Map();
380
+ for (let i = 0; i < priceAccounts.length; i++) {
381
+ const ai = accountInfos[i];
382
+ if (!ai)
383
+ throw new Error(`Account ${priceAccounts[i].toBase58()} not found`);
384
+ const [state, _] = PythState.decode(ai.data, 8);
385
+ const feedIdHex = "0x" + state.priceMessage.feedId.toString("hex");
386
+ feedIds.push(feedIdHex);
387
+ feedIdToAccount.set(feedIdHex, priceAccounts[i]);
388
+ }
389
+ return { feedIds, feedIdToAccount };
390
+ });
391
+ }
392
+ /**
393
+ * Build Pyth price feed update as TxBatchData compatible with SDK txUtils.
394
+ *
395
+ * TxBatchData.batches layout:
396
+ * batch 0 (sequential): [createAccount + initEncodedVaa + writeVaa_part1]
397
+ * batch 1 (sequential): [writeVaa_part2 + verifyEncodedVaa]
398
+ * batch 2 (parallel): [updateFeed_0], [updateFeed_1], ... [updateFeed_N-2]
399
+ * batch 3 (sequential): [updateFeed_last + closeEncodedVaa]
400
+ *
401
+ * Transactions within each batch are sent in parallel by sendVersionedTxs.
402
+ * Batches are sent sequentially.
403
+ */
404
+ function buildPythPriceFeedUpdateIxs(payer, priceFeedIds, defaultShardId, hermesClient) {
405
+ return __awaiter(this, void 0, void 0, function* () {
406
+ var _a;
407
+ const client = hermesClient !== null && hermesClient !== void 0 ? hermesClient : new hermes_client_1.HermesClient(constants_2.HERMES_PUBLIC_ENDPOINT, {});
408
+ const shardId = defaultShardId !== null && defaultShardId !== void 0 ? defaultShardId : 0;
409
+ const priceUpdates = yield client.getLatestPriceUpdates(priceFeedIds, { encoding: "base64" });
410
+ if (!((_a = priceUpdates.binary) === null || _a === void 0 ? void 0 : _a.data) || priceUpdates.binary.data.length === 0) {
411
+ throw new Error("Failed to fetch price updates from Hermes");
412
+ }
413
+ const treasuryId = getRandomTreasuryId();
414
+ let allVaaCreateInitEncodeIxs = [];
415
+ let allVaaWriteVerifyIxs = [];
416
+ const allUpdateIxs = [];
417
+ const allCloseIxs = [];
418
+ for (const updateData of priceUpdates.binary.data) {
419
+ const accumulatorData = parseAccumulatorUpdateData(Buffer.from(updateData, "base64"));
420
+ const vaa = accumulatorData.vaa;
421
+ const encodedVaaKeypair = web3_js_1.Keypair.generate();
422
+ const encodedVaaSize = vaa.length + VAA_START;
423
+ const guardianSetIndex = getGuardianSetIndex(vaa);
424
+ let vaaCreateInitEncodeIxs = [];
425
+ let vaaWriteVerifyIxs = [];
426
+ // createAccount
427
+ vaaCreateInitEncodeIxs.push(web3_js_1.SystemProgram.createAccount({
428
+ fromPubkey: payer,
429
+ newAccountPubkey: encodedVaaKeypair.publicKey,
430
+ lamports: ENCODED_VAA_RENT_EXEMPTION,
431
+ space: encodedVaaSize,
432
+ programId: DEFAULT_WORMHOLE_PROGRAM_ID,
433
+ }));
434
+ // initEncodedVaa
435
+ vaaCreateInitEncodeIxs.push(buildInitEncodedVaaInstruction(payer, encodedVaaKeypair.publicKey));
436
+ // writeEncodedVaa part 1
437
+ const firstPartEnd = Math.min(VAA_SPLIT_INDEX, vaa.length);
438
+ vaaCreateInitEncodeIxs.push(buildWriteEncodedVaaInstruction(payer, encodedVaaKeypair.publicKey, 0, vaa.subarray(0, firstPartEnd)));
439
+ // writeEncodedVaa part 2 (if needed)
440
+ if (vaa.length > VAA_SPLIT_INDEX) {
441
+ vaaWriteVerifyIxs.push(buildWriteEncodedVaaInstruction(payer, encodedVaaKeypair.publicKey, VAA_SPLIT_INDEX, vaa.subarray(VAA_SPLIT_INDEX)));
442
+ }
443
+ // verifyEncodedVaaV1
444
+ vaaWriteVerifyIxs.push(buildVerifyEncodedVaaV1Instruction(payer, encodedVaaKeypair.publicKey, guardianSetIndex));
445
+ // updateFeed per price update
446
+ for (const update of accumulatorData.updates) {
447
+ const parsedMessage = parsePriceFeedMessage(update.message);
448
+ const feedId = parsedMessage.feedId;
449
+ allUpdateIxs.push(buildUpdatePriceFeedInstruction({
450
+ payer,
451
+ encodedVaa: encodedVaaKeypair.publicKey,
452
+ priceFeedAccount: getPythPriceFeedAccountAddress(shardId, feedId),
453
+ treasuryId,
454
+ shardId,
455
+ feedId,
456
+ merklePriceUpdate: {
457
+ message: update.message,
458
+ proof: convertProof(update.proof),
459
+ },
460
+ }));
461
+ }
462
+ allVaaCreateInitEncodeIxs.push({
463
+ ixs: vaaCreateInitEncodeIxs,
464
+ signer: encodedVaaKeypair,
465
+ });
466
+ allVaaWriteVerifyIxs.push(vaaWriteVerifyIxs);
467
+ // closeEncodedVaa
468
+ allCloseIxs.push(buildCloseEncodedVaaInstruction(payer, encodedVaaKeypair.publicKey));
469
+ }
470
+ return {
471
+ vaaCreateInitEncodeIxs: allVaaCreateInitEncodeIxs,
472
+ vaaWriteVerifyIxs: allVaaWriteVerifyIxs,
473
+ updateFeedIxs: allUpdateIxs,
474
+ closeVaaIxs: allCloseIxs,
475
+ };
476
+ });
477
+ }
478
+ class PriceFeedMessage {
479
+ constructor(params) {
480
+ this.feedId = params.feedId;
481
+ this.price = params.price;
482
+ this.conf = params.conf;
483
+ this.exponent = params.exponent;
484
+ this.publishTime = params.publishTime;
485
+ this.prevPublishTime = params.prevPublishTime;
486
+ this.emaPrice = params.emaPrice;
487
+ this.emaConf = params.emaConf;
488
+ }
489
+ static decode(buf, offset = 0) {
490
+ let cursor = offset;
491
+ // feed_id: [u8;32]
492
+ const feedId = buf.subarray(cursor, cursor + 32);
493
+ cursor += 32;
494
+ // price: i64 (signed)
495
+ const price = new bn_js_1.default(buf.subarray(cursor, cursor + 8), "le").fromTwos(64);
496
+ cursor += 8;
497
+ // conf: u64 (unsigned)
498
+ const conf = new bn_js_1.default(buf.subarray(cursor, cursor + 8), "le");
499
+ cursor += 8;
500
+ // exponent: i32 (signed)
501
+ const exponent = new bn_js_1.default(buf.subarray(cursor, cursor + 4), "le").fromTwos(32).toNumber();
502
+ cursor += 4;
503
+ // publish_time: i64 (signed)
504
+ const publishTime = new bn_js_1.default(buf.subarray(cursor, cursor + 8), "le").fromTwos(64);
505
+ cursor += 8;
506
+ // prev_publish_time: i64 (signed)
507
+ const prevPublishTime = new bn_js_1.default(buf.subarray(cursor, cursor + 8), "le").fromTwos(64);
508
+ cursor += 8;
509
+ // ema_price: i64 (signed)
510
+ const emaPrice = new bn_js_1.default(buf.subarray(cursor, cursor + 8), "le").fromTwos(64);
511
+ cursor += 8;
512
+ // ema_conf: u64 (unsigned)
513
+ const emaConf = new bn_js_1.default(buf.subarray(cursor, cursor + 8), "le");
514
+ cursor += 8;
515
+ return [new PriceFeedMessage({
516
+ feedId,
517
+ price,
518
+ conf,
519
+ exponent,
520
+ publishTime,
521
+ prevPublishTime,
522
+ emaPrice,
523
+ emaConf
524
+ }), cursor];
525
+ }
526
+ }
527
+ exports.PriceFeedMessage = PriceFeedMessage;
528
+ class PythState {
529
+ constructor(writeAuthority, verificationLevel, priceMessage, postedSlot) {
530
+ this.writeAuthority = writeAuthority;
531
+ this.verificationLevel = verificationLevel;
532
+ this.priceMessage = priceMessage;
533
+ this.postedSlot = postedSlot;
534
+ }
535
+ ;
536
+ static decode(buf, offset = 0) {
537
+ let cursor = offset;
538
+ // write_authority: Pubkey (32)
539
+ const writeAuthority = new web3_js_1.PublicKey(buf.subarray(cursor, cursor + 32));
540
+ cursor += 32;
541
+ const { level, size } = parseVerificationLevel(buf, cursor);
542
+ cursor += size;
543
+ // price_message: PriceFeedMessage
544
+ const [priceMessage, next] = PriceFeedMessage.decode(buf, cursor);
545
+ cursor = next;
546
+ // posted_slot: u64
547
+ const postedSlot = new bn_js_1.default(buf.subarray(cursor, cursor + 8), "le");
548
+ cursor += 8;
549
+ return [new PythState(writeAuthority, level, priceMessage, postedSlot), cursor];
550
+ }
551
+ }
552
+ exports.PythState = PythState;
553
+ class PythOracle {
554
+ static fetch(oracleParams, accountInfos, solPrice, usdPrice) {
555
+ //@ts-ignore
556
+ let state = null;
557
+ try {
558
+ const stateAi = accountInfos[0];
559
+ const [parsedState, _] = PythState.decode(stateAi.data, 8);
560
+ state = parsedState;
561
+ }
562
+ catch (error) {
563
+ return new oracle_1.OraclePrice(new decimal_js_1.default(0), new decimal_js_1.default(0), 0);
564
+ }
565
+ let pr = new decimal_js_1.default(state.priceMessage.price.toString()).mul(decimal_js_1.default.pow(10, state.priceMessage.exponent - oracleParams.tokenDecimals));
566
+ let ema = new decimal_js_1.default(state.priceMessage.emaPrice.toString()).mul(decimal_js_1.default.pow(10, state.priceMessage.exponent - oracleParams.tokenDecimals));
567
+ let cf = new decimal_js_1.default(state.priceMessage.conf.toString()).mul(decimal_js_1.default.pow(10, state.priceMessage.exponent - oracleParams.tokenDecimals));
568
+ const lastUpdateTimestamp = state.priceMessage.publishTime;
569
+ // Validate primary price is not zero
570
+ if (pr.lte(0)) {
571
+ return new oracle_1.OraclePrice(new decimal_js_1.default(0), new decimal_js_1.default(0), 0);
572
+ }
573
+ // === 1. Inflate confidence by staleness ===
574
+ // confidence = confidence * (1 + delta_t * stalenessConfRateBps / 10_000)
575
+ const now = new bn_js_1.default(Math.floor(Date.now() / 1000));
576
+ const deltaSeconds = Math.max(now.toNumber() - lastUpdateTimestamp.toNumber(), 0);
577
+ const deltaT = new decimal_js_1.default(deltaSeconds);
578
+ const deltaTBN = new bn_js_1.default(deltaSeconds);
579
+ const stalenessRate = new decimal_js_1.default(oracleParams.stalenessConfRateBps).div(new decimal_js_1.default(constants_1.HUNDRED_PERCENT_BPS));
580
+ const inflateFactor = new decimal_js_1.default(1).add(deltaT.mul(stalenessRate));
581
+ cf = cf.mul(inflateFactor);
582
+ let validated = true;
583
+ // === 2. Validate confidence threshold ===
584
+ // confidence / price * 10_000 < confThreshBps
585
+ const confRatioBps = cf.div(pr).mul(new decimal_js_1.default(constants_1.HUNDRED_PERCENT_BPS));
586
+ if (confRatioBps.gt(new decimal_js_1.default(oracleParams.confThreshBps)))
587
+ validated = false;
588
+ // === 3. Validate staleness threshold ===
589
+ if (deltaTBN.gt(oracleParams.stalenessThresh))
590
+ validated = false;
591
+ // === 4. Validate volatility threshold using EMA and price ===
592
+ // max(ema, price) / min(ema, price) * 10_000 < volatilityThreshBps
593
+ const maxPrice = decimal_js_1.default.max(ema, pr);
594
+ const minPrice = decimal_js_1.default.min(ema, pr);
595
+ if (!minPrice.eq(0)) {
596
+ const volRatio = maxPrice.sub(minPrice).div(minPrice);
597
+ const volRatioBps = volRatio.mul(new decimal_js_1.default(constants_1.HUNDRED_PERCENT_BPS));
598
+ if (volRatioBps.gt(new decimal_js_1.default(oracleParams.volatilityThreshBps))) {
599
+ validated = false;
600
+ }
601
+ }
602
+ else {
603
+ validated = false;
604
+ }
605
+ // we don't validate liquidity for Pyth
606
+ return new oracle_1.OraclePrice(pr, cf, parseInt(state.priceMessage.publishTime.toString()), validated);
607
+ }
608
+ }
609
+ exports.PythOracle = PythOracle;