@pythnetwork/pyth-solana-receiver 0.12.0 → 0.14.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 (51) hide show
  1. package/README.md +0 -44
  2. package/dist/cjs/PythSolanaReceiver.cjs +442 -0
  3. package/{lib → dist/cjs}/PythSolanaReceiver.d.ts +10 -86
  4. package/dist/cjs/address.cjs +63 -0
  5. package/{lib → dist/cjs}/address.d.ts +0 -1
  6. package/dist/cjs/compute_budget.cjs +42 -0
  7. package/{lib → dist/cjs}/compute_budget.d.ts +0 -5
  8. package/dist/cjs/idl/pyth_push_oracle.cjs +132 -0
  9. package/{lib → dist/cjs}/idl/pyth_push_oracle.d.ts +0 -1
  10. package/dist/cjs/idl/pyth_solana_receiver.cjs +858 -0
  11. package/{lib → dist/cjs}/idl/pyth_solana_receiver.d.ts +0 -1
  12. package/dist/cjs/idl/wormhole_core_bridge_solana.cjs +1751 -0
  13. package/{lib → dist/cjs}/idl/wormhole_core_bridge_solana.d.ts +0 -1
  14. package/dist/cjs/index.cjs +44 -0
  15. package/dist/cjs/index.d.ts +5 -0
  16. package/dist/cjs/package.json +1 -0
  17. package/dist/cjs/vaa.cjs +192 -0
  18. package/{lib → dist/cjs}/vaa.d.ts +4 -30
  19. package/dist/esm/PythSolanaReceiver.d.ts +318 -0
  20. package/dist/esm/PythSolanaReceiver.mjs +462 -0
  21. package/dist/esm/address.d.ts +29 -0
  22. package/dist/esm/address.mjs +45 -0
  23. package/dist/esm/compute_budget.d.ts +28 -0
  24. package/dist/esm/compute_budget.mjs +21 -0
  25. package/dist/esm/idl/pyth_push_oracle.d.ts +117 -0
  26. package/{lib/idl/pyth_push_oracle.js → dist/esm/idl/pyth_push_oracle.mjs} +41 -38
  27. package/dist/esm/idl/pyth_solana_receiver.d.ts +840 -0
  28. package/{lib/idl/pyth_solana_receiver.js → dist/esm/idl/pyth_solana_receiver.mjs} +237 -231
  29. package/dist/esm/idl/wormhole_core_bridge_solana.d.ts +1606 -0
  30. package/{lib/idl/wormhole_core_bridge_solana.js → dist/esm/idl/wormhole_core_bridge_solana.mjs} +598 -465
  31. package/dist/esm/index.d.ts +5 -0
  32. package/dist/esm/index.mjs +5 -0
  33. package/dist/esm/package.json +1 -0
  34. package/dist/esm/vaa.d.ts +75 -0
  35. package/dist/esm/vaa.mjs +197 -0
  36. package/package.json +101 -13
  37. package/lib/PythSolanaReceiver.d.ts.map +0 -1
  38. package/lib/PythSolanaReceiver.js +0 -633
  39. package/lib/address.d.ts.map +0 -1
  40. package/lib/address.js +0 -46
  41. package/lib/compute_budget.d.ts.map +0 -1
  42. package/lib/compute_budget.js +0 -35
  43. package/lib/idl/pyth_push_oracle.d.ts.map +0 -1
  44. package/lib/idl/pyth_solana_receiver.d.ts.map +0 -1
  45. package/lib/idl/pyth_solana_receiver.json +0 -839
  46. package/lib/idl/wormhole_core_bridge_solana.d.ts.map +0 -1
  47. package/lib/index.d.ts +0 -6
  48. package/lib/index.d.ts.map +0 -1
  49. package/lib/index.js +0 -17
  50. package/lib/vaa.d.ts.map +0 -1
  51. package/lib/vaa.js +0 -270
package/README.md CHANGED
@@ -175,50 +175,6 @@ Price updates are relatively large and can take multiple transactions to post on
175
175
  You can reduce the size of the transaction payload by using `addPostPartiallyVerifiedPriceUpdates` instead of `addPostPriceUpdates`.
176
176
  This method does sacrifice some security however -- please see the method documentation for more details.
177
177
 
178
- ### Post a TWAP price update
179
-
180
- TWAP price updates are calculated using a pair of verifiable cumulative price updates per price feed (the "start" and "end" updates for the given time window), and then performing an averaging calculation on-chain to create the time-weighted average price.
181
-
182
- The flow of using, verifying, posting, and consuming these prices is the same as standard price updates. Get the binary update data from Hermes or Benchmarks, post and verify the VAAs via the Wormhole contract, and verify the updates against the VAAs via Pyth receiver contract. After this, you can consume the calculated TWAP posted to the TwapUpdate account. You can also optionally close these ephemeral accounts after the TWAP has been consumed to save on rent.
183
-
184
- ```typescript
185
- // Fetch the binary TWAP data from hermes or benchmarks. See Preliminaries section above for more info.
186
- const binaryDataArray = ["UE5BV...khz609", "UE5BV...BAg8i6"];
187
-
188
- // Pass `closeUpdateAccounts: true` to automatically close the TWAP update accounts
189
- // after they're consumed
190
- const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
191
- closeUpdateAccounts: false,
192
- });
193
-
194
- // Post the updates and calculate the TWAP
195
- await transactionBuilder.addPostTwapUpdates(binaryDataArray);
196
-
197
- // You can now use the TWAP prices in subsequent instructions
198
- await transactionBuilder.addTwapConsumerInstructions(
199
- async (
200
- getTwapUpdateAccount: (priceFeedId: string) => PublicKey
201
- ): Promise<InstructionWithEphemeralSigners[]> => {
202
- // Generate instructions here that use the TWAP updates posted above.
203
- // getTwapUpdateAccount(<price feed id>) will give you the account for each TWAP update.
204
- return [];
205
- }
206
- );
207
-
208
- // Send the instructions in the builder in 1 or more transactions.
209
- // The builder will pack the instructions into transactions automatically.
210
- sendTransactions(
211
- await transactionBuilder.buildVersionedTransactions({
212
- computeUnitPriceMicroLamports: 100000,
213
- tightComputeBudget: true,
214
- }),
215
- pythSolanaReceiver.connection,
216
- pythSolanaReceiver.wallet
217
- );
218
- ```
219
-
220
- See `examples/post_twap_update.ts` for a runnable example of posting a TWAP price update.
221
-
222
178
  ### Get Instructions
223
179
 
224
180
  The `PythTransactionBuilder` class used in the examples above helps craft transactions that update prices and then use them in successive instructions.
@@ -0,0 +1,442 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable tsdoc/syntax */ // eslint-disable-next-line unicorn/prefer-node-protocol
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ function _export(target, all) {
7
+ for(var name in all)Object.defineProperty(target, name, {
8
+ enumerable: true,
9
+ get: Object.getOwnPropertyDescriptor(all, name).get
10
+ });
11
+ }
12
+ _export(exports, {
13
+ get DEFAULT_TREASURY_ID () {
14
+ return DEFAULT_TREASURY_ID;
15
+ },
16
+ get PythSolanaReceiver () {
17
+ return PythSolanaReceiver;
18
+ },
19
+ get PythTransactionBuilder () {
20
+ return PythTransactionBuilder;
21
+ },
22
+ get getPriceFeedAccountForProgram () {
23
+ return getPriceFeedAccountForProgram;
24
+ }
25
+ });
26
+ const _buffer = require("buffer");
27
+ const _anchor = require("@coral-xyz/anchor");
28
+ const _priceservicesdk = require("@pythnetwork/price-service-sdk");
29
+ const _solanautils = require("@pythnetwork/solana-utils");
30
+ const _web3 = require("@solana/web3.js");
31
+ const _address = require("./address.cjs");
32
+ const _compute_budget = require("./compute_budget.cjs");
33
+ const _pyth_push_oracle = require("./idl/pyth_push_oracle.cjs");
34
+ const _pyth_solana_receiver = require("./idl/pyth_solana_receiver.cjs");
35
+ const _wormhole_core_bridge_solana = require("./idl/wormhole_core_bridge_solana.cjs");
36
+ const _vaa = require("./vaa.cjs");
37
+ const DEFAULT_TREASURY_ID = 0;
38
+ class PythTransactionBuilder extends _solanautils.TransactionBuilder {
39
+ pythSolanaReceiver;
40
+ closeInstructions;
41
+ priceFeedIdToPriceUpdateAccount;
42
+ closeUpdateAccounts;
43
+ constructor(pythSolanaReceiver, config, addressLookupTable){
44
+ super(pythSolanaReceiver.wallet.publicKey, pythSolanaReceiver.connection, addressLookupTable);
45
+ this.pythSolanaReceiver = pythSolanaReceiver;
46
+ this.closeInstructions = [];
47
+ this.priceFeedIdToPriceUpdateAccount = {};
48
+ this.closeUpdateAccounts = config.closeUpdateAccounts ?? true;
49
+ }
50
+ /**
51
+ * Add instructions to post price updates to the builder.
52
+ * Use this function to post fully verified price updates from the present or from the past for your program to consume.
53
+ *
54
+ * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * // Get the price feed ids from https://pyth.network/developers/price-feed-ids#pyth-evm-stable
59
+ * const priceUpdateData = await priceServiceConnection.getLatestVaas([
60
+ * SOL_PRICE_FEED_ID,
61
+ * ETH_PRICE_FEED_ID,
62
+ * ]);
63
+ *
64
+ * const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({});
65
+ * await transactionBuilder.addPostPriceUpdates(priceUpdateData);
66
+ * console.log("The SOL/USD price update will get posted to:", transactionBuilder.getPriceUpdateAccount(SOL_PRICE_FEED_ID).toBase58())
67
+ * await transactionBuilder.addPriceConsumerInstructions(...)
68
+ * ```
69
+ */ async addPostPriceUpdates(priceUpdateDataArray) {
70
+ const { postInstructions, priceFeedIdToPriceUpdateAccount, closeInstructions } = await this.pythSolanaReceiver.buildPostPriceUpdateInstructions(priceUpdateDataArray);
71
+ this.closeInstructions.push(...closeInstructions);
72
+ Object.assign(this.priceFeedIdToPriceUpdateAccount, priceFeedIdToPriceUpdateAccount);
73
+ this.addInstructions(postInstructions);
74
+ }
75
+ /**
76
+ * Add instructions to post partially verified price updates to the builder.
77
+ * Use this function to post partially verified price updates from the present or from the past for your program to consume.
78
+ *
79
+ * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
80
+ *
81
+ * Partially verified price updates are price updates where not all the guardian signatures have been verified. By default this methods checks `DEFAULT_REDUCED_GUARDIAN_SET_SIZE` signatures when posting the VAA.
82
+ * If you are a on-chain program developer, make sure you understand the risks of consuming partially verified price updates here: {@link https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs}.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * // Get the price feed ids from https://pyth.network/developers/price-feed-ids#pyth-evm-stable
87
+ * const priceUpdateData = await priceServiceConnection.getLatestVaas([
88
+ * SOL_PRICE_FEED_ID,
89
+ * ETH_PRICE_FEED_ID,
90
+ * ]);
91
+ *
92
+ * const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({});
93
+ * await transactionBuilder.addPostPartiallyVerifiedPriceUpdates(priceUpdateData);
94
+ * console.log("The SOL/USD price update will get posted to:", transactionBuilder.getPriceUpdateAccount(SOL_PRICE_FEED_ID).toBase58())
95
+ * await transactionBuilder.addPriceConsumerInstructions(...)
96
+ * ...
97
+ * ```
98
+ */ async addPostPartiallyVerifiedPriceUpdates(priceUpdateDataArray) {
99
+ const { postInstructions, priceFeedIdToPriceUpdateAccount, closeInstructions } = await this.pythSolanaReceiver.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray);
100
+ this.closeInstructions.push(...closeInstructions);
101
+ Object.assign(this.priceFeedIdToPriceUpdateAccount, priceFeedIdToPriceUpdateAccount);
102
+ this.addInstructions(postInstructions);
103
+ }
104
+ /**
105
+ * Add instructions to update price feed accounts to the builder.
106
+ * Price feed accounts are fixed accounts per price feed id that can only be updated with a more recent price.
107
+ *
108
+ * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
109
+ * @param shardId the shard ID of the set of price feed accounts. This shard ID allows for multiple price feed accounts for the same price feed id to exist.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * // Get the price feed ids from https://pyth.network/developers/price-feed-ids#pyth-evm-stable
114
+ * const priceUpdateData = await priceServiceConnection.getLatestVaas([
115
+ * SOL_PRICE_FEED_ID,
116
+ * ETH_PRICE_FEED_ID,
117
+ * ]);
118
+ *
119
+ * const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({});
120
+ * await transactionBuilder.addUpdatePriceFeed(priceUpdateData);
121
+ * await transactionBuilder.addPriceConsumerInstructions(...)
122
+ * ...
123
+ * ```
124
+ */ async addUpdatePriceFeed(priceUpdateDataArray, shardId) {
125
+ const { postInstructions, priceFeedIdToPriceUpdateAccount, closeInstructions } = await this.pythSolanaReceiver.buildUpdatePriceFeedInstructions(priceUpdateDataArray, shardId);
126
+ this.closeInstructions.push(...closeInstructions);
127
+ Object.assign(this.priceFeedIdToPriceUpdateAccount, priceFeedIdToPriceUpdateAccount);
128
+ this.addInstructions(postInstructions);
129
+ }
130
+ /**
131
+ * Add instructions that consume price updates to the builder.
132
+ *
133
+ * @param getInstructions a function that given a mapping of price feed IDs to price update accounts, generates a series of instructions. Price updates get posted to ephemeral accounts and this function allows the user to indicate which accounts in their instruction need to be "replaced" with each price update account.
134
+ * If multiple price updates for the same price feed ID are posted with the same builder, the account corresponding to the last update to get posted will be used.
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * ...
139
+ * await transactionBuilder.addPostPriceUpdates(priceUpdateData);
140
+ * await transactionBuilder.addPriceConsumerInstructions(
141
+ * async (
142
+ * getPriceUpdateAccount: ( priceFeedId: string) => PublicKey
143
+ * ): Promise<InstructionWithEphemeralSigners[]> => {
144
+ * return [
145
+ * {
146
+ * instruction: await myFirstPythApp.methods
147
+ * .consume()
148
+ * .accounts({
149
+ * solPriceUpdate: getPriceUpdateAccount(SOL_PRICE_FEED_ID),
150
+ * ethPriceUpdate: getPriceUpdateAccount(ETH_PRICE_FEED_ID),
151
+ * })
152
+ * .instruction(),
153
+ * signers: [],
154
+ * },
155
+ * ];
156
+ * }
157
+ * );
158
+ * ```
159
+ */ async addPriceConsumerInstructions(getInstructions) {
160
+ this.addInstructions(await getInstructions(this.getPriceUpdateAccount.bind(this)));
161
+ }
162
+ /** Add instructions to close encoded VAA accounts from previous actions.
163
+ * If you have previously used the PythTransactionBuilder with closeUpdateAccounts set to false or if you posted encoded VAAs but the transaction to close them did not land on-chain, your wallet might own many encoded VAA accounts.
164
+ * The rent cost for these accounts is 0.008 SOL per encoded VAA account. You can recover this rent calling this function when building a set of transactions.
165
+ */ async addClosePreviousEncodedVaasInstructions(maxInstructions = 40) {
166
+ this.addInstructions(await this.pythSolanaReceiver.buildClosePreviousEncodedVaasInstructions(maxInstructions));
167
+ }
168
+ /**
169
+ * Returns all the added instructions batched into versioned transactions, plus for each transaction the ephemeral signers that need to sign it
170
+ */ async buildVersionedTransactions(args) {
171
+ if (this.closeUpdateAccounts) {
172
+ this.addInstructions(this.closeInstructions);
173
+ }
174
+ return super.buildVersionedTransactions(args);
175
+ }
176
+ /**
177
+ * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it
178
+ */ buildLegacyTransactions(args) {
179
+ if (this.closeUpdateAccounts) {
180
+ this.addInstructions(this.closeInstructions);
181
+ }
182
+ return super.buildLegacyTransactions(args);
183
+ }
184
+ /**
185
+ * This method is used to retrieve the address of the price update account where the price update for a given price feed ID will be posted.
186
+ * If multiple price updates for the same price feed ID will be posted with the same builder, the address of the account corresponding to the last update to get posted will be returned.
187
+ * */ getPriceUpdateAccount(priceFeedId) {
188
+ const priceUpdateAccount = this.priceFeedIdToPriceUpdateAccount[priceFeedId];
189
+ if (!priceUpdateAccount) {
190
+ throw new Error(`No price update account found for the price feed ID ${priceFeedId}. Make sure to call addPostPriceUpdates or addPostPartiallyVerifiedPriceUpdates before calling this function.`);
191
+ }
192
+ return priceUpdateAccount;
193
+ }
194
+ }
195
+ class PythSolanaReceiver {
196
+ connection;
197
+ wallet;
198
+ provider;
199
+ receiver;
200
+ wormhole;
201
+ pushOracle;
202
+ treasuryId;
203
+ constructor({ connection, wallet, wormholeProgramId = _address.DEFAULT_WORMHOLE_PROGRAM_ID, receiverProgramId = _address.DEFAULT_RECEIVER_PROGRAM_ID, pushOracleProgramId = _address.DEFAULT_PUSH_ORACLE_PROGRAM_ID, treasuryId }){
204
+ if (treasuryId !== undefined && (treasuryId < 0 || treasuryId > 255)) {
205
+ throw new Error("treasuryId must be between 0 and 255");
206
+ }
207
+ this.connection = connection;
208
+ this.wallet = wallet;
209
+ this.provider = new _anchor.AnchorProvider(this.connection, this.wallet, {
210
+ commitment: connection.commitment
211
+ });
212
+ this.receiver = new _anchor.Program(_pyth_solana_receiver.IDL, receiverProgramId, this.provider);
213
+ this.wormhole = new _anchor.Program(_wormhole_core_bridge_solana.IDL, wormholeProgramId, this.provider);
214
+ this.pushOracle = new _anchor.Program(_pyth_push_oracle.IDL, pushOracleProgramId, this.provider);
215
+ this.treasuryId = treasuryId;
216
+ }
217
+ /**
218
+ * Get a new transaction builder to build transactions that interact with the Pyth Solana Receiver program and consume price updates
219
+ */ newTransactionBuilder(config, addressLookupAccount) {
220
+ return new PythTransactionBuilder(this, config, addressLookupAccount);
221
+ }
222
+ /**
223
+ * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to close the price update accounts.
224
+ *
225
+ * This function uses partially verified price updates. Partially verified price updates are price updates where not all the guardian signatures have been verified. By default this methods checks `DEFAULT_REDUCED_GUARDIAN_SET_SIZE` signatures when posting the VAA.
226
+ * If you are a on-chain program developer, make sure you understand the risks of consuming partially verified price updates here: {@link https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs}.
227
+ *
228
+ * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
229
+ * @returns `postInstructions`: the instructions to post the price updates, these should be called before consuming the price updates
230
+ * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update.
231
+ * @returns `closeInstructions`: the instructions to close the price update accounts, these should be called after consuming the price updates
232
+ */ async buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray) {
233
+ const postInstructions = [];
234
+ const priceFeedIdToPriceUpdateAccount = {};
235
+ const closeInstructions = [];
236
+ const treasuryId = this.treasuryId ?? (0, _address.getRandomTreasuryId)();
237
+ for (const priceUpdateData of priceUpdateDataArray){
238
+ const accumulatorUpdateData = (0, _priceservicesdk.parseAccumulatorUpdateData)(_buffer.Buffer.from(priceUpdateData, "base64"));
239
+ const guardianSetIndex = (0, _vaa.getGuardianSetIndex)(accumulatorUpdateData.vaa);
240
+ const trimmedVaa = (0, _vaa.trimSignatures)(accumulatorUpdateData.vaa);
241
+ for (const update of accumulatorUpdateData.updates){
242
+ const priceUpdateKeypair = new _web3.Keypair();
243
+ postInstructions.push({
244
+ instruction: await this.receiver.methods.postUpdateAtomic({
245
+ vaa: trimmedVaa,
246
+ merklePriceUpdate: update,
247
+ treasuryId
248
+ }).accounts({
249
+ priceUpdateAccount: priceUpdateKeypair.publicKey,
250
+ treasury: (0, _address.getTreasuryPda)(treasuryId, this.receiver.programId),
251
+ config: (0, _address.getConfigPda)(this.receiver.programId),
252
+ guardianSet: (0, _address.getGuardianSetPda)(guardianSetIndex, this.wormhole.programId)
253
+ }).instruction(),
254
+ signers: [
255
+ priceUpdateKeypair
256
+ ],
257
+ computeUnits: _compute_budget.POST_UPDATE_ATOMIC_COMPUTE_BUDGET
258
+ });
259
+ priceFeedIdToPriceUpdateAccount["0x" + (0, _priceservicesdk.parsePriceFeedMessage)(update.message).feedId.toString("hex")] = priceUpdateKeypair.publicKey;
260
+ closeInstructions.push(await this.buildClosePriceUpdateInstruction(priceUpdateKeypair.publicKey));
261
+ }
262
+ }
263
+ return {
264
+ postInstructions,
265
+ priceFeedIdToPriceUpdateAccount,
266
+ closeInstructions
267
+ };
268
+ }
269
+ /**
270
+ * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to close the encoded vaa accounts and the price update accounts.
271
+ *
272
+ * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
273
+ * @returns `postInstructions`: the instructions to post the price updates, these should be called before consuming the price updates
274
+ * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update.
275
+ * @returns `closeInstructions`: the instructions to close the price update accounts, these should be called after consuming the price updates
276
+ */ async buildPostPriceUpdateInstructions(priceUpdateDataArray) {
277
+ const postInstructions = [];
278
+ const priceFeedIdToPriceUpdateAccount = {};
279
+ const closeInstructions = [];
280
+ const treasuryId = this.treasuryId ?? (0, _address.getRandomTreasuryId)();
281
+ for (const priceUpdateData of priceUpdateDataArray){
282
+ const accumulatorUpdateData = (0, _priceservicesdk.parseAccumulatorUpdateData)(_buffer.Buffer.from(priceUpdateData, "base64"));
283
+ const { postInstructions: postEncodedVaaInstructions, encodedVaaAddress: encodedVaa, closeInstructions: postEncodedVaacloseInstructions } = await this.buildPostEncodedVaaInstructions(accumulatorUpdateData.vaa);
284
+ postInstructions.push(...postEncodedVaaInstructions);
285
+ closeInstructions.push(...postEncodedVaacloseInstructions);
286
+ for (const update of accumulatorUpdateData.updates){
287
+ const priceUpdateKeypair = new _web3.Keypair();
288
+ postInstructions.push({
289
+ instruction: await this.receiver.methods.postUpdate({
290
+ merklePriceUpdate: update,
291
+ treasuryId
292
+ }).accounts({
293
+ encodedVaa,
294
+ priceUpdateAccount: priceUpdateKeypair.publicKey,
295
+ treasury: (0, _address.getTreasuryPda)(treasuryId, this.receiver.programId),
296
+ config: (0, _address.getConfigPda)(this.receiver.programId)
297
+ }).instruction(),
298
+ signers: [
299
+ priceUpdateKeypair
300
+ ],
301
+ computeUnits: _compute_budget.POST_UPDATE_COMPUTE_BUDGET
302
+ });
303
+ priceFeedIdToPriceUpdateAccount["0x" + (0, _priceservicesdk.parsePriceFeedMessage)(update.message).feedId.toString("hex")] = priceUpdateKeypair.publicKey;
304
+ closeInstructions.push(await this.buildClosePriceUpdateInstruction(priceUpdateKeypair.publicKey));
305
+ }
306
+ }
307
+ return {
308
+ postInstructions,
309
+ priceFeedIdToPriceUpdateAccount,
310
+ closeInstructions
311
+ };
312
+ }
313
+ /**
314
+ * Build a series of helper instructions that update one or many price feed accounts and another series to close the encoded vaa accounts used to update the price feed accounts.
315
+ *
316
+ * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
317
+ * @param shardId the shard ID of the set of price feed accounts. This shard ID allows for multiple price feed accounts for the same price feed id to exist.
318
+ * @returns `postInstructions`: the instructions to update the price feed accounts. If the price feed accounts don't contain a recent update, these should be called before consuming the price updates.
319
+ * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update. Note that since price feed accounts are PDAs, the address of the account can also be found with `getPriceFeedAccountAddress`.
320
+ * @returns `closeInstructions`: the instructions to close the encoded VAA accounts that were used to update the price feed accounts.
321
+ */ async buildUpdatePriceFeedInstructions(priceUpdateDataArray, shardId) {
322
+ const postInstructions = [];
323
+ const priceFeedIdToPriceUpdateAccount = {};
324
+ const closeInstructions = [];
325
+ const treasuryId = this.treasuryId ?? (0, _address.getRandomTreasuryId)();
326
+ for (const priceUpdateData of priceUpdateDataArray){
327
+ const accumulatorUpdateData = (0, _priceservicesdk.parseAccumulatorUpdateData)(_buffer.Buffer.from(priceUpdateData, "base64"));
328
+ const { postInstructions: postEncodedVaaInstructions, encodedVaaAddress: encodedVaa, closeInstructions: postEncodedVaacloseInstructions } = await this.buildPostEncodedVaaInstructions(accumulatorUpdateData.vaa);
329
+ postInstructions.push(...postEncodedVaaInstructions);
330
+ closeInstructions.push(...postEncodedVaacloseInstructions);
331
+ for (const update of accumulatorUpdateData.updates){
332
+ const feedId = (0, _priceservicesdk.parsePriceFeedMessage)(update.message).feedId;
333
+ postInstructions.push({
334
+ instruction: await this.pushOracle.methods.updatePriceFeed({
335
+ merklePriceUpdate: update,
336
+ treasuryId
337
+ }, shardId, [
338
+ ...feedId
339
+ ]).accounts({
340
+ pythSolanaReceiver: this.receiver.programId,
341
+ encodedVaa,
342
+ priceFeedAccount: this.getPriceFeedAccountAddress(shardId, feedId),
343
+ treasury: (0, _address.getTreasuryPda)(treasuryId, this.receiver.programId),
344
+ config: (0, _address.getConfigPda)(this.receiver.programId)
345
+ }).instruction(),
346
+ signers: [],
347
+ computeUnits: _compute_budget.UPDATE_PRICE_FEED_COMPUTE_BUDGET
348
+ });
349
+ priceFeedIdToPriceUpdateAccount["0x" + (0, _priceservicesdk.parsePriceFeedMessage)(update.message).feedId.toString("hex")] = this.getPriceFeedAccountAddress(shardId, feedId);
350
+ }
351
+ }
352
+ return {
353
+ postInstructions,
354
+ priceFeedIdToPriceUpdateAccount,
355
+ closeInstructions
356
+ };
357
+ }
358
+ /**
359
+ * Build a series of helper instructions that post a VAA in an encoded VAA account. This function is bespoke for posting Pyth VAAs and might not work for other usecases.
360
+ *
361
+ * @param vaa a Wormhole VAA
362
+ * @returns `encodedVaaAddress`: the address of the encoded VAA account where the VAA will be posted
363
+ * @returns `postInstructions`: the instructions to post the VAA
364
+ * @returns `closeInstructions`: the instructions to close the encoded VAA account
365
+ */ async buildPostEncodedVaaInstructions(vaa) {
366
+ return (0, _vaa.buildPostEncodedVaaInstructions)(this.wormhole, vaa);
367
+ }
368
+ /**
369
+ * Build an instruction to close an encoded VAA account, recovering the rent.
370
+ */ async buildCloseEncodedVaaInstruction(encodedVaa) {
371
+ return (0, _vaa.buildCloseEncodedVaaInstruction)(this.wormhole, encodedVaa);
372
+ }
373
+ /**
374
+ * Build aset of instructions to close all the existing encoded VAA accounts owned by this PythSolanaReceiver's wallet
375
+ */ async buildClosePreviousEncodedVaasInstructions(maxInstructions) {
376
+ const encodedVaas = await this.findOwnedEncodedVaaAccounts();
377
+ const instructions = [];
378
+ for (const encodedVaa of encodedVaas){
379
+ instructions.push(await this.buildCloseEncodedVaaInstruction(encodedVaa));
380
+ }
381
+ return instructions.slice(0, maxInstructions);
382
+ }
383
+ /**
384
+ * Build an instruction to close a price update account, recovering the rent.
385
+ */ async buildClosePriceUpdateInstruction(priceUpdateAccount) {
386
+ const instruction = await this.receiver.methods.reclaimRent().accounts({
387
+ priceUpdateAccount
388
+ }).instruction();
389
+ return {
390
+ instruction,
391
+ signers: []
392
+ };
393
+ }
394
+ /**
395
+ * Returns a set of versioned transactions that contain the provided instructions in the same order and with efficient batching
396
+ */ async batchIntoVersionedTransactions(instructions, priorityFeeConfig, addressLookupTable) {
397
+ return _solanautils.TransactionBuilder.batchIntoVersionedTransactions(this.wallet.publicKey, this.connection, instructions, priorityFeeConfig, addressLookupTable);
398
+ }
399
+ /**
400
+ * Fetch the contents of a price update account
401
+ * @param priceUpdateAccount The address of the price update account
402
+ * @returns The contents of the deserialized price update account or `null` if the account doesn't exist
403
+ */ async fetchPriceUpdateAccount(priceUpdateAccount) {
404
+ return this.receiver.account.priceUpdateV2.fetchNullable(priceUpdateAccount);
405
+ }
406
+ /**
407
+ * Fetch the contents of a price feed account
408
+ * @param shardId The shard ID of the set of price feed accounts. This shard ID allows for multiple price feed accounts for the same price feed id to exist.
409
+ * @param priceFeedId The price feed ID, as either a 32-byte buffer or hexadecimal string with or without a leading "0x" prefix.
410
+ * @returns The contents of the deserialized price feed account or `null` if the account doesn't exist
411
+ */ async fetchPriceFeedAccount(shardId, priceFeedId) {
412
+ return this.receiver.account.priceUpdateV2.fetchNullable(this.getPriceFeedAccountAddress(shardId, priceFeedId));
413
+ }
414
+ /**
415
+ * Derive the address of a price feed account
416
+ * @param shardId The shard ID of the set of price feed accounts. This shard ID allows for multiple price feed accounts for the same price feed id to exist.
417
+ * @param priceFeedId The price feed ID, as either a 32-byte buffer or hexadecimal string with or without a leading "0x" prefix.
418
+ * @returns The address of the price feed account
419
+ */ getPriceFeedAccountAddress(shardId, priceFeedId) {
420
+ return getPriceFeedAccountForProgram(shardId, priceFeedId, this.pushOracle.programId);
421
+ }
422
+ /**
423
+ * Find all the encoded VAA accounts owned by this PythSolanaReceiver's wallet
424
+ * @returns a list of the public keys of the encoded VAA accounts
425
+ */ async findOwnedEncodedVaaAccounts() {
426
+ return await (0, _vaa.findEncodedVaaAccountsByWriteAuthority)(this.receiver.provider.connection, this.wallet.publicKey, this.wormhole.programId);
427
+ }
428
+ }
429
+ function getPriceFeedAccountForProgram(shardId, priceFeedId, pushOracleProgramId) {
430
+ if (typeof priceFeedId == "string") {
431
+ priceFeedId = priceFeedId.startsWith("0x") ? _buffer.Buffer.from(priceFeedId.slice(2), "hex") : _buffer.Buffer.from(priceFeedId, "hex");
432
+ }
433
+ if (priceFeedId.length != 32) {
434
+ throw new Error("Feed ID should be 32 bytes long");
435
+ }
436
+ const shardBuffer = _buffer.Buffer.alloc(2);
437
+ shardBuffer.writeUint16LE(shardId, 0);
438
+ return _web3.PublicKey.findProgramAddressSync([
439
+ shardBuffer,
440
+ priceFeedId
441
+ ], pushOracleProgramId ?? _address.DEFAULT_PUSH_ORACLE_PROGRAM_ID)[0];
442
+ }
@@ -1,13 +1,13 @@
1
- import { AnchorProvider, IdlAccounts, Program } from "@coral-xyz/anchor";
2
- import { AddressLookupTableAccount, Connection, Signer, Transaction, VersionedTransaction } from "@solana/web3.js";
3
- import { PythSolanaReceiver as PythSolanaReceiverProgram } from "./idl/pyth_solana_receiver";
4
- import { WormholeCoreBridgeSolana } from "./idl/wormhole_core_bridge_solana";
5
- import { PublicKey } from "@solana/web3.js";
6
- import { Wallet } from "@coral-xyz/anchor";
7
- import { TransactionBuilder, InstructionWithEphemeralSigners, PriorityFeeConfig } from "@pythnetwork/solana-utils";
8
- import { PythPushOracle } from "./idl/pyth_push_oracle";
1
+ import type { IdlAccounts } from "@coral-xyz/anchor";
2
+ import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor";
3
+ import type { InstructionWithEphemeralSigners, PriorityFeeConfig } from "@pythnetwork/solana-utils";
4
+ import { TransactionBuilder } from "@pythnetwork/solana-utils";
5
+ import type { Signer } from "@solana/web3.js";
6
+ import { AddressLookupTableAccount, Connection, Transaction, VersionedTransaction, PublicKey } from "@solana/web3.js";
7
+ import type { PythPushOracle } from "./idl/pyth_push_oracle";
8
+ import type { PythSolanaReceiver as PythSolanaReceiverProgram } from "./idl/pyth_solana_receiver";
9
+ import type { WormholeCoreBridgeSolana } from "./idl/wormhole_core_bridge_solana";
9
10
  export type PriceUpdateAccount = IdlAccounts<PythSolanaReceiverProgram>["priceUpdateV2"];
10
- export type TwapUpdateAccount = IdlAccounts<PythSolanaReceiverProgram>["twapUpdate"];
11
11
  /**
12
12
  * Configuration for the PythTransactionBuilder
13
13
  * @property closeUpdateAccounts (default: true) if true, the builder will add instructions to close the price update accounts and the encoded vaa accounts to recover the rent
@@ -54,7 +54,6 @@ export declare class PythTransactionBuilder extends TransactionBuilder {
54
54
  readonly pythSolanaReceiver: PythSolanaReceiver;
55
55
  readonly closeInstructions: InstructionWithEphemeralSigners[];
56
56
  readonly priceFeedIdToPriceUpdateAccount: Record<string, PublicKey>;
57
- readonly priceFeedIdToTwapUpdateAccount: Record<string, PublicKey>;
58
57
  readonly closeUpdateAccounts: boolean;
59
58
  constructor(pythSolanaReceiver: PythSolanaReceiver, config: PythTransactionBuilderConfig, addressLookupTable?: AddressLookupTableAccount);
60
59
  /**
@@ -103,27 +102,6 @@ export declare class PythTransactionBuilder extends TransactionBuilder {
103
102
  * ```
104
103
  */
105
104
  addPostPartiallyVerifiedPriceUpdates(priceUpdateDataArray: string[]): Promise<void>;
106
- /**
107
- * Add instructions to post TWAP updates to the builder.
108
- * Use this function to post fully verified TWAP updates from the present or from the past for your program to consume.
109
- *
110
- * @param twapUpdateDataArray the output of the `@pythnetwork/hermes-client`'s `getLatestTwaps`. This is an array of verifiable price updates.
111
- *
112
- * @example
113
- * ```typescript
114
- * // Get the price feed ids from https://pyth.network/developers/price-feed-ids#pyth-evm-stable
115
- * const twapUpdateData = await hermesClient.getLatestTwaps([
116
- * SOL_PRICE_FEED_ID,
117
- * ETH_PRICE_FEED_ID,
118
- * ]);
119
- *
120
- * const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({});
121
- * await transactionBuilder.addPostTwapUpdates(priceUpdateData);
122
- * console.log("The SOL/USD price update will get posted to:", transactionBuilder.getTwapUpdateAccount(SOL_PRICE_FEED_ID).toBase58())
123
- * await transactionBuilder.addTwapConsumerInstructions(...)
124
- * ```
125
- */
126
- addPostTwapUpdates(twapUpdateDataArray: string[]): Promise<void>;
127
105
  /**
128
106
  * Add instructions to update price feed accounts to the builder.
129
107
  * Price feed accounts are fixed accounts per price feed id that can only be updated with a more recent price.
@@ -177,37 +155,6 @@ export declare class PythTransactionBuilder extends TransactionBuilder {
177
155
  * ```
178
156
  */
179
157
  addPriceConsumerInstructions(getInstructions: (getPriceUpdateAccount: (priceFeedId: string) => PublicKey) => Promise<InstructionWithEphemeralSigners[]>): Promise<void>;
180
- /**
181
- * Add instructions that consume TWAP updates to the builder.
182
- *
183
- * @param getInstructions a function that given a mapping of price feed IDs to TWAP update accounts, generates a series of instructions. TWAP updates get posted to ephemeral accounts and this function allows the user to indicate which accounts in their instruction need to be "replaced" with each price update account.
184
- * If multiple TWAP updates for the same price feed ID are posted with the same builder, the account corresponding to the last update to get posted will be used.
185
- *
186
- * @example
187
- * ```typescript
188
- * ...
189
- * await transactionBuilder.addPostTwapUpdates(twapUpdateData);
190
- * await transactionBuilder.addTwapConsumerInstructions(
191
- * async (
192
- * getTwapUpdateAccount: ( priceFeedId: string) => PublicKey
193
- * ): Promise<InstructionWithEphemeralSigners[]> => {
194
- * return [
195
- * {
196
- * instruction: await myFirstPythApp.methods
197
- * .consume()
198
- * .accounts({
199
- * solTwapUpdate: getTwapUpdateAccount(SOL_PRICE_FEED_ID),
200
- * ethTwapUpdate: getTwapUpdateAccount(ETH_PRICE_FEED_ID),
201
- * })
202
- * .instruction(),
203
- * signers: [],
204
- * },
205
- * ];
206
- * }
207
- * );
208
- * ```
209
- */
210
- addTwapConsumerInstructions(getInstructions: (getTwapUpdateAccount: (priceFeedId: string) => PublicKey) => Promise<InstructionWithEphemeralSigners[]>): Promise<void>;
211
158
  /** Add instructions to close encoded VAA accounts from previous actions.
212
159
  * If you have previously used the PythTransactionBuilder with closeUpdateAccounts set to false or if you posted encoded VAAs but the transaction to close them did not land on-chain, your wallet might own many encoded VAA accounts.
213
160
  * The rent cost for these accounts is 0.008 SOL per encoded VAA account. You can recover this rent calling this function when building a set of transactions.
@@ -232,11 +179,6 @@ export declare class PythTransactionBuilder extends TransactionBuilder {
232
179
  * If multiple price updates for the same price feed ID will be posted with the same builder, the address of the account corresponding to the last update to get posted will be returned.
233
180
  * */
234
181
  getPriceUpdateAccount(priceFeedId: string): PublicKey;
235
- /**
236
- * This method is used to retrieve the address of the TWAP update account where the TWAP update for a given price feed ID will be posted.
237
- * If multiple updates for the same price feed ID will be posted with the same builder, the address of the account corresponding to the last update to get posted will be returned.
238
- * */
239
- getTwapUpdateAccount(priceFeedId: string): PublicKey;
240
182
  }
241
183
  /**
242
184
  * A class to interact with the Pyth Solana Receiver program.
@@ -252,7 +194,7 @@ export declare class PythSolanaReceiver {
252
194
  readonly receiver: Program<PythSolanaReceiverProgram>;
253
195
  readonly wormhole: Program<WormholeCoreBridgeSolana>;
254
196
  readonly pushOracle: Program<PythPushOracle>;
255
- readonly treasuryId?: number;
197
+ readonly treasuryId?: number | undefined;
256
198
  constructor({ connection, wallet, wormholeProgramId, receiverProgramId, pushOracleProgramId, treasuryId, }: {
257
199
  connection: Connection;
258
200
  wallet: Wallet;
@@ -294,19 +236,6 @@ export declare class PythSolanaReceiver {
294
236
  priceFeedIdToPriceUpdateAccount: Record<string, PublicKey>;
295
237
  closeInstructions: InstructionWithEphemeralSigners[];
296
238
  }>;
297
- /**
298
- * Build a series of helper instructions that post TWAP updates to the Pyth Solana Receiver program and another series to close the encoded vaa accounts and the TWAP update accounts.
299
- *
300
- * @param twapUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestTwaps`. This is an array of verifiable price updates.
301
- * @returns `postInstructions`: the instructions to post the TWAP updates, these should be called before consuming the price updates
302
- * @returns `priceFeedIdToTwapUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the TWAP update.
303
- * @returns `closeInstructions`: the instructions to close the TWAP update accounts, these should be called after consuming the TWAP updates
304
- */
305
- buildPostTwapUpdateInstructions(twapUpdateDataArray: string[]): Promise<{
306
- postInstructions: InstructionWithEphemeralSigners[];
307
- priceFeedIdToTwapUpdateAccount: Record<string, PublicKey>;
308
- closeInstructions: InstructionWithEphemeralSigners[];
309
- }>;
310
239
  /**
311
240
  * Build a series of helper instructions that update one or many price feed accounts and another series to close the encoded vaa accounts used to update the price feed accounts.
312
241
  *
@@ -346,10 +275,6 @@ export declare class PythSolanaReceiver {
346
275
  * Build an instruction to close a price update account, recovering the rent.
347
276
  */
348
277
  buildClosePriceUpdateInstruction(priceUpdateAccount: PublicKey): Promise<InstructionWithEphemeralSigners>;
349
- /**
350
- * Build an instruction to close a TWAP update account, recovering the rent.
351
- */
352
- buildCloseTwapUpdateInstruction(twapUpdateAccount: PublicKey): Promise<InstructionWithEphemeralSigners>;
353
278
  /**
354
279
  * Returns a set of versioned transactions that contain the provided instructions in the same order and with efficient batching
355
280
  */
@@ -391,4 +316,3 @@ export declare class PythSolanaReceiver {
391
316
  * @returns The address of the price feed account
392
317
  */
393
318
  export declare function getPriceFeedAccountForProgram(shardId: number, priceFeedId: Buffer | string, pushOracleProgramId?: PublicKey): PublicKey;
394
- //# sourceMappingURL=PythSolanaReceiver.d.ts.map