solana-priority-fee 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fee.js +36 -0
- package/index.js +137 -21
- package/package.json +3 -2
package/fee.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const { Connection, PublicKey } = require('@solana/web3.js');
|
|
2
|
+
const { SolanaFeeEstimator } = require('solana-priority-fee');
|
|
3
|
+
|
|
4
|
+
const connection = new Connection('https://mainnet.helius-rpc.com/?api-key=af5cb288-41c0-4728-a606-fee9e27909ea');
|
|
5
|
+
|
|
6
|
+
// Hot DeFi accounts
|
|
7
|
+
const HOT_ACCOUNTS = [
|
|
8
|
+
new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'), // Raydium
|
|
9
|
+
new PublicKey('JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4'), // Jupiter
|
|
10
|
+
new PublicKey('whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc'), // Orca
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// Convert to SOL (assuming 200k compute units)
|
|
14
|
+
function toSol(microlamports) {
|
|
15
|
+
return (microlamports * 200000) / 1_000_000 / 1_000_000_000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
console.log('Solana Priority Fees\n');
|
|
20
|
+
|
|
21
|
+
const estimator = new SolanaFeeEstimator(connection);
|
|
22
|
+
const fees = await estimator.getAllFees(HOT_ACCOUNTS);
|
|
23
|
+
|
|
24
|
+
console.log('┌──────────┬─────────────────┐');
|
|
25
|
+
console.log('│ Level │ Fee (SOL) │');
|
|
26
|
+
console.log('├──────────┼─────────────────┤');
|
|
27
|
+
console.log(`│ Economy │ ${toSol(fees.economy).toFixed(9)} │`);
|
|
28
|
+
console.log(`│ Medium │ ${toSol(fees.medium).toFixed(9)} │`);
|
|
29
|
+
console.log(`│ Fast │ ${toSol(fees.fast).toFixed(9)} │`);
|
|
30
|
+
console.log(`│ Urgent │ ${toSol(fees.urgent).toFixed(9)} │`);
|
|
31
|
+
console.log('└──────────┴─────────────────┘');
|
|
32
|
+
|
|
33
|
+
console.log(`\nBased on ${fees.raw.samples} recent transactions`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main().catch(console.error);
|
package/index.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
const { Connection } = require('@solana/web3.js');
|
|
1
|
+
const { Connection, PublicKey } = require('@solana/web3.js');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* SolanaFeeEstimator - Priority fee
|
|
4
|
+
* SolanaFeeEstimator - Priority fee + Jito tip estimation
|
|
5
5
|
*/
|
|
6
6
|
class SolanaFeeEstimator {
|
|
7
7
|
constructor(connection, options = {}) {
|
|
8
8
|
this.connection = connection;
|
|
9
9
|
|
|
10
|
-
// Configuration
|
|
11
10
|
this.config = {
|
|
12
|
-
cacheMs: options.cacheMs ?? 5000,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
cacheMs: options.cacheMs ?? 5000,
|
|
12
|
+
minFee: options.minFee ?? 1000,
|
|
13
|
+
maxFee: options.maxFee ?? 10_000_000,
|
|
14
|
+
jitoEndpoint: options.jitoEndpoint ?? 'https://bundles.jito.wtf/api/v1/bundles/tip_floor',
|
|
16
15
|
...options
|
|
17
16
|
};
|
|
18
17
|
|
|
19
|
-
// Percentiles for each level
|
|
20
18
|
this.levels = {
|
|
21
19
|
economy: 0.25,
|
|
22
20
|
medium: 0.50,
|
|
@@ -24,22 +22,21 @@ class SolanaFeeEstimator {
|
|
|
24
22
|
urgent: 0.95
|
|
25
23
|
};
|
|
26
24
|
|
|
27
|
-
// Cache
|
|
28
25
|
this.cache = {
|
|
29
26
|
global: null,
|
|
30
27
|
accounts: new Map(),
|
|
31
|
-
|
|
28
|
+
jito: null,
|
|
29
|
+
lastUpdate: 0,
|
|
30
|
+
jitoLastUpdate: 0
|
|
32
31
|
};
|
|
33
32
|
|
|
34
|
-
// Stats tracking
|
|
35
33
|
this.stats = {
|
|
36
34
|
requests: 0,
|
|
37
35
|
cacheHits: 0,
|
|
38
|
-
estimates: [],
|
|
39
|
-
confirmed: [],
|
|
36
|
+
estimates: [],
|
|
37
|
+
confirmed: [],
|
|
40
38
|
};
|
|
41
39
|
|
|
42
|
-
// WebSocket subscription
|
|
43
40
|
this.subscription = null;
|
|
44
41
|
this.realtimeFees = [];
|
|
45
42
|
}
|
|
@@ -56,7 +53,6 @@ class SolanaFeeEstimator {
|
|
|
56
53
|
|
|
57
54
|
this.stats.requests++;
|
|
58
55
|
|
|
59
|
-
// Check cache
|
|
60
56
|
const cacheKey = accountKeys.length > 0
|
|
61
57
|
? accountKeys.sort().join(',')
|
|
62
58
|
: 'global';
|
|
@@ -66,13 +62,11 @@ class SolanaFeeEstimator {
|
|
|
66
62
|
return this._calculateFee(this._getCachedFees(cacheKey), level);
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
// Fetch fresh data
|
|
70
65
|
const fees = await this._fetchFees(accountKeys);
|
|
71
66
|
this._updateCache(cacheKey, fees);
|
|
72
67
|
|
|
73
68
|
const estimate = this._calculateFee(fees, level);
|
|
74
69
|
|
|
75
|
-
// Track estimate
|
|
76
70
|
this.stats.estimates.push({
|
|
77
71
|
level,
|
|
78
72
|
fee: estimate,
|
|
@@ -102,6 +96,122 @@ class SolanaFeeEstimator {
|
|
|
102
96
|
};
|
|
103
97
|
}
|
|
104
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Get Jito tip recommendations (the real cost for fast landing)
|
|
101
|
+
*/
|
|
102
|
+
async getJitoTips() {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
|
|
105
|
+
// Check cache
|
|
106
|
+
if (this.cache.jito && (now - this.cache.jitoLastUpdate) < this.config.cacheMs) {
|
|
107
|
+
return this.cache.jito;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(this.config.jitoEndpoint);
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
|
|
114
|
+
// Jito returns tips in lamports
|
|
115
|
+
const tips = {
|
|
116
|
+
landed_tips_25th_percentile: data[0]?.landed_tips_25th_percentile ?? 0,
|
|
117
|
+
landed_tips_50th_percentile: data[0]?.landed_tips_50th_percentile ?? 0,
|
|
118
|
+
landed_tips_75th_percentile: data[0]?.landed_tips_75th_percentile ?? 0,
|
|
119
|
+
landed_tips_95th_percentile: data[0]?.landed_tips_95th_percentile ?? 0,
|
|
120
|
+
landed_tips_99th_percentile: data[0]?.landed_tips_99th_percentile ?? 0,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = {
|
|
124
|
+
// In SOL
|
|
125
|
+
economy: tips.landed_tips_25th_percentile / 1_000_000_000,
|
|
126
|
+
medium: tips.landed_tips_50th_percentile / 1_000_000_000,
|
|
127
|
+
fast: tips.landed_tips_75th_percentile / 1_000_000_000,
|
|
128
|
+
urgent: tips.landed_tips_95th_percentile / 1_000_000_000,
|
|
129
|
+
max: tips.landed_tips_99th_percentile / 1_000_000_000,
|
|
130
|
+
// Raw lamports
|
|
131
|
+
raw: tips
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this.cache.jito = result;
|
|
135
|
+
this.cache.jitoLastUpdate = now;
|
|
136
|
+
|
|
137
|
+
return result;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Failed to fetch Jito tips:', error.message);
|
|
140
|
+
// Return defaults if API fails
|
|
141
|
+
return {
|
|
142
|
+
economy: 0.0001,
|
|
143
|
+
medium: 0.001,
|
|
144
|
+
fast: 0.01,
|
|
145
|
+
urgent: 0.05,
|
|
146
|
+
max: 0.1,
|
|
147
|
+
raw: null,
|
|
148
|
+
error: error.message
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get total landing cost (priority fee + Jito tip)
|
|
155
|
+
*/
|
|
156
|
+
async getTotalLandingCost(options = {}) {
|
|
157
|
+
const {
|
|
158
|
+
level = 'medium',
|
|
159
|
+
accountKeys = [],
|
|
160
|
+
computeUnits = 200000
|
|
161
|
+
} = options;
|
|
162
|
+
|
|
163
|
+
const [priorityFees, jitoTips] = await Promise.all([
|
|
164
|
+
this.getAllFees(accountKeys),
|
|
165
|
+
this.getJitoTips()
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
// Convert priority fee to SOL
|
|
169
|
+
const priorityFeeSol = (priorityFees[level] * computeUnits) / 1_000_000 / 1_000_000_000;
|
|
170
|
+
const jitoTipSol = jitoTips[level];
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
priorityFee: priorityFeeSol,
|
|
174
|
+
jitoTip: jitoTipSol,
|
|
175
|
+
total: priorityFeeSol + jitoTipSol,
|
|
176
|
+
breakdown: {
|
|
177
|
+
priorityFee: `${priorityFeeSol.toFixed(9)} SOL`,
|
|
178
|
+
jitoTip: `${jitoTipSol.toFixed(9)} SOL`,
|
|
179
|
+
total: `${(priorityFeeSol + jitoTipSol).toFixed(9)} SOL`
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get all costs for all levels
|
|
186
|
+
*/
|
|
187
|
+
async getAllCosts(accountKeys = [], computeUnits = 200000) {
|
|
188
|
+
const [priorityFees, jitoTips] = await Promise.all([
|
|
189
|
+
this.getAllFees(accountKeys),
|
|
190
|
+
this.getJitoTips()
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
const levels = ['economy', 'medium', 'fast', 'urgent'];
|
|
194
|
+
const result = {};
|
|
195
|
+
|
|
196
|
+
for (const level of levels) {
|
|
197
|
+
const priorityFeeSol = (priorityFees[level] * computeUnits) / 1_000_000 / 1_000_000_000;
|
|
198
|
+
const jitoTipSol = jitoTips[level];
|
|
199
|
+
|
|
200
|
+
result[level] = {
|
|
201
|
+
priorityFee: priorityFeeSol,
|
|
202
|
+
jitoTip: jitoTipSol,
|
|
203
|
+
total: priorityFeeSol + jitoTipSol
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
result.raw = {
|
|
208
|
+
priorityFees: priorityFees.raw,
|
|
209
|
+
jitoTips: jitoTips.raw
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
105
215
|
/**
|
|
106
216
|
* Start real-time monitoring via WebSocket
|
|
107
217
|
*/
|
|
@@ -111,7 +221,6 @@ class SolanaFeeEstimator {
|
|
|
111
221
|
return;
|
|
112
222
|
}
|
|
113
223
|
|
|
114
|
-
// Subscribe to slot changes
|
|
115
224
|
this.subscription = this.connection.onSlotChange(async (slotInfo) => {
|
|
116
225
|
try {
|
|
117
226
|
const fees = await this._fetchFees([]);
|
|
@@ -127,7 +236,6 @@ class SolanaFeeEstimator {
|
|
|
127
236
|
|
|
128
237
|
this.realtimeFees.push(allFees);
|
|
129
238
|
|
|
130
|
-
// Keep last 100 samples
|
|
131
239
|
if (this.realtimeFees.length > 100) {
|
|
132
240
|
this.realtimeFees.shift();
|
|
133
241
|
}
|
|
@@ -163,7 +271,6 @@ class SolanaFeeEstimator {
|
|
|
163
271
|
timestamp: Date.now()
|
|
164
272
|
});
|
|
165
273
|
|
|
166
|
-
// Keep last 1000 results
|
|
167
274
|
if (this.stats.confirmed.length > 1000) {
|
|
168
275
|
this.stats.confirmed.shift();
|
|
169
276
|
}
|
|
@@ -288,7 +395,6 @@ class SolanaFeeEstimator {
|
|
|
288
395
|
} else {
|
|
289
396
|
this.cache.accounts.set(key, { fees, timestamp: now });
|
|
290
397
|
|
|
291
|
-
// Limit account cache size
|
|
292
398
|
if (this.cache.accounts.size > 100) {
|
|
293
399
|
const oldest = [...this.cache.accounts.entries()]
|
|
294
400
|
.sort((a, b) => a[1].timestamp - b[1].timestamp)[0];
|
|
@@ -306,6 +412,15 @@ async function getPriorityFee(connection, level = 'medium', accountKeys = []) {
|
|
|
306
412
|
return estimator.getFee({ level, accountKeys });
|
|
307
413
|
}
|
|
308
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Get Jito tips quickly
|
|
417
|
+
*/
|
|
418
|
+
async function getJitoTip(level = 'medium') {
|
|
419
|
+
const estimator = new SolanaFeeEstimator(null);
|
|
420
|
+
const tips = await estimator.getJitoTips();
|
|
421
|
+
return tips[level];
|
|
422
|
+
}
|
|
423
|
+
|
|
309
424
|
/**
|
|
310
425
|
* Create compute budget instruction with estimated fee
|
|
311
426
|
*/
|
|
@@ -322,5 +437,6 @@ async function createPriorityFeeInstruction(connection, level = 'medium', accoun
|
|
|
322
437
|
module.exports = {
|
|
323
438
|
SolanaFeeEstimator,
|
|
324
439
|
getPriorityFee,
|
|
440
|
+
getJitoTip,
|
|
325
441
|
createPriorityFeeInstruction
|
|
326
442
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-priority-fee",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Priority fee estimation for Solana with caching, monitoring, and success tracking",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"author": "",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@solana/web3.js": "^1.98.4"
|
|
19
|
+
"@solana/web3.js": "^1.98.4",
|
|
20
|
+
"solana-priority-fee": "^1.0.0"
|
|
20
21
|
}
|
|
21
22
|
}
|