solana-priority-fee 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +179 -0
  2. package/example.js +141 -0
  3. package/index.js +326 -0
  4. package/package.json +21 -0
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # solana-priority-fee
2
+
3
+ Priority fee estimation for Solana with caching, real-time monitoring, and success tracking.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install solana-priority-fee @solana/web3.js
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```javascript
14
+ const { Connection } = require('@solana/web3.js');
15
+ const { getPriorityFee } = require('solana-priority-fee');
16
+
17
+ const connection = new Connection('https://api.mainnet-beta.solana.com');
18
+
19
+ // Get a fee estimate
20
+ const fee = await getPriorityFee(connection, 'fast');
21
+ console.log(fee); // e.g., 50000 microlamports
22
+ ```
23
+
24
+ ## Fee Levels
25
+
26
+ | Level | Percentile | Use Case |
27
+ |-------|------------|----------|
28
+ | `economy` | 25th | Can wait, saving costs |
29
+ | `medium` | 50th | Normal transactions |
30
+ | `fast` | 75th | Need quick confirmation |
31
+ | `urgent` | 95th | Time-critical, willing to pay |
32
+
33
+ ## Full API
34
+
35
+ ### SolanaFeeEstimator
36
+
37
+ ```javascript
38
+ const { SolanaFeeEstimator } = require('solana-priority-fee');
39
+
40
+ const estimator = new SolanaFeeEstimator(connection, {
41
+ cacheMs: 5000, // Cache duration (default: 5s)
42
+ minFee: 1000, // Minimum fee (default: 1000)
43
+ maxFee: 10_000_000, // Maximum fee cap (default: 10M)
44
+ });
45
+ ```
46
+
47
+ #### getFee(options)
48
+
49
+ ```javascript
50
+ const fee = await estimator.getFee({
51
+ level: 'fast', // economy | medium | fast | urgent
52
+ accountKeys: [pubkey], // Optional: specific accounts for better accuracy
53
+ useCache: true // Optional: use cached data (default: true)
54
+ });
55
+ ```
56
+
57
+ #### getAllFees(accountKeys?)
58
+
59
+ ```javascript
60
+ const fees = await estimator.getAllFees();
61
+ // {
62
+ // economy: 10000,
63
+ // medium: 25000,
64
+ // fast: 50000,
65
+ // urgent: 150000,
66
+ // raw: { min, max, median, samples }
67
+ // }
68
+ ```
69
+
70
+ #### Real-time Monitoring
71
+
72
+ ```javascript
73
+ // Start monitoring (updates each slot)
74
+ await estimator.startMonitoring((fees) => {
75
+ console.log(`Slot ${fees.slot}: medium=${fees.medium}`);
76
+ });
77
+
78
+ // Get trend analysis
79
+ const trend = estimator.getTrend();
80
+ // { trend: 'rising' | 'falling' | 'stable', change: '+15.2%' }
81
+
82
+ // Stop monitoring
83
+ await estimator.stopMonitoring();
84
+ ```
85
+
86
+ #### Success Tracking
87
+
88
+ ```javascript
89
+ // Record transaction results
90
+ estimator.recordResult(50000, 'fast', true); // landed
91
+ estimator.recordResult(50000, 'fast', false); // dropped
92
+
93
+ // Get success rates per level
94
+ const rates = estimator.getSuccessRates();
95
+ // { fast: { total: 10, landed: 8, rate: '80.0%' }, ... }
96
+ ```
97
+
98
+ #### Stats
99
+
100
+ ```javascript
101
+ const stats = estimator.getStats();
102
+ // {
103
+ // requests: 100,
104
+ // cacheHits: 85,
105
+ // cacheHitRate: '85.0%',
106
+ // isMonitoring: true,
107
+ // ...
108
+ // }
109
+ ```
110
+
111
+ ### Helper Functions
112
+
113
+ #### createPriorityFeeInstruction
114
+
115
+ ```javascript
116
+ const { createPriorityFeeInstruction } = require('solana-priority-fee');
117
+
118
+ const ix = await createPriorityFeeInstruction(connection, 'fast');
119
+
120
+ const tx = new Transaction().add(
121
+ ix,
122
+ // ... your other instructions
123
+ );
124
+ ```
125
+
126
+ ## Account-Specific Fees
127
+
128
+ For more accurate estimates, pass the accounts your transaction writes to:
129
+
130
+ ```javascript
131
+ const fee = await estimator.getFee({
132
+ level: 'fast',
133
+ accountKeys: [poolAddress, userTokenAccount]
134
+ });
135
+ ```
136
+
137
+ This uses `getRecentPrioritizationFees` with `lockedWritableAccounts` for account-specific data.
138
+
139
+ ## Example: Full Transaction
140
+
141
+ ```javascript
142
+ const { Connection, Transaction, SystemProgram } = require('@solana/web3.js');
143
+ const { createPriorityFeeInstruction, SolanaFeeEstimator } = require('solana-priority-fee');
144
+
145
+ const connection = new Connection('https://api.mainnet-beta.solana.com');
146
+ const estimator = new SolanaFeeEstimator(connection);
147
+
148
+ async function sendWithPriorityFee(fromKeypair, toAddress, amount) {
149
+ // Get priority fee instruction
150
+ const priorityFeeIx = await createPriorityFeeInstruction(connection, 'fast', [toAddress]);
151
+
152
+ // Build transaction
153
+ const tx = new Transaction().add(
154
+ priorityFeeIx,
155
+ SystemProgram.transfer({
156
+ fromPubkey: fromKeypair.publicKey,
157
+ toPubkey: toAddress,
158
+ lamports: amount
159
+ })
160
+ );
161
+
162
+ // Send and confirm
163
+ const sig = await connection.sendTransaction(tx, [fromKeypair]);
164
+ const result = await connection.confirmTransaction(sig);
165
+
166
+ // Track result for success rate analysis
167
+ estimator.recordResult(
168
+ priorityFeeIx.data.readBigUInt64LE(1), // fee from instruction
169
+ 'fast',
170
+ result.value.err === null
171
+ );
172
+
173
+ return sig;
174
+ }
175
+ ```
176
+
177
+ ## License
178
+
179
+ MIT
package/example.js ADDED
@@ -0,0 +1,141 @@
1
+ const { Connection, PublicKey, Transaction, SystemProgram, Keypair } = require('@solana/web3.js');
2
+ const { SolanaFeeEstimator, getPriorityFee, createPriorityFeeInstruction } = require('./src/index');
3
+
4
+ // Connect to Solana
5
+ const connection = new Connection('https://api.mainnet-beta.solana.com');
6
+
7
+ async function basicUsage() {
8
+ console.log('\n=== Basic Usage ===\n');
9
+
10
+ // Quick one-liner
11
+ const fee = await getPriorityFee(connection, 'fast');
12
+ console.log('Quick estimate (fast):', fee, 'microlamports');
13
+
14
+ // Get all levels
15
+ const estimator = new SolanaFeeEstimator(connection);
16
+ const allFees = await estimator.getAllFees();
17
+ console.log('All fee levels:', allFees);
18
+ }
19
+
20
+ async function accountSpecificFees() {
21
+ console.log('\n=== Account-Specific Fees ===\n');
22
+
23
+ // For specific accounts you're writing to (more accurate)
24
+ const poolAddress = new PublicKey('8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6');
25
+
26
+ const estimator = new SolanaFeeEstimator(connection);
27
+ const fee = await estimator.getFee({
28
+ level: 'fast',
29
+ accountKeys: [poolAddress]
30
+ });
31
+
32
+ console.log('Account-specific fee:', fee, 'microlamports');
33
+ }
34
+
35
+ async function withCaching() {
36
+ console.log('\n=== Caching Demo ===\n');
37
+
38
+ const estimator = new SolanaFeeEstimator(connection, {
39
+ cacheMs: 10000 // 10 second cache
40
+ });
41
+
42
+ // First call - fetches from network
43
+ console.time('First call');
44
+ await estimator.getFee({ level: 'medium' });
45
+ console.timeEnd('First call');
46
+
47
+ // Second call - from cache
48
+ console.time('Second call (cached)');
49
+ await estimator.getFee({ level: 'medium' });
50
+ console.timeEnd('Second call (cached)');
51
+
52
+ console.log('Stats:', estimator.getStats());
53
+ }
54
+
55
+ async function realtimeMonitoring() {
56
+ console.log('\n=== Real-time Monitoring ===\n');
57
+
58
+ const estimator = new SolanaFeeEstimator(connection);
59
+
60
+ // Start monitoring (updates on each slot)
61
+ await estimator.startMonitoring((fees) => {
62
+ console.log(`Slot ${fees.slot}: economy=${fees.economy}, medium=${fees.medium}, fast=${fees.fast}`);
63
+ });
64
+
65
+ // Let it run for 30 seconds
66
+ console.log('Monitoring for 30 seconds...');
67
+ await new Promise(resolve => setTimeout(resolve, 30000));
68
+
69
+ // Check trend
70
+ console.log('Trend:', estimator.getTrend());
71
+
72
+ // Stop monitoring
73
+ await estimator.stopMonitoring();
74
+ }
75
+
76
+ async function buildTransaction() {
77
+ console.log('\n=== Building a Transaction ===\n');
78
+
79
+ // Get priority fee instruction
80
+ const priorityFeeIx = await createPriorityFeeInstruction(connection, 'fast');
81
+
82
+ // Build your transaction
83
+ const tx = new Transaction().add(
84
+ priorityFeeIx,
85
+ // ... add your other instructions here
86
+ // SystemProgram.transfer({ ... })
87
+ );
88
+
89
+ console.log('Transaction built with priority fee instruction');
90
+ console.log('Instructions:', tx.instructions.length);
91
+ }
92
+
93
+ async function successTracking() {
94
+ console.log('\n=== Success Rate Tracking ===\n');
95
+
96
+ const estimator = new SolanaFeeEstimator(connection);
97
+
98
+ // Simulate some transaction results
99
+ // In real usage, call this after each transaction confirms/fails
100
+ estimator.recordResult(50000, 'fast', true); // landed
101
+ estimator.recordResult(50000, 'fast', true); // landed
102
+ estimator.recordResult(50000, 'fast', false); // dropped
103
+ estimator.recordResult(10000, 'economy', true);
104
+ estimator.recordResult(10000, 'economy', false);
105
+ estimator.recordResult(10000, 'economy', false);
106
+
107
+ console.log('Success rates:', estimator.getSuccessRates());
108
+ }
109
+
110
+ async function customConfiguration() {
111
+ console.log('\n=== Custom Configuration ===\n');
112
+
113
+ const estimator = new SolanaFeeEstimator(connection, {
114
+ cacheMs: 3000, // 3 second cache
115
+ minFee: 5000, // Minimum 5000 microlamports
116
+ maxFee: 5_000_000, // Cap at 5M microlamports
117
+ });
118
+
119
+ const fees = await estimator.getAllFees();
120
+ console.log('Fees with custom config:', fees);
121
+ }
122
+
123
+ // Run examples
124
+ async function main() {
125
+ try {
126
+ await basicUsage();
127
+ await accountSpecificFees();
128
+ await withCaching();
129
+ await buildTransaction();
130
+ await successTracking();
131
+ await customConfiguration();
132
+
133
+ // Uncomment to run real-time monitoring (takes 30 seconds)
134
+ // await realtimeMonitoring();
135
+
136
+ } catch (error) {
137
+ console.error('Error:', error);
138
+ }
139
+ }
140
+
141
+ main();
package/index.js ADDED
@@ -0,0 +1,326 @@
1
+ const { Connection } = require('@solana/web3.js');
2
+
3
+ /**
4
+ * SolanaFeeEstimator - Priority fee estimation with caching and monitoring
5
+ */
6
+ class SolanaFeeEstimator {
7
+ constructor(connection, options = {}) {
8
+ this.connection = connection;
9
+
10
+ // Configuration
11
+ this.config = {
12
+ cacheMs: options.cacheMs ?? 5000, // Cache duration
13
+ sampleSize: options.sampleSize ?? 150, // Recent slots to analyze
14
+ minFee: options.minFee ?? 1000, // Minimum fee (microlamports)
15
+ maxFee: options.maxFee ?? 10_000_000, // Maximum fee cap
16
+ ...options
17
+ };
18
+
19
+ // Percentiles for each level
20
+ this.levels = {
21
+ economy: 0.25,
22
+ medium: 0.50,
23
+ fast: 0.75,
24
+ urgent: 0.95
25
+ };
26
+
27
+ // Cache
28
+ this.cache = {
29
+ global: null,
30
+ accounts: new Map(),
31
+ lastUpdate: 0
32
+ };
33
+
34
+ // Stats tracking
35
+ this.stats = {
36
+ requests: 0,
37
+ cacheHits: 0,
38
+ estimates: [], // { level, fee, timestamp }
39
+ confirmed: [], // { level, fee, landed, timestamp }
40
+ };
41
+
42
+ // WebSocket subscription
43
+ this.subscription = null;
44
+ this.realtimeFees = [];
45
+ }
46
+
47
+ /**
48
+ * Get priority fee recommendation
49
+ */
50
+ async getFee(options = {}) {
51
+ const {
52
+ level = 'medium',
53
+ accountKeys = [],
54
+ useCache = true
55
+ } = options;
56
+
57
+ this.stats.requests++;
58
+
59
+ // Check cache
60
+ const cacheKey = accountKeys.length > 0
61
+ ? accountKeys.sort().join(',')
62
+ : 'global';
63
+
64
+ if (useCache && this._isCacheValid(cacheKey)) {
65
+ this.stats.cacheHits++;
66
+ return this._calculateFee(this._getCachedFees(cacheKey), level);
67
+ }
68
+
69
+ // Fetch fresh data
70
+ const fees = await this._fetchFees(accountKeys);
71
+ this._updateCache(cacheKey, fees);
72
+
73
+ const estimate = this._calculateFee(fees, level);
74
+
75
+ // Track estimate
76
+ this.stats.estimates.push({
77
+ level,
78
+ fee: estimate,
79
+ timestamp: Date.now()
80
+ });
81
+
82
+ return estimate;
83
+ }
84
+
85
+ /**
86
+ * Get all fee levels at once
87
+ */
88
+ async getAllFees(accountKeys = []) {
89
+ const fees = await this._fetchFees(accountKeys);
90
+
91
+ return {
92
+ economy: this._calculateFee(fees, 'economy'),
93
+ medium: this._calculateFee(fees, 'medium'),
94
+ fast: this._calculateFee(fees, 'fast'),
95
+ urgent: this._calculateFee(fees, 'urgent'),
96
+ raw: {
97
+ min: fees.length > 0 ? fees[0] : this.config.minFee,
98
+ max: fees.length > 0 ? fees[fees.length - 1] : this.config.minFee,
99
+ median: fees.length > 0 ? fees[Math.floor(fees.length / 2)] : this.config.minFee,
100
+ samples: fees.length
101
+ }
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Start real-time monitoring via WebSocket
107
+ */
108
+ async startMonitoring(callback) {
109
+ if (this.subscription) {
110
+ console.warn('Already monitoring');
111
+ return;
112
+ }
113
+
114
+ // Subscribe to slot changes
115
+ this.subscription = this.connection.onSlotChange(async (slotInfo) => {
116
+ try {
117
+ const fees = await this._fetchFees([]);
118
+ this._updateCache('global', fees);
119
+
120
+ const allFees = {
121
+ slot: slotInfo.slot,
122
+ economy: this._calculateFee(fees, 'economy'),
123
+ medium: this._calculateFee(fees, 'medium'),
124
+ fast: this._calculateFee(fees, 'fast'),
125
+ urgent: this._calculateFee(fees, 'urgent')
126
+ };
127
+
128
+ this.realtimeFees.push(allFees);
129
+
130
+ // Keep last 100 samples
131
+ if (this.realtimeFees.length > 100) {
132
+ this.realtimeFees.shift();
133
+ }
134
+
135
+ if (callback) callback(allFees);
136
+ } catch (err) {
137
+ console.error('Monitoring error:', err);
138
+ }
139
+ });
140
+
141
+ console.log('Fee monitoring started');
142
+ }
143
+
144
+ /**
145
+ * Stop real-time monitoring
146
+ */
147
+ async stopMonitoring() {
148
+ if (this.subscription) {
149
+ await this.connection.removeSlotChangeListener(this.subscription);
150
+ this.subscription = null;
151
+ console.log('Fee monitoring stopped');
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Record transaction result for accuracy tracking
157
+ */
158
+ recordResult(fee, level, landed) {
159
+ this.stats.confirmed.push({
160
+ fee,
161
+ level,
162
+ landed,
163
+ timestamp: Date.now()
164
+ });
165
+
166
+ // Keep last 1000 results
167
+ if (this.stats.confirmed.length > 1000) {
168
+ this.stats.confirmed.shift();
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Get success rates per level
174
+ */
175
+ getSuccessRates() {
176
+ const rates = {};
177
+
178
+ for (const level of Object.keys(this.levels)) {
179
+ const results = this.stats.confirmed.filter(r => r.level === level);
180
+ const landed = results.filter(r => r.landed).length;
181
+
182
+ rates[level] = {
183
+ total: results.length,
184
+ landed,
185
+ rate: results.length > 0 ? (landed / results.length * 100).toFixed(1) + '%' : 'N/A'
186
+ };
187
+ }
188
+
189
+ return rates;
190
+ }
191
+
192
+ /**
193
+ * Get trend analysis
194
+ */
195
+ getTrend() {
196
+ if (this.realtimeFees.length < 10) {
197
+ return { trend: 'insufficient_data', change: 0 };
198
+ }
199
+
200
+ const recent = this.realtimeFees.slice(-10);
201
+ const older = this.realtimeFees.slice(-20, -10);
202
+
203
+ if (older.length === 0) {
204
+ return { trend: 'insufficient_data', change: 0 };
205
+ }
206
+
207
+ const recentAvg = recent.reduce((sum, f) => sum + f.medium, 0) / recent.length;
208
+ const olderAvg = older.reduce((sum, f) => sum + f.medium, 0) / older.length;
209
+
210
+ const change = ((recentAvg - olderAvg) / olderAvg) * 100;
211
+
212
+ let trend = 'stable';
213
+ if (change > 20) trend = 'rising';
214
+ else if (change < -20) trend = 'falling';
215
+
216
+ return {
217
+ trend,
218
+ change: change.toFixed(1) + '%',
219
+ recentAvg: Math.round(recentAvg),
220
+ olderAvg: Math.round(olderAvg)
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Get cache and request statistics
226
+ */
227
+ getStats() {
228
+ return {
229
+ requests: this.stats.requests,
230
+ cacheHits: this.stats.cacheHits,
231
+ cacheHitRate: this.stats.requests > 0
232
+ ? ((this.stats.cacheHits / this.stats.requests) * 100).toFixed(1) + '%'
233
+ : 'N/A',
234
+ estimatesTracked: this.stats.estimates.length,
235
+ resultsTracked: this.stats.confirmed.length,
236
+ realtimeSamples: this.realtimeFees.length,
237
+ isMonitoring: this.subscription !== null
238
+ };
239
+ }
240
+
241
+ // Private methods
242
+
243
+ async _fetchFees(accountKeys) {
244
+ const options = accountKeys.length > 0
245
+ ? { lockedWritableAccounts: accountKeys }
246
+ : {};
247
+
248
+ const fees = await this.connection.getRecentPrioritizationFees(options);
249
+
250
+ return fees
251
+ .map(f => f.prioritizationFee)
252
+ .filter(f => f > 0)
253
+ .sort((a, b) => a - b);
254
+ }
255
+
256
+ _calculateFee(fees, level) {
257
+ if (fees.length === 0) return this.config.minFee;
258
+
259
+ const percentile = this.levels[level] ?? 0.5;
260
+ const index = Math.floor(fees.length * percentile);
261
+ const fee = fees[Math.min(index, fees.length - 1)];
262
+
263
+ return Math.max(this.config.minFee, Math.min(fee, this.config.maxFee));
264
+ }
265
+
266
+ _isCacheValid(key) {
267
+ const now = Date.now();
268
+
269
+ if (key === 'global') {
270
+ return this.cache.global && (now - this.cache.lastUpdate) < this.config.cacheMs;
271
+ }
272
+
273
+ const cached = this.cache.accounts.get(key);
274
+ return cached && (now - cached.timestamp) < this.config.cacheMs;
275
+ }
276
+
277
+ _getCachedFees(key) {
278
+ if (key === 'global') return this.cache.global;
279
+ return this.cache.accounts.get(key)?.fees ?? [];
280
+ }
281
+
282
+ _updateCache(key, fees) {
283
+ const now = Date.now();
284
+
285
+ if (key === 'global') {
286
+ this.cache.global = fees;
287
+ this.cache.lastUpdate = now;
288
+ } else {
289
+ this.cache.accounts.set(key, { fees, timestamp: now });
290
+
291
+ // Limit account cache size
292
+ if (this.cache.accounts.size > 100) {
293
+ const oldest = [...this.cache.accounts.entries()]
294
+ .sort((a, b) => a[1].timestamp - b[1].timestamp)[0];
295
+ this.cache.accounts.delete(oldest[0]);
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Helper function for quick one-off estimates
303
+ */
304
+ async function getPriorityFee(connection, level = 'medium', accountKeys = []) {
305
+ const estimator = new SolanaFeeEstimator(connection);
306
+ return estimator.getFee({ level, accountKeys });
307
+ }
308
+
309
+ /**
310
+ * Create compute budget instruction with estimated fee
311
+ */
312
+ async function createPriorityFeeInstruction(connection, level = 'medium', accountKeys = []) {
313
+ const { ComputeBudgetProgram } = require('@solana/web3.js');
314
+
315
+ const fee = await getPriorityFee(connection, level, accountKeys);
316
+
317
+ return ComputeBudgetProgram.setComputeUnitPrice({
318
+ microLamports: fee
319
+ });
320
+ }
321
+
322
+ module.exports = {
323
+ SolanaFeeEstimator,
324
+ getPriorityFee,
325
+ createPriorityFeeInstruction
326
+ };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "solana-priority-fee",
3
+ "version": "1.0.0",
4
+ "description": "Priority fee estimation for Solana with caching, monitoring, and success tracking",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "example": "node example.js"
8
+ },
9
+ "keywords": [
10
+ "solana",
11
+ "priority-fee",
12
+ "transaction",
13
+ "web3",
14
+ "blockchain"
15
+ ],
16
+ "author": "",
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "@solana/web3.js": "^1.98.4"
20
+ }
21
+ }