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