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