@atomiqlabs/chain-solana 13.5.13 → 13.5.14

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 (131) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +73 -73
  3. package/dist/index.d.ts +81 -81
  4. package/dist/index.js +102 -102
  5. package/dist/node/index.d.ts +9 -9
  6. package/dist/node/index.js +13 -13
  7. package/dist/solana/SolanaChainType.d.ts +15 -15
  8. package/dist/solana/SolanaChainType.js +2 -2
  9. package/dist/solana/SolanaChains.d.ts +12 -12
  10. package/dist/solana/SolanaChains.js +45 -45
  11. package/dist/solana/SolanaInitializer.d.ts +94 -94
  12. package/dist/solana/SolanaInitializer.js +174 -174
  13. package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +222 -222
  14. package/dist/solana/btcrelay/SolanaBtcRelay.js +455 -455
  15. package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +84 -84
  16. package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +70 -70
  17. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +92 -92
  18. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +109 -109
  19. package/dist/solana/btcrelay/program/programIdl.json +671 -671
  20. package/dist/solana/chain/SolanaAction.d.ts +26 -26
  21. package/dist/solana/chain/SolanaAction.js +87 -87
  22. package/dist/solana/chain/SolanaChainInterface.d.ts +224 -224
  23. package/dist/solana/chain/SolanaChainInterface.js +275 -275
  24. package/dist/solana/chain/SolanaModule.d.ts +14 -14
  25. package/dist/solana/chain/SolanaModule.js +13 -13
  26. package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
  27. package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
  28. package/dist/solana/chain/modules/SolanaBlocks.d.ts +32 -32
  29. package/dist/solana/chain/modules/SolanaBlocks.js +78 -78
  30. package/dist/solana/chain/modules/SolanaEvents.d.ts +68 -68
  31. package/dist/solana/chain/modules/SolanaEvents.js +238 -238
  32. package/dist/solana/chain/modules/SolanaFees.d.ts +189 -189
  33. package/dist/solana/chain/modules/SolanaFees.js +434 -434
  34. package/dist/solana/chain/modules/SolanaSignatures.d.ts +24 -24
  35. package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
  36. package/dist/solana/chain/modules/SolanaSlots.d.ts +33 -33
  37. package/dist/solana/chain/modules/SolanaSlots.js +72 -72
  38. package/dist/solana/chain/modules/SolanaTokens.d.ts +123 -123
  39. package/dist/solana/chain/modules/SolanaTokens.js +242 -242
  40. package/dist/solana/chain/modules/SolanaTransactions.d.ts +149 -149
  41. package/dist/solana/chain/modules/SolanaTransactions.js +445 -445
  42. package/dist/solana/connection/ConnectionWithRetries.d.ts +35 -35
  43. package/dist/solana/connection/ConnectionWithRetries.js +86 -71
  44. package/dist/solana/events/SolanaChainEvents.d.ts +45 -45
  45. package/dist/solana/events/SolanaChainEvents.js +108 -108
  46. package/dist/solana/events/SolanaChainEventsBrowser.d.ts +205 -205
  47. package/dist/solana/events/SolanaChainEventsBrowser.js +404 -404
  48. package/dist/solana/program/SolanaProgramBase.d.ts +73 -73
  49. package/dist/solana/program/SolanaProgramBase.js +54 -54
  50. package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
  51. package/dist/solana/program/SolanaProgramModule.js +11 -11
  52. package/dist/solana/program/modules/SolanaProgramEvents.d.ts +53 -53
  53. package/dist/solana/program/modules/SolanaProgramEvents.js +117 -117
  54. package/dist/solana/swaps/SolanaSwapData.d.ts +333 -333
  55. package/dist/solana/swaps/SolanaSwapData.js +535 -535
  56. package/dist/solana/swaps/SolanaSwapModule.d.ts +11 -11
  57. package/dist/solana/swaps/SolanaSwapModule.js +12 -12
  58. package/dist/solana/swaps/SolanaSwapProgram.d.ts +376 -376
  59. package/dist/solana/swaps/SolanaSwapProgram.js +769 -769
  60. package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
  61. package/dist/solana/swaps/SwapTypeEnum.js +43 -43
  62. package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +95 -95
  63. package/dist/solana/swaps/modules/SolanaDataAccount.js +232 -232
  64. package/dist/solana/swaps/modules/SolanaLpVault.d.ts +69 -69
  65. package/dist/solana/swaps/modules/SolanaLpVault.js +171 -171
  66. package/dist/solana/swaps/modules/SwapClaim.d.ts +126 -126
  67. package/dist/solana/swaps/modules/SwapClaim.js +294 -294
  68. package/dist/solana/swaps/modules/SwapInit.d.ts +213 -213
  69. package/dist/solana/swaps/modules/SwapInit.js +658 -658
  70. package/dist/solana/swaps/modules/SwapRefund.d.ts +87 -87
  71. package/dist/solana/swaps/modules/SwapRefund.js +293 -293
  72. package/dist/solana/swaps/programIdl.json +945 -945
  73. package/dist/solana/swaps/programTypes.d.ts +943 -943
  74. package/dist/solana/swaps/programTypes.js +945 -945
  75. package/dist/solana/swaps/v1/programIdl.json +945 -945
  76. package/dist/solana/swaps/v1/programTypes.d.ts +943 -943
  77. package/dist/solana/swaps/v1/programTypes.js +945 -945
  78. package/dist/solana/swaps/v2/programIdl.json +952 -952
  79. package/dist/solana/swaps/v2/programTypes.d.ts +950 -950
  80. package/dist/solana/swaps/v2/programTypes.js +952 -952
  81. package/dist/solana/wallet/SolanaKeypairWallet.d.ts +29 -29
  82. package/dist/solana/wallet/SolanaKeypairWallet.js +50 -50
  83. package/dist/solana/wallet/SolanaSigner.d.ts +30 -30
  84. package/dist/solana/wallet/SolanaSigner.js +30 -30
  85. package/dist/utils/Utils.d.ts +58 -58
  86. package/dist/utils/Utils.js +170 -170
  87. package/node/index.d.ts +1 -1
  88. package/node/index.js +3 -3
  89. package/package.json +46 -46
  90. package/src/index.ts +87 -87
  91. package/src/node/index.ts +9 -9
  92. package/src/solana/SolanaChainType.ts +32 -32
  93. package/src/solana/SolanaChains.ts +46 -46
  94. package/src/solana/SolanaInitializer.ts +278 -278
  95. package/src/solana/btcrelay/SolanaBtcRelay.ts +615 -615
  96. package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +116 -116
  97. package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +148 -148
  98. package/src/solana/btcrelay/program/programIdl.json +670 -670
  99. package/src/solana/chain/SolanaAction.ts +109 -109
  100. package/src/solana/chain/SolanaChainInterface.ts +404 -404
  101. package/src/solana/chain/SolanaModule.ts +20 -20
  102. package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
  103. package/src/solana/chain/modules/SolanaBlocks.ts +89 -89
  104. package/src/solana/chain/modules/SolanaEvents.ts +271 -271
  105. package/src/solana/chain/modules/SolanaFees.ts +522 -522
  106. package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
  107. package/src/solana/chain/modules/SolanaSlots.ts +85 -85
  108. package/src/solana/chain/modules/SolanaTokens.ts +300 -300
  109. package/src/solana/chain/modules/SolanaTransactions.ts +503 -503
  110. package/src/solana/connection/ConnectionWithRetries.ts +113 -96
  111. package/src/solana/events/SolanaChainEvents.ts +127 -127
  112. package/src/solana/events/SolanaChainEventsBrowser.ts +495 -495
  113. package/src/solana/program/SolanaProgramBase.ts +119 -119
  114. package/src/solana/program/SolanaProgramModule.ts +15 -15
  115. package/src/solana/program/modules/SolanaProgramEvents.ts +157 -157
  116. package/src/solana/swaps/SolanaSwapData.ts +735 -735
  117. package/src/solana/swaps/SolanaSwapModule.ts +19 -19
  118. package/src/solana/swaps/SolanaSwapProgram.ts +1074 -1074
  119. package/src/solana/swaps/SwapTypeEnum.ts +30 -30
  120. package/src/solana/swaps/modules/SolanaDataAccount.ts +302 -302
  121. package/src/solana/swaps/modules/SolanaLpVault.ts +208 -208
  122. package/src/solana/swaps/modules/SwapClaim.ts +387 -387
  123. package/src/solana/swaps/modules/SwapInit.ts +785 -785
  124. package/src/solana/swaps/modules/SwapRefund.ts +353 -353
  125. package/src/solana/swaps/v1/programIdl.json +944 -944
  126. package/src/solana/swaps/v1/programTypes.ts +1885 -1885
  127. package/src/solana/swaps/v2/programIdl.json +951 -951
  128. package/src/solana/swaps/v2/programTypes.ts +1899 -1899
  129. package/src/solana/wallet/SolanaKeypairWallet.ts +56 -56
  130. package/src/solana/wallet/SolanaSigner.ts +43 -43
  131. package/src/utils/Utils.ts +194 -194
@@ -1,434 +1,434 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SolanaFees = void 0;
4
- const web3_js_1 = require("@solana/web3.js");
5
- const Utils_1 = require("../../../utils/Utils");
6
- const MAX_FEE_AGE = 5000;
7
- /**
8
- * Fee estimation service for the Solana network. Uses client-side fee estimation algorithm by default, which
9
- * fetches a bunch (default 8) random blocks in the past period (default 150) and computes the average fee. It
10
- * automatically detects whether the underlying RPC endpoint is a Helius one which features the `getPriorityFeeEstimate`
11
- * endpoint, and if available uses that one.
12
- *
13
- * @category Chain Interface
14
- */
15
- class SolanaFees {
16
- /**
17
- * @param connection Underlying Solana network connection to use for read access to Solana
18
- * @param maxFeeMicroLamports Maximum allowed fee in microLamports/CU (1/1,000,000 of a lamport per compute unit)
19
- * @param numSamples Number of samples to use when estimating the global fee on the client-side, this many blocks are
20
- * sampled from the last `period` blocks to estimate an average fee rate
21
- * @param period Period of past blocks to sample random blocks from when estimating the global fee on the client-side
22
- * @param useHeliusApi Whether to use the helius API or not, default to `"auto"`, which automatically detects if the
23
- * underlying RPC supports Helius's `getPriorityFeeEstimate` RPC call
24
- * @param heliusFeeLevel Fee level to use when fetching the fee rate from Helius's `getPriorityFeeEstimate` RPC endpoint,
25
- * for the meaning of the different levels refer to https://www.helius.dev/docs/priority-fee-api#priority-levels-explained
26
- * @param getStaticFee Optional function for adding a base fee to transactions (this function returns the base fee
27
- * in lamports to be added to the transaction) - this fee doesn't scale with CUs of the transaction and is instead
28
- * applied as-is
29
- * @param bribeData Bribe fee configuration (used for e.g. Jito tips)
30
- */
31
- constructor(connection, maxFeeMicroLamports = 250000, numSamples = 8, period = 150, useHeliusApi = "auto", heliusFeeLevel = "veryHigh", getStaticFee, bribeData) {
32
- this.heliusApiSupported = true;
33
- this.logger = (0, Utils_1.getLogger)("SolanaFees: ");
34
- this.connection = connection;
35
- this.maxFeeMicroLamports = BigInt(maxFeeMicroLamports);
36
- this.numSamples = numSamples;
37
- this.period = period;
38
- this.useHeliusApi = useHeliusApi;
39
- this.heliusFeeLevel = heliusFeeLevel;
40
- this.bribeData = bribeData;
41
- this.getStaticFee = getStaticFee;
42
- }
43
- /**
44
- * Returns solana block with transactionDetails="signatures"
45
- *
46
- * @param slot
47
- * @private
48
- */
49
- async getBlockWithSignature(slot) {
50
- const response = await this.connection._rpcRequest("getBlock", [
51
- slot,
52
- {
53
- encoding: "json",
54
- transactionDetails: "signatures",
55
- commitment: "confirmed",
56
- rewards: true
57
- }
58
- ]);
59
- if (response.error != null) {
60
- if (response.error.code === -32004 || response.error.code === -32007 || response.error.code === -32009 || response.error.code === -32014) {
61
- return null;
62
- }
63
- throw new Error(response.error.message);
64
- }
65
- return response.result;
66
- }
67
- /**
68
- * Returns fee estimate from Helius API - only works with Helius RPC, return null for all other RPC providers
69
- *
70
- * @param mutableAccounts
71
- * @private
72
- */
73
- async getPriorityFeeEstimate(mutableAccounts) {
74
- //Try to use getPriorityFeeEstimate api of Helius
75
- const response = await this.connection._rpcRequest("getPriorityFeeEstimate", [
76
- {
77
- "accountKeys": mutableAccounts.map(e => e.toBase58()),
78
- "options": {
79
- "includeAllPriorityFeeLevels": true
80
- }
81
- }
82
- ]).catch((e) => {
83
- //Catching not supported errors
84
- if (e.message != null && (e.message.includes("-32601") || e.message.includes("-32600"))) {
85
- return {
86
- error: {
87
- code: -32601,
88
- message: e.message
89
- }
90
- };
91
- }
92
- throw e;
93
- });
94
- if (response.error != null) {
95
- //Catching not supported errors
96
- if (response.error.code !== -32601 && response.error.code !== -32600)
97
- throw new Error(response.error.message);
98
- return null;
99
- }
100
- return response.result.priorityFeeLevels;
101
- }
102
- /**
103
- * Sends the transaction over Jito
104
- *
105
- * @param tx
106
- * @param options
107
- * @private
108
- * @returns {Promise<string>} transaction signature
109
- */
110
- async sendJitoTx(tx, options) {
111
- if (this.bribeData?.endpoint == null)
112
- throw new Error("Jito endpoint not specified!");
113
- if (options == null)
114
- options = {};
115
- const request = await fetch(this.bribeData.endpoint, {
116
- method: "POST",
117
- body: JSON.stringify({
118
- jsonrpc: "2.0",
119
- id: 1,
120
- method: "sendTransaction",
121
- params: [tx.toString("base64"), {
122
- ...options,
123
- encoding: "base64"
124
- }],
125
- }),
126
- headers: {
127
- "Content-Type": "application/json"
128
- }
129
- });
130
- if (request.ok) {
131
- const parsedResponse = await request.json();
132
- return parsedResponse.result;
133
- }
134
- throw new Error(await request.text());
135
- }
136
- /**
137
- * Checks whether the transaction should be sent over Jito, returns the fee paid to Jito in case the transaction
138
- * should be sent over Jito, returns null if the transaction shouldn't be sent over Jito
139
- *
140
- * @param parsedTx
141
- * @private
142
- */
143
- getJitoTxFee(parsedTx) {
144
- const lastIx = parsedTx.instructions[parsedTx.instructions.length - 1];
145
- if (!lastIx.programId.equals(web3_js_1.SystemProgram.programId))
146
- return null;
147
- if (web3_js_1.SystemInstruction.decodeInstructionType(lastIx) !== "Transfer")
148
- return null;
149
- const decodedIxData = web3_js_1.SystemInstruction.decodeTransfer(lastIx);
150
- if (decodedIxData.toPubkey.toBase58() !== this.bribeData?.address)
151
- return null;
152
- return decodedIxData.lamports;
153
- }
154
- /**
155
- * Gets the mean microLamports/CU fee rate for the block at a specific slot
156
- *
157
- * @param slot
158
- * @private
159
- */
160
- async getBlockMeanFeeRate(slot) {
161
- const block = await this.getBlockWithSignature(slot);
162
- if (block == null || block.rewards == null)
163
- return null;
164
- const blockComission = block.rewards.find(e => e.rewardType === "Fee");
165
- if (blockComission == null)
166
- return null;
167
- const totalBlockFees = BigInt(blockComission.lamports) * 2n;
168
- //Subtract per-signature fees to get pure compute fees
169
- const totalTransactionBaseFees = BigInt(block.signatures.length) * 5000n;
170
- const computeFees = totalBlockFees - totalTransactionBaseFees;
171
- //Total compute fees in micro lamports
172
- const computeFeesMicroLamports = computeFees * 1000000n;
173
- //micro lamports per CU considering block was full (48M compute units)
174
- const perCUMicroLamports = computeFeesMicroLamports / 48000000n;
175
- this.logger.debug("getBlockMeanFeeRate(): slot: " + slot + " total reward: " + totalBlockFees.toString(10) +
176
- " total transactions: " + block.signatures.length + " computed fee: " + perCUMicroLamports);
177
- return perCUMicroLamports;
178
- }
179
- /**
180
- * Manually gets global fee rate by sampling random blocks over the last period
181
- *
182
- * @private
183
- * @returns {Promise<BN>} sampled mean microLamports/CU fee over the last period
184
- */
185
- async _getGlobalFeeRate() {
186
- let slot = await this.connection.getSlot();
187
- const slots = [];
188
- for (let i = 0; i < this.period; i++) {
189
- slots.push(slot - i);
190
- }
191
- const promises = [];
192
- for (let i = 0; i < this.numSamples; i++) {
193
- promises.push((async () => {
194
- let feeRate = null;
195
- while (feeRate == null) {
196
- if (slots.length === 0)
197
- throw new Error("Ran out of slots to check!");
198
- const index = Math.floor(Math.random() * slots.length);
199
- const slotNumber = slots[index];
200
- slots.splice(index, 1);
201
- feeRate = await this.getBlockMeanFeeRate(slotNumber);
202
- }
203
- return feeRate;
204
- })());
205
- }
206
- const meanFees = await Promise.all(promises);
207
- const min = meanFees.reduce((prev, current) => prev == null || prev > current ? current : prev, null);
208
- if (min == null)
209
- throw new Error("Cannot estimate fee, meanFees length is 0");
210
- this.logger.debug("_getGlobalFeeRate(): slot: " + slot + " global fee minimum: " + min.toString(10));
211
- return min;
212
- }
213
- /**
214
- * Gets the combined microLamports/CU fee rate (from localized & global fee market)
215
- *
216
- * @param mutableAccounts
217
- * @private
218
- */
219
- async _getFeeRate(mutableAccounts) {
220
- if (this.useHeliusApi === "yes" || (this.useHeliusApi === "auto" && this.heliusApiSupported)) {
221
- //Try to use getPriorityFeeEstimate api of Helius
222
- const fees = await this.getPriorityFeeEstimate(mutableAccounts);
223
- if (fees != null) {
224
- let calculatedFee = BigInt(fees[this.heliusFeeLevel]);
225
- if (calculatedFee < 8000n)
226
- calculatedFee = 8000n;
227
- if (calculatedFee > this.maxFeeMicroLamports)
228
- calculatedFee = this.maxFeeMicroLamports;
229
- return calculatedFee;
230
- }
231
- this.logger.warn("_getFeeRate(): tried fetching fees from Helius API, not supported," +
232
- " falling back to client-side fee estimation");
233
- this.heliusApiSupported = false;
234
- }
235
- const [globalFeeRate, localFeeRate] = await Promise.all([
236
- this.getGlobalFeeRate(),
237
- this.connection.getRecentPrioritizationFees({
238
- lockedWritableAccounts: mutableAccounts
239
- }).then(resp => {
240
- let lamports = 0;
241
- for (let i = 20; i >= 0; i--) {
242
- const data = resp[resp.length - i - 1];
243
- if (data != null)
244
- lamports = Math.min(lamports, data.prioritizationFee);
245
- }
246
- return BigInt(lamports);
247
- })
248
- ]);
249
- let fee = globalFeeRate;
250
- if (fee < localFeeRate)
251
- fee = localFeeRate;
252
- if (fee < 8000n)
253
- fee = 8000n;
254
- if (fee > this.maxFeeMicroLamports)
255
- fee = this.maxFeeMicroLamports;
256
- return fee;
257
- }
258
- /**
259
- * Gets global fee rate, with caching
260
- *
261
- * @returns {Promise<BN>} global fee rate microLamports/CU
262
- */
263
- getGlobalFeeRate() {
264
- if (this.blockFeeCache == null || Date.now() - this.blockFeeCache.timestamp > MAX_FEE_AGE) {
265
- let obj;
266
- this.blockFeeCache = obj = {
267
- timestamp: Date.now(),
268
- feeRate: this._getGlobalFeeRate().catch(e => {
269
- if (this.blockFeeCache === obj)
270
- delete this.blockFeeCache;
271
- throw e;
272
- })
273
- };
274
- }
275
- return this.blockFeeCache.feeRate;
276
- }
277
- /**
278
- * Gets the combined microLamports/CU fee rate (from localized & global fee market), cached & adjusted as for
279
- * when bribe and/or static fee should be included, format: <uLamports/CU>;<static fee lamport>[;<bribe address>]
280
- *
281
- * @param mutableAccounts
282
- * @private
283
- */
284
- async getFeeRate(mutableAccounts) {
285
- let feeMicroLamportPerCU = await this._getFeeRate(mutableAccounts);
286
- if (this.bribeData?.getBribeFee != null)
287
- feeMicroLamportPerCU = this.bribeData.getBribeFee(feeMicroLamportPerCU);
288
- let fee = feeMicroLamportPerCU.toString(10);
289
- if (this.getStaticFee != null) {
290
- fee += ";" + this.getStaticFee(feeMicroLamportPerCU);
291
- }
292
- else {
293
- fee += ";0";
294
- }
295
- if (this.bribeData?.address) {
296
- fee += ";" + this.bribeData.address;
297
- }
298
- this.logger.debug("getFeeRate(): calculated fee: " + fee);
299
- return fee;
300
- }
301
- /**
302
- * Calculates the total priority fee paid for a given compute budget at a given fee rate
303
- *
304
- * @param computeUnits
305
- * @param feeRate
306
- * @param includeStaticFee whether the include the static/base part of the fee rate
307
- */
308
- getPriorityFee(computeUnits, feeRate, includeStaticFee = true) {
309
- if (feeRate == null)
310
- return 0n;
311
- const hashArr = feeRate.split("#");
312
- if (hashArr.length > 1) {
313
- feeRate = hashArr[0];
314
- }
315
- const arr = feeRate.split(";");
316
- const cuPrice = BigInt(arr[0]);
317
- const staticFee = includeStaticFee ? BigInt(arr[1]) : 0n;
318
- return staticFee + (cuPrice * BigInt(computeUnits) / 1000000n);
319
- }
320
- /**
321
- * Applies fee rate to a transaction, should be called before adding instructions to the transaction, specifically
322
- * it adds the setComputeUnitLimit & setComputeUnitPrice instruction.
323
- *
324
- * @example
325
- * ```typescript
326
- * const feeRate = solanaFees.getFeeRate([...writeableAccounts]);
327
- * const tx = new Transaction();
328
- * //Apply the fee rate part at the beginning of the transaction (specifically setComputeUnitLimit & setComputeUnitPrice)
329
- * SolanaFees.applyFeeRateBegin(tx, feeRate);
330
- * //Add instructions here
331
- * tx.add(instruction1);
332
- * tx.add(instruction2);
333
- * //Set the fee payer
334
- * tx.feePayer = feePayerPublicKey;
335
- * //Apply the fee rate part at the end of the transaction (specifically the transfer to the bribe account, e.g. Jito tip)
336
- * SolanaFees.applyFeeRateEnd(tx, feeRate);
337
- * ```
338
- *
339
- * @param tx
340
- * @param computeBudget
341
- * @param feeRate
342
- */
343
- static applyFeeRateBegin(tx, computeBudget, feeRate) {
344
- if (feeRate == null)
345
- return;
346
- const hashArr = feeRate.split("#");
347
- if (hashArr.length > 1) {
348
- feeRate = hashArr[0];
349
- }
350
- if (computeBudget != null && computeBudget > 0)
351
- tx.add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({
352
- units: computeBudget,
353
- }));
354
- //Check if bribe is included
355
- const arr = feeRate.split(";");
356
- if (arr.length > 2) {
357
- }
358
- else {
359
- let fee = BigInt(arr[0]);
360
- if (arr.length > 1) {
361
- const staticFee = BigInt(arr[1]);
362
- const cuBigInt = BigInt(computeBudget || (200000 * Utils_1.SolanaTxUtils.getNonComputeBudgetIxs(tx)));
363
- const staticFeePerCU = staticFee * BigInt(1000000) / cuBigInt;
364
- fee += staticFeePerCU;
365
- }
366
- tx.add(web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({
367
- microLamports: fee
368
- }));
369
- }
370
- }
371
- /**
372
- * Applies fee rate to a transaction, should be called after adding instructions to the transaction, specifically
373
- * it adds the adds the bribe SystemProgram.transfer instruction.
374
- *
375
- * @example
376
- * ```typescript
377
- * const feeRate = solanaFees.getFeeRate([...writeableAccounts]);
378
- * const tx = new Transaction();
379
- * //Apply the fee rate part at the beginning of the transaction (specifically setComputeUnitLimit & setComputeUnitPrice)
380
- * SolanaFees.applyFeeRateBegin(tx, feeRate);
381
- * //Add instructions here
382
- * tx.add(instruction1);
383
- * tx.add(instruction2);
384
- * //Set the fee payer
385
- * tx.feePayer = feePayerPublicKey;
386
- * //Apply the fee rate part at the end of the transaction (specifically the transfer to the bribe account, e.g. Jito tip)
387
- * SolanaFees.applyFeeRateEnd(tx, feeRate);
388
- * ```
389
- *
390
- * @param tx
391
- * @param computeBudget
392
- * @param feeRate
393
- */
394
- static applyFeeRateEnd(tx, computeBudget, feeRate) {
395
- if (feeRate == null)
396
- return;
397
- const hashArr = feeRate.split("#");
398
- if (hashArr.length > 1) {
399
- feeRate = hashArr[0];
400
- }
401
- //Check if bribe is included
402
- const arr = feeRate.split(";");
403
- if (arr.length > 2) {
404
- const cuBigInt = BigInt(computeBudget ?? (200000 * (Utils_1.SolanaTxUtils.getNonComputeBudgetIxs(tx) + 1)));
405
- const cuPrice = BigInt(arr[0]);
406
- const staticFee = BigInt(arr[1]);
407
- const bribeAddress = new web3_js_1.PublicKey(arr[2]);
408
- if (tx.feePayer == null)
409
- throw new Error("Cannot apply tx bribe without feePayer being known!");
410
- tx.add(web3_js_1.SystemProgram.transfer({
411
- fromPubkey: tx.feePayer,
412
- toPubkey: bribeAddress,
413
- lamports: staticFee + (cuBigInt * cuPrice / BigInt(1000000))
414
- }));
415
- return;
416
- }
417
- }
418
- /**
419
- * Checks if the transaction should be submitted over Jito and if yes submits it
420
- *
421
- * @param tx Raw signed transaction to be attempted to be sent over Jito
422
- * @param options Send options for the sendTransaction RPC call
423
- * @returns {Promise<string | null>} null if the transaction was not sent over Jito, tx signature when tx was sent over Jito
424
- */
425
- submitTx(tx, options) {
426
- const parsedTx = web3_js_1.Transaction.from(tx);
427
- const jitoFee = this.getJitoTxFee(parsedTx);
428
- if (jitoFee == null)
429
- return Promise.resolve(null);
430
- this.logger.info("submitTx(): sending tx over Jito, signature: " + parsedTx.signature + " fee: " + jitoFee.toString(10));
431
- return this.sendJitoTx(tx, options);
432
- }
433
- }
434
- exports.SolanaFees = SolanaFees;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SolanaFees = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const Utils_1 = require("../../../utils/Utils");
6
+ const MAX_FEE_AGE = 5000;
7
+ /**
8
+ * Fee estimation service for the Solana network. Uses client-side fee estimation algorithm by default, which
9
+ * fetches a bunch (default 8) random blocks in the past period (default 150) and computes the average fee. It
10
+ * automatically detects whether the underlying RPC endpoint is a Helius one which features the `getPriorityFeeEstimate`
11
+ * endpoint, and if available uses that one.
12
+ *
13
+ * @category Chain Interface
14
+ */
15
+ class SolanaFees {
16
+ /**
17
+ * @param connection Underlying Solana network connection to use for read access to Solana
18
+ * @param maxFeeMicroLamports Maximum allowed fee in microLamports/CU (1/1,000,000 of a lamport per compute unit)
19
+ * @param numSamples Number of samples to use when estimating the global fee on the client-side, this many blocks are
20
+ * sampled from the last `period` blocks to estimate an average fee rate
21
+ * @param period Period of past blocks to sample random blocks from when estimating the global fee on the client-side
22
+ * @param useHeliusApi Whether to use the helius API or not, default to `"auto"`, which automatically detects if the
23
+ * underlying RPC supports Helius's `getPriorityFeeEstimate` RPC call
24
+ * @param heliusFeeLevel Fee level to use when fetching the fee rate from Helius's `getPriorityFeeEstimate` RPC endpoint,
25
+ * for the meaning of the different levels refer to https://www.helius.dev/docs/priority-fee-api#priority-levels-explained
26
+ * @param getStaticFee Optional function for adding a base fee to transactions (this function returns the base fee
27
+ * in lamports to be added to the transaction) - this fee doesn't scale with CUs of the transaction and is instead
28
+ * applied as-is
29
+ * @param bribeData Bribe fee configuration (used for e.g. Jito tips)
30
+ */
31
+ constructor(connection, maxFeeMicroLamports = 250000, numSamples = 8, period = 150, useHeliusApi = "auto", heliusFeeLevel = "veryHigh", getStaticFee, bribeData) {
32
+ this.heliusApiSupported = true;
33
+ this.logger = (0, Utils_1.getLogger)("SolanaFees: ");
34
+ this.connection = connection;
35
+ this.maxFeeMicroLamports = BigInt(maxFeeMicroLamports);
36
+ this.numSamples = numSamples;
37
+ this.period = period;
38
+ this.useHeliusApi = useHeliusApi;
39
+ this.heliusFeeLevel = heliusFeeLevel;
40
+ this.bribeData = bribeData;
41
+ this.getStaticFee = getStaticFee;
42
+ }
43
+ /**
44
+ * Returns solana block with transactionDetails="signatures"
45
+ *
46
+ * @param slot
47
+ * @private
48
+ */
49
+ async getBlockWithSignature(slot) {
50
+ const response = await this.connection._rpcRequest("getBlock", [
51
+ slot,
52
+ {
53
+ encoding: "json",
54
+ transactionDetails: "signatures",
55
+ commitment: "confirmed",
56
+ rewards: true
57
+ }
58
+ ]);
59
+ if (response.error != null) {
60
+ if (response.error.code === -32004 || response.error.code === -32007 || response.error.code === -32009 || response.error.code === -32014) {
61
+ return null;
62
+ }
63
+ throw new Error(response.error.message);
64
+ }
65
+ return response.result;
66
+ }
67
+ /**
68
+ * Returns fee estimate from Helius API - only works with Helius RPC, return null for all other RPC providers
69
+ *
70
+ * @param mutableAccounts
71
+ * @private
72
+ */
73
+ async getPriorityFeeEstimate(mutableAccounts) {
74
+ //Try to use getPriorityFeeEstimate api of Helius
75
+ const response = await this.connection._rpcRequest("getPriorityFeeEstimate", [
76
+ {
77
+ "accountKeys": mutableAccounts.map(e => e.toBase58()),
78
+ "options": {
79
+ "includeAllPriorityFeeLevels": true
80
+ }
81
+ }
82
+ ]).catch((e) => {
83
+ //Catching not supported errors
84
+ if (e.message != null && (e.message.includes("-32601") || e.message.includes("-32600"))) {
85
+ return {
86
+ error: {
87
+ code: -32601,
88
+ message: e.message
89
+ }
90
+ };
91
+ }
92
+ throw e;
93
+ });
94
+ if (response.error != null) {
95
+ //Catching not supported errors
96
+ if (response.error.code !== -32601 && response.error.code !== -32600)
97
+ throw new Error(response.error.message);
98
+ return null;
99
+ }
100
+ return response.result.priorityFeeLevels;
101
+ }
102
+ /**
103
+ * Sends the transaction over Jito
104
+ *
105
+ * @param tx
106
+ * @param options
107
+ * @private
108
+ * @returns {Promise<string>} transaction signature
109
+ */
110
+ async sendJitoTx(tx, options) {
111
+ if (this.bribeData?.endpoint == null)
112
+ throw new Error("Jito endpoint not specified!");
113
+ if (options == null)
114
+ options = {};
115
+ const request = await fetch(this.bribeData.endpoint, {
116
+ method: "POST",
117
+ body: JSON.stringify({
118
+ jsonrpc: "2.0",
119
+ id: 1,
120
+ method: "sendTransaction",
121
+ params: [tx.toString("base64"), {
122
+ ...options,
123
+ encoding: "base64"
124
+ }],
125
+ }),
126
+ headers: {
127
+ "Content-Type": "application/json"
128
+ }
129
+ });
130
+ if (request.ok) {
131
+ const parsedResponse = await request.json();
132
+ return parsedResponse.result;
133
+ }
134
+ throw new Error(await request.text());
135
+ }
136
+ /**
137
+ * Checks whether the transaction should be sent over Jito, returns the fee paid to Jito in case the transaction
138
+ * should be sent over Jito, returns null if the transaction shouldn't be sent over Jito
139
+ *
140
+ * @param parsedTx
141
+ * @private
142
+ */
143
+ getJitoTxFee(parsedTx) {
144
+ const lastIx = parsedTx.instructions[parsedTx.instructions.length - 1];
145
+ if (!lastIx.programId.equals(web3_js_1.SystemProgram.programId))
146
+ return null;
147
+ if (web3_js_1.SystemInstruction.decodeInstructionType(lastIx) !== "Transfer")
148
+ return null;
149
+ const decodedIxData = web3_js_1.SystemInstruction.decodeTransfer(lastIx);
150
+ if (decodedIxData.toPubkey.toBase58() !== this.bribeData?.address)
151
+ return null;
152
+ return decodedIxData.lamports;
153
+ }
154
+ /**
155
+ * Gets the mean microLamports/CU fee rate for the block at a specific slot
156
+ *
157
+ * @param slot
158
+ * @private
159
+ */
160
+ async getBlockMeanFeeRate(slot) {
161
+ const block = await this.getBlockWithSignature(slot);
162
+ if (block == null || block.rewards == null)
163
+ return null;
164
+ const blockComission = block.rewards.find(e => e.rewardType === "Fee");
165
+ if (blockComission == null)
166
+ return null;
167
+ const totalBlockFees = BigInt(blockComission.lamports) * 2n;
168
+ //Subtract per-signature fees to get pure compute fees
169
+ const totalTransactionBaseFees = BigInt(block.signatures.length) * 5000n;
170
+ const computeFees = totalBlockFees - totalTransactionBaseFees;
171
+ //Total compute fees in micro lamports
172
+ const computeFeesMicroLamports = computeFees * 1000000n;
173
+ //micro lamports per CU considering block was full (48M compute units)
174
+ const perCUMicroLamports = computeFeesMicroLamports / 48000000n;
175
+ this.logger.debug("getBlockMeanFeeRate(): slot: " + slot + " total reward: " + totalBlockFees.toString(10) +
176
+ " total transactions: " + block.signatures.length + " computed fee: " + perCUMicroLamports);
177
+ return perCUMicroLamports;
178
+ }
179
+ /**
180
+ * Manually gets global fee rate by sampling random blocks over the last period
181
+ *
182
+ * @private
183
+ * @returns {Promise<BN>} sampled mean microLamports/CU fee over the last period
184
+ */
185
+ async _getGlobalFeeRate() {
186
+ let slot = await this.connection.getSlot();
187
+ const slots = [];
188
+ for (let i = 0; i < this.period; i++) {
189
+ slots.push(slot - i);
190
+ }
191
+ const promises = [];
192
+ for (let i = 0; i < this.numSamples; i++) {
193
+ promises.push((async () => {
194
+ let feeRate = null;
195
+ while (feeRate == null) {
196
+ if (slots.length === 0)
197
+ throw new Error("Ran out of slots to check!");
198
+ const index = Math.floor(Math.random() * slots.length);
199
+ const slotNumber = slots[index];
200
+ slots.splice(index, 1);
201
+ feeRate = await this.getBlockMeanFeeRate(slotNumber);
202
+ }
203
+ return feeRate;
204
+ })());
205
+ }
206
+ const meanFees = await Promise.all(promises);
207
+ const min = meanFees.reduce((prev, current) => prev == null || prev > current ? current : prev, null);
208
+ if (min == null)
209
+ throw new Error("Cannot estimate fee, meanFees length is 0");
210
+ this.logger.debug("_getGlobalFeeRate(): slot: " + slot + " global fee minimum: " + min.toString(10));
211
+ return min;
212
+ }
213
+ /**
214
+ * Gets the combined microLamports/CU fee rate (from localized & global fee market)
215
+ *
216
+ * @param mutableAccounts
217
+ * @private
218
+ */
219
+ async _getFeeRate(mutableAccounts) {
220
+ if (this.useHeliusApi === "yes" || (this.useHeliusApi === "auto" && this.heliusApiSupported)) {
221
+ //Try to use getPriorityFeeEstimate api of Helius
222
+ const fees = await this.getPriorityFeeEstimate(mutableAccounts);
223
+ if (fees != null) {
224
+ let calculatedFee = BigInt(fees[this.heliusFeeLevel]);
225
+ if (calculatedFee < 8000n)
226
+ calculatedFee = 8000n;
227
+ if (calculatedFee > this.maxFeeMicroLamports)
228
+ calculatedFee = this.maxFeeMicroLamports;
229
+ return calculatedFee;
230
+ }
231
+ this.logger.warn("_getFeeRate(): tried fetching fees from Helius API, not supported," +
232
+ " falling back to client-side fee estimation");
233
+ this.heliusApiSupported = false;
234
+ }
235
+ const [globalFeeRate, localFeeRate] = await Promise.all([
236
+ this.getGlobalFeeRate(),
237
+ this.connection.getRecentPrioritizationFees({
238
+ lockedWritableAccounts: mutableAccounts
239
+ }).then(resp => {
240
+ let lamports = 0;
241
+ for (let i = 20; i >= 0; i--) {
242
+ const data = resp[resp.length - i - 1];
243
+ if (data != null)
244
+ lamports = Math.min(lamports, data.prioritizationFee);
245
+ }
246
+ return BigInt(lamports);
247
+ })
248
+ ]);
249
+ let fee = globalFeeRate;
250
+ if (fee < localFeeRate)
251
+ fee = localFeeRate;
252
+ if (fee < 8000n)
253
+ fee = 8000n;
254
+ if (fee > this.maxFeeMicroLamports)
255
+ fee = this.maxFeeMicroLamports;
256
+ return fee;
257
+ }
258
+ /**
259
+ * Gets global fee rate, with caching
260
+ *
261
+ * @returns {Promise<BN>} global fee rate microLamports/CU
262
+ */
263
+ getGlobalFeeRate() {
264
+ if (this.blockFeeCache == null || Date.now() - this.blockFeeCache.timestamp > MAX_FEE_AGE) {
265
+ let obj;
266
+ this.blockFeeCache = obj = {
267
+ timestamp: Date.now(),
268
+ feeRate: this._getGlobalFeeRate().catch(e => {
269
+ if (this.blockFeeCache === obj)
270
+ delete this.blockFeeCache;
271
+ throw e;
272
+ })
273
+ };
274
+ }
275
+ return this.blockFeeCache.feeRate;
276
+ }
277
+ /**
278
+ * Gets the combined microLamports/CU fee rate (from localized & global fee market), cached & adjusted as for
279
+ * when bribe and/or static fee should be included, format: <uLamports/CU>;<static fee lamport>[;<bribe address>]
280
+ *
281
+ * @param mutableAccounts
282
+ * @private
283
+ */
284
+ async getFeeRate(mutableAccounts) {
285
+ let feeMicroLamportPerCU = await this._getFeeRate(mutableAccounts);
286
+ if (this.bribeData?.getBribeFee != null)
287
+ feeMicroLamportPerCU = this.bribeData.getBribeFee(feeMicroLamportPerCU);
288
+ let fee = feeMicroLamportPerCU.toString(10);
289
+ if (this.getStaticFee != null) {
290
+ fee += ";" + this.getStaticFee(feeMicroLamportPerCU);
291
+ }
292
+ else {
293
+ fee += ";0";
294
+ }
295
+ if (this.bribeData?.address) {
296
+ fee += ";" + this.bribeData.address;
297
+ }
298
+ this.logger.debug("getFeeRate(): calculated fee: " + fee);
299
+ return fee;
300
+ }
301
+ /**
302
+ * Calculates the total priority fee paid for a given compute budget at a given fee rate
303
+ *
304
+ * @param computeUnits
305
+ * @param feeRate
306
+ * @param includeStaticFee whether the include the static/base part of the fee rate
307
+ */
308
+ getPriorityFee(computeUnits, feeRate, includeStaticFee = true) {
309
+ if (feeRate == null)
310
+ return 0n;
311
+ const hashArr = feeRate.split("#");
312
+ if (hashArr.length > 1) {
313
+ feeRate = hashArr[0];
314
+ }
315
+ const arr = feeRate.split(";");
316
+ const cuPrice = BigInt(arr[0]);
317
+ const staticFee = includeStaticFee ? BigInt(arr[1]) : 0n;
318
+ return staticFee + (cuPrice * BigInt(computeUnits) / 1000000n);
319
+ }
320
+ /**
321
+ * Applies fee rate to a transaction, should be called before adding instructions to the transaction, specifically
322
+ * it adds the setComputeUnitLimit & setComputeUnitPrice instruction.
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * const feeRate = solanaFees.getFeeRate([...writeableAccounts]);
327
+ * const tx = new Transaction();
328
+ * //Apply the fee rate part at the beginning of the transaction (specifically setComputeUnitLimit & setComputeUnitPrice)
329
+ * SolanaFees.applyFeeRateBegin(tx, feeRate);
330
+ * //Add instructions here
331
+ * tx.add(instruction1);
332
+ * tx.add(instruction2);
333
+ * //Set the fee payer
334
+ * tx.feePayer = feePayerPublicKey;
335
+ * //Apply the fee rate part at the end of the transaction (specifically the transfer to the bribe account, e.g. Jito tip)
336
+ * SolanaFees.applyFeeRateEnd(tx, feeRate);
337
+ * ```
338
+ *
339
+ * @param tx
340
+ * @param computeBudget
341
+ * @param feeRate
342
+ */
343
+ static applyFeeRateBegin(tx, computeBudget, feeRate) {
344
+ if (feeRate == null)
345
+ return;
346
+ const hashArr = feeRate.split("#");
347
+ if (hashArr.length > 1) {
348
+ feeRate = hashArr[0];
349
+ }
350
+ if (computeBudget != null && computeBudget > 0)
351
+ tx.add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({
352
+ units: computeBudget,
353
+ }));
354
+ //Check if bribe is included
355
+ const arr = feeRate.split(";");
356
+ if (arr.length > 2) {
357
+ }
358
+ else {
359
+ let fee = BigInt(arr[0]);
360
+ if (arr.length > 1) {
361
+ const staticFee = BigInt(arr[1]);
362
+ const cuBigInt = BigInt(computeBudget || (200000 * Utils_1.SolanaTxUtils.getNonComputeBudgetIxs(tx)));
363
+ const staticFeePerCU = staticFee * BigInt(1000000) / cuBigInt;
364
+ fee += staticFeePerCU;
365
+ }
366
+ tx.add(web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({
367
+ microLamports: fee
368
+ }));
369
+ }
370
+ }
371
+ /**
372
+ * Applies fee rate to a transaction, should be called after adding instructions to the transaction, specifically
373
+ * it adds the adds the bribe SystemProgram.transfer instruction.
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * const feeRate = solanaFees.getFeeRate([...writeableAccounts]);
378
+ * const tx = new Transaction();
379
+ * //Apply the fee rate part at the beginning of the transaction (specifically setComputeUnitLimit & setComputeUnitPrice)
380
+ * SolanaFees.applyFeeRateBegin(tx, feeRate);
381
+ * //Add instructions here
382
+ * tx.add(instruction1);
383
+ * tx.add(instruction2);
384
+ * //Set the fee payer
385
+ * tx.feePayer = feePayerPublicKey;
386
+ * //Apply the fee rate part at the end of the transaction (specifically the transfer to the bribe account, e.g. Jito tip)
387
+ * SolanaFees.applyFeeRateEnd(tx, feeRate);
388
+ * ```
389
+ *
390
+ * @param tx
391
+ * @param computeBudget
392
+ * @param feeRate
393
+ */
394
+ static applyFeeRateEnd(tx, computeBudget, feeRate) {
395
+ if (feeRate == null)
396
+ return;
397
+ const hashArr = feeRate.split("#");
398
+ if (hashArr.length > 1) {
399
+ feeRate = hashArr[0];
400
+ }
401
+ //Check if bribe is included
402
+ const arr = feeRate.split(";");
403
+ if (arr.length > 2) {
404
+ const cuBigInt = BigInt(computeBudget ?? (200000 * (Utils_1.SolanaTxUtils.getNonComputeBudgetIxs(tx) + 1)));
405
+ const cuPrice = BigInt(arr[0]);
406
+ const staticFee = BigInt(arr[1]);
407
+ const bribeAddress = new web3_js_1.PublicKey(arr[2]);
408
+ if (tx.feePayer == null)
409
+ throw new Error("Cannot apply tx bribe without feePayer being known!");
410
+ tx.add(web3_js_1.SystemProgram.transfer({
411
+ fromPubkey: tx.feePayer,
412
+ toPubkey: bribeAddress,
413
+ lamports: staticFee + (cuBigInt * cuPrice / BigInt(1000000))
414
+ }));
415
+ return;
416
+ }
417
+ }
418
+ /**
419
+ * Checks if the transaction should be submitted over Jito and if yes submits it
420
+ *
421
+ * @param tx Raw signed transaction to be attempted to be sent over Jito
422
+ * @param options Send options for the sendTransaction RPC call
423
+ * @returns {Promise<string | null>} null if the transaction was not sent over Jito, tx signature when tx was sent over Jito
424
+ */
425
+ submitTx(tx, options) {
426
+ const parsedTx = web3_js_1.Transaction.from(tx);
427
+ const jitoFee = this.getJitoTxFee(parsedTx);
428
+ if (jitoFee == null)
429
+ return Promise.resolve(null);
430
+ this.logger.info("submitTx(): sending tx over Jito, signature: " + parsedTx.signature + " fee: " + jitoFee.toString(10));
431
+ return this.sendJitoTx(tx, options);
432
+ }
433
+ }
434
+ exports.SolanaFees = SolanaFees;