@drift-labs/sdk 2.58.0-beta.0 → 2.58.0-beta.1

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/VERSION CHANGED
@@ -1 +1 @@
1
- 2.58.0-beta.0
1
+ 2.58.0-beta.1
@@ -1,7 +1,5 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
  export declare class AverageOverSlotsStrategy implements PriorityFeeStrategy {
3
- calculate(samples: {
4
- slot: number;
5
- prioritizationFee: number;
6
- }[]): number;
4
+ calculate(samples: SolanaPriorityFeeResponse[]): number;
7
5
  }
@@ -1,7 +1,5 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
  export declare class AverageStrategy implements PriorityFeeStrategy {
3
- calculate(samples: {
4
- slot: number;
5
- prioritizationFee: number;
6
- }[]): number;
4
+ calculate(samples: SolanaPriorityFeeResponse[]): number;
7
5
  }
@@ -1,3 +1,4 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
  declare class EwmaStrategy implements PriorityFeeStrategy {
3
4
  private halfLife;
@@ -5,9 +6,6 @@ declare class EwmaStrategy implements PriorityFeeStrategy {
5
6
  * @param halfLife The half life of the EWMA in slots. Default is 25 slots, approx 10 seconds.
6
7
  */
7
8
  constructor(halfLife?: number);
8
- calculate(samples: {
9
- slot: number;
10
- prioritizationFee: number;
11
- }[]): number;
9
+ calculate(samples: SolanaPriorityFeeResponse[]): number;
12
10
  }
13
11
  export { EwmaStrategy };
@@ -0,0 +1,20 @@
1
+ export declare enum HeliusPriorityLevel {
2
+ MIN = "min",
3
+ LOW = "low",
4
+ MEDIUM = "medium",
5
+ HIGH = "high",
6
+ VERY_HIGH = "veryHigh",
7
+ UNSAFE_MAX = "unsafeMax"
8
+ }
9
+ export type HeliusPriorityFeeLevels = {
10
+ [key in HeliusPriorityLevel]: number;
11
+ };
12
+ export type HeliusPriorityFeeResponse = {
13
+ jsonrpc: string;
14
+ result: {
15
+ priorityFeeEstimate?: number;
16
+ priorityFeeLevels?: HeliusPriorityFeeLevels;
17
+ };
18
+ id: string;
19
+ };
20
+ export declare function fetchHeliusPriorityFee(heliusRpcUrl: string, lookbackDistance: number, addresses: string[]): Promise<HeliusPriorityFeeResponse>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fetchHeliusPriorityFee = exports.HeliusPriorityLevel = void 0;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ var HeliusPriorityLevel;
9
+ (function (HeliusPriorityLevel) {
10
+ HeliusPriorityLevel["MIN"] = "min";
11
+ HeliusPriorityLevel["LOW"] = "low";
12
+ HeliusPriorityLevel["MEDIUM"] = "medium";
13
+ HeliusPriorityLevel["HIGH"] = "high";
14
+ HeliusPriorityLevel["VERY_HIGH"] = "veryHigh";
15
+ HeliusPriorityLevel["UNSAFE_MAX"] = "unsafeMax";
16
+ })(HeliusPriorityLevel = exports.HeliusPriorityLevel || (exports.HeliusPriorityLevel = {}));
17
+ /// Fetches the priority fee from the Helius API
18
+ /// https://docs.helius.dev/solana-rpc-nodes/alpha-priority-fee-api
19
+ async function fetchHeliusPriorityFee(heliusRpcUrl, lookbackDistance, addresses) {
20
+ const response = await (0, node_fetch_1.default)(heliusRpcUrl, {
21
+ method: 'POST',
22
+ headers: { 'Content-Type': 'application/json' },
23
+ body: JSON.stringify({
24
+ jsonrpc: '2.0',
25
+ id: '1',
26
+ method: 'getPriorityFeeEstimate',
27
+ params: [
28
+ {
29
+ accountKeys: addresses,
30
+ options: {
31
+ includeAllPriorityFeeLevels: true,
32
+ lookbackSlots: lookbackDistance,
33
+ },
34
+ },
35
+ ],
36
+ }),
37
+ });
38
+ return await response.json();
39
+ }
40
+ exports.fetchHeliusPriorityFee = fetchHeliusPriorityFee;
@@ -4,4 +4,6 @@ export * from './ewmaStrategy';
4
4
  export * from './maxOverSlotsStrategy';
5
5
  export * from './maxStrategy';
6
6
  export * from './priorityFeeSubscriber';
7
+ export * from './solanaPriorityFeeMethod';
8
+ export * from './heliusPriorityFeeMethod';
7
9
  export * from './types';
@@ -20,4 +20,6 @@ __exportStar(require("./ewmaStrategy"), exports);
20
20
  __exportStar(require("./maxOverSlotsStrategy"), exports);
21
21
  __exportStar(require("./maxStrategy"), exports);
22
22
  __exportStar(require("./priorityFeeSubscriber"), exports);
23
+ __exportStar(require("./solanaPriorityFeeMethod"), exports);
24
+ __exportStar(require("./heliusPriorityFeeMethod"), exports);
23
25
  __exportStar(require("./types"), exports);
@@ -1,7 +1,5 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
  export declare class MaxOverSlotsStrategy implements PriorityFeeStrategy {
3
- calculate(samples: {
4
- slot: number;
5
- prioritizationFee: number;
6
- }[]): number;
4
+ calculate(samples: SolanaPriorityFeeResponse[]): number;
7
5
  }
@@ -1,33 +1,33 @@
1
- import { Connection, PublicKey } from '@solana/web3.js';
2
- import { PriorityFeeStrategy } from './types';
1
+ import { Connection } from '@solana/web3.js';
2
+ import { PriorityFeeMethod, PriorityFeeStrategy, PriorityFeeSubscriberConfig } from './types';
3
3
  import { AverageOverSlotsStrategy } from './averageOverSlotsStrategy';
4
4
  import { MaxOverSlotsStrategy } from './maxOverSlotsStrategy';
5
+ import { HeliusPriorityFeeLevels, HeliusPriorityLevel } from './heliusPriorityFeeMethod';
5
6
  export declare class PriorityFeeSubscriber {
6
7
  connection: Connection;
7
8
  frequencyMs: number;
8
- addresses: PublicKey[];
9
+ addresses: string[];
9
10
  customStrategy?: PriorityFeeStrategy;
10
11
  averageStrategy: AverageOverSlotsStrategy;
11
12
  maxStrategy: MaxOverSlotsStrategy;
13
+ priorityFeeMethod: PriorityFeeMethod;
12
14
  lookbackDistance: number;
15
+ heliusRpcUrl?: string;
16
+ lastHeliusSample?: HeliusPriorityFeeLevels;
13
17
  intervalId?: ReturnType<typeof setTimeout>;
14
18
  latestPriorityFee: number;
15
19
  lastCustomStrategyResult: number;
16
20
  lastAvgStrategyResult: number;
17
21
  lastMaxStrategyResult: number;
18
22
  lastSlotSeen: number;
19
- /**
20
- * @param props
21
- * customStrategy : strategy to return the priority fee to use based on recent samples. defaults to AVERAGE.
22
- */
23
- constructor({ connection, frequencyMs, addresses, customStrategy, slotsToCheck, }: {
24
- connection: Connection;
25
- frequencyMs: number;
26
- addresses: PublicKey[];
27
- customStrategy?: PriorityFeeStrategy;
28
- slotsToCheck?: number;
29
- });
23
+ constructor(config: PriorityFeeSubscriberConfig);
30
24
  subscribe(): Promise<void>;
25
+ private loadForSolana;
26
+ private loadForHelius;
27
+ getHeliusPriorityFeeLevel(level?: HeliusPriorityLevel): number;
28
+ getCustomStrategyResult(): number;
29
+ getAvgStrategyResult(): number;
30
+ getMaxStrategyResult(): number;
31
31
  load(): Promise<void>;
32
32
  unsubscribe(): Promise<void>;
33
33
  }
@@ -1,62 +1,103 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PriorityFeeSubscriber = void 0;
4
+ const types_1 = require("./types");
4
5
  const averageOverSlotsStrategy_1 = require("./averageOverSlotsStrategy");
5
6
  const maxOverSlotsStrategy_1 = require("./maxOverSlotsStrategy");
7
+ const solanaPriorityFeeMethod_1 = require("./solanaPriorityFeeMethod");
8
+ const heliusPriorityFeeMethod_1 = require("./heliusPriorityFeeMethod");
6
9
  class PriorityFeeSubscriber {
7
- /**
8
- * @param props
9
- * customStrategy : strategy to return the priority fee to use based on recent samples. defaults to AVERAGE.
10
- */
11
- constructor({ connection, frequencyMs, addresses, customStrategy, slotsToCheck = 10, }) {
10
+ constructor(config) {
11
+ var _a;
12
12
  this.averageStrategy = new averageOverSlotsStrategy_1.AverageOverSlotsStrategy();
13
13
  this.maxStrategy = new maxOverSlotsStrategy_1.MaxOverSlotsStrategy();
14
+ this.priorityFeeMethod = types_1.PriorityFeeMethod.SOLANA;
14
15
  this.latestPriorityFee = 0;
15
16
  this.lastCustomStrategyResult = 0;
16
17
  this.lastAvgStrategyResult = 0;
17
18
  this.lastMaxStrategyResult = 0;
18
19
  this.lastSlotSeen = 0;
19
- this.connection = connection;
20
- this.frequencyMs = frequencyMs;
21
- this.addresses = addresses;
22
- if (!customStrategy) {
23
- this.customStrategy = new averageOverSlotsStrategy_1.AverageOverSlotsStrategy();
20
+ this.connection = config.connection;
21
+ this.frequencyMs = config.frequencyMs;
22
+ this.addresses = config.addresses.map((address) => address.toBase58());
23
+ if (config.customStrategy) {
24
+ this.customStrategy = config.customStrategy;
24
25
  }
25
26
  else {
26
- this.customStrategy = customStrategy;
27
+ this.customStrategy = this.averageStrategy;
28
+ }
29
+ this.lookbackDistance = (_a = config.slotsToCheck) !== null && _a !== void 0 ? _a : 50;
30
+ if (config.priorityFeeMethod) {
31
+ this.priorityFeeMethod = config.priorityFeeMethod;
32
+ if (this.priorityFeeMethod === types_1.PriorityFeeMethod.HELIUS) {
33
+ if (config.heliusRpcUrl === undefined) {
34
+ if (this.connection.rpcEndpoint.includes('helius')) {
35
+ this.heliusRpcUrl = this.connection.rpcEndpoint;
36
+ }
37
+ else {
38
+ throw new Error('Connection must be helius, or heliusRpcUrl must be provided to use PriorityFeeMethod.HELIUS');
39
+ }
40
+ }
41
+ }
42
+ }
43
+ if (this.priorityFeeMethod === types_1.PriorityFeeMethod.SOLANA) {
44
+ if (this.connection === undefined) {
45
+ throw new Error('connection must be provided to use SOLANA priority fee API');
46
+ }
27
47
  }
28
- this.lookbackDistance = slotsToCheck;
29
48
  }
30
49
  async subscribe() {
31
50
  if (this.intervalId) {
32
51
  return;
33
52
  }
53
+ await this.load();
34
54
  this.intervalId = setInterval(this.load.bind(this), this.frequencyMs);
35
55
  }
56
+ async loadForSolana() {
57
+ const samples = await (0, solanaPriorityFeeMethod_1.fetchSolanaPriorityFee)(this.connection, this.lookbackDistance, this.addresses);
58
+ this.latestPriorityFee = samples[0].prioritizationFee;
59
+ this.lastSlotSeen = samples[0].slot;
60
+ this.lastAvgStrategyResult = this.averageStrategy.calculate(samples);
61
+ this.lastMaxStrategyResult = this.maxStrategy.calculate(samples);
62
+ if (this.customStrategy) {
63
+ this.lastCustomStrategyResult = this.customStrategy.calculate(samples);
64
+ }
65
+ }
66
+ async loadForHelius() {
67
+ var _a, _b;
68
+ const sample = await (0, heliusPriorityFeeMethod_1.fetchHeliusPriorityFee)(this.heliusRpcUrl, this.lookbackDistance, this.addresses);
69
+ this.lastHeliusSample = (_b = (_a = sample === null || sample === void 0 ? void 0 : sample.result) === null || _a === void 0 ? void 0 : _a.priorityFeeLevels) !== null && _b !== void 0 ? _b : undefined;
70
+ }
71
+ getHeliusPriorityFeeLevel(level = heliusPriorityFeeMethod_1.HeliusPriorityLevel.MEDIUM) {
72
+ if (this.lastHeliusSample === undefined) {
73
+ return 0;
74
+ }
75
+ return this.lastHeliusSample[level];
76
+ }
77
+ getCustomStrategyResult() {
78
+ return this.lastCustomStrategyResult;
79
+ }
80
+ getAvgStrategyResult() {
81
+ return this.lastAvgStrategyResult;
82
+ }
83
+ getMaxStrategyResult() {
84
+ return this.lastMaxStrategyResult;
85
+ }
36
86
  async load() {
37
87
  try {
38
- // @ts-ignore
39
- const rpcJSONResponse = await this.connection._rpcRequest('getRecentPrioritizationFees', [this.addresses]);
40
- const results = rpcJSONResponse === null || rpcJSONResponse === void 0 ? void 0 : rpcJSONResponse.result;
41
- if (!results.length)
42
- return;
43
- // # Sort and filter results based on the slot lookback setting
44
- const descResults = results.sort((a, b) => b.slot - a.slot);
45
- const mostRecentResult = descResults[0];
46
- const cutoffSlot = mostRecentResult.slot - this.lookbackDistance;
47
- const resultsToUse = descResults.filter((result) => result.slot >= cutoffSlot);
48
- // # Handle results
49
- this.latestPriorityFee = mostRecentResult.prioritizationFee;
50
- this.lastSlotSeen = mostRecentResult.slot;
51
- this.lastAvgStrategyResult = this.averageStrategy.calculate(resultsToUse);
52
- this.lastMaxStrategyResult = this.maxStrategy.calculate(resultsToUse);
53
- if (this.customStrategy) {
54
- this.lastCustomStrategyResult =
55
- this.customStrategy.calculate(resultsToUse);
88
+ if (this.priorityFeeMethod === types_1.PriorityFeeMethod.SOLANA) {
89
+ await this.loadForSolana();
90
+ }
91
+ else if (this.priorityFeeMethod === types_1.PriorityFeeMethod.HELIUS) {
92
+ await this.loadForHelius();
93
+ }
94
+ else {
95
+ throw new Error(`${this.priorityFeeMethod} load not implemented`);
56
96
  }
57
97
  }
58
98
  catch (err) {
59
- // It's possible to get here with "TypeError: failed to fetch"
99
+ const e = err;
100
+ console.error(`Error loading priority fee ${this.priorityFeeMethod}: ${e.message}\n${e.stack ? e.stack : ''}`);
60
101
  return;
61
102
  }
62
103
  }
@@ -0,0 +1,6 @@
1
+ import { Connection } from '@solana/web3.js';
2
+ export type SolanaPriorityFeeResponse = {
3
+ slot: number;
4
+ prioritizationFee: number;
5
+ };
6
+ export declare function fetchSolanaPriorityFee(connection: Connection, lookbackDistance: number, addresses: string[]): Promise<SolanaPriorityFeeResponse[]>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchSolanaPriorityFee = void 0;
4
+ async function fetchSolanaPriorityFee(connection, lookbackDistance, addresses) {
5
+ // @ts-ignore
6
+ const rpcJSONResponse = await connection._rpcRequest('getRecentPrioritizationFees', [addresses]);
7
+ const results = rpcJSONResponse === null || rpcJSONResponse === void 0 ? void 0 : rpcJSONResponse.result;
8
+ if (!results.length)
9
+ return;
10
+ // Sort and filter results based on the slot lookback setting
11
+ const descResults = results.sort((a, b) => b.slot - a.slot);
12
+ const cutoffSlot = descResults[0].slot - lookbackDistance;
13
+ return descResults.filter((result) => result.slot >= cutoffSlot);
14
+ }
15
+ exports.fetchSolanaPriorityFee = fetchSolanaPriorityFee;
@@ -1,6 +1,19 @@
1
+ import { Connection, PublicKey } from '@solana/web3.js';
2
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
3
+ import { HeliusPriorityFeeResponse } from './heliusPriorityFeeMethod';
1
4
  export interface PriorityFeeStrategy {
2
- calculate(samples: {
3
- slot: number;
4
- prioritizationFee: number;
5
- }[]): number;
5
+ calculate(samples: SolanaPriorityFeeResponse[] | HeliusPriorityFeeResponse): number;
6
6
  }
7
+ export declare enum PriorityFeeMethod {
8
+ SOLANA = "solana",
9
+ HELIUS = "helius"
10
+ }
11
+ export type PriorityFeeSubscriberConfig = {
12
+ connection?: Connection;
13
+ frequencyMs: number;
14
+ addresses: PublicKey[];
15
+ customStrategy?: PriorityFeeStrategy;
16
+ priorityFeeMethod?: PriorityFeeMethod;
17
+ slotsToCheck?: number;
18
+ heliusRpcUrl?: string;
19
+ };
@@ -1,2 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PriorityFeeMethod = void 0;
4
+ var PriorityFeeMethod;
5
+ (function (PriorityFeeMethod) {
6
+ PriorityFeeMethod["SOLANA"] = "solana";
7
+ PriorityFeeMethod["HELIUS"] = "helius";
8
+ })(PriorityFeeMethod = exports.PriorityFeeMethod || (exports.PriorityFeeMethod = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.58.0-beta.0",
3
+ "version": "2.58.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -1,7 +1,8 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
 
3
4
  export class AverageOverSlotsStrategy implements PriorityFeeStrategy {
4
- calculate(samples: { slot: number; prioritizationFee: number }[]): number {
5
+ calculate(samples: SolanaPriorityFeeResponse[]): number {
5
6
  if (samples.length === 0) {
6
7
  return 0;
7
8
  }
@@ -1,7 +1,8 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
 
3
4
  export class AverageStrategy implements PriorityFeeStrategy {
4
- calculate(samples: { slot: number; prioritizationFee: number }[]): number {
5
+ calculate(samples: SolanaPriorityFeeResponse[]): number {
5
6
  return (
6
7
  samples.reduce((a, b) => {
7
8
  return a + b.prioritizationFee;
@@ -1,3 +1,4 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
 
3
4
  class EwmaStrategy implements PriorityFeeStrategy {
@@ -11,7 +12,7 @@ class EwmaStrategy implements PriorityFeeStrategy {
11
12
  }
12
13
 
13
14
  // samples provided in desc slot order
14
- calculate(samples: { slot: number; prioritizationFee: number }[]): number {
15
+ calculate(samples: SolanaPriorityFeeResponse[]): number {
15
16
  if (samples.length === 0) {
16
17
  return 0;
17
18
  }
@@ -0,0 +1,51 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ export enum HeliusPriorityLevel {
4
+ MIN = 'min', // 25th percentile
5
+ LOW = 'low', // 25th percentile
6
+ MEDIUM = 'medium', // 50th percentile
7
+ HIGH = 'high', // 75th percentile
8
+ VERY_HIGH = 'veryHigh', // 95th percentile
9
+ UNSAFE_MAX = 'unsafeMax', // 100th percentile
10
+ }
11
+
12
+ export type HeliusPriorityFeeLevels = {
13
+ [key in HeliusPriorityLevel]: number;
14
+ };
15
+
16
+ export type HeliusPriorityFeeResponse = {
17
+ jsonrpc: string;
18
+ result: {
19
+ priorityFeeEstimate?: number;
20
+ priorityFeeLevels?: HeliusPriorityFeeLevels;
21
+ };
22
+ id: string;
23
+ };
24
+
25
+ /// Fetches the priority fee from the Helius API
26
+ /// https://docs.helius.dev/solana-rpc-nodes/alpha-priority-fee-api
27
+ export async function fetchHeliusPriorityFee(
28
+ heliusRpcUrl: string,
29
+ lookbackDistance: number,
30
+ addresses: string[]
31
+ ): Promise<HeliusPriorityFeeResponse> {
32
+ const response = await fetch(heliusRpcUrl, {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({
36
+ jsonrpc: '2.0',
37
+ id: '1',
38
+ method: 'getPriorityFeeEstimate',
39
+ params: [
40
+ {
41
+ accountKeys: addresses,
42
+ options: {
43
+ includeAllPriorityFeeLevels: true,
44
+ lookbackSlots: lookbackDistance,
45
+ },
46
+ },
47
+ ],
48
+ }),
49
+ });
50
+ return await response.json();
51
+ }
@@ -4,4 +4,6 @@ export * from './ewmaStrategy';
4
4
  export * from './maxOverSlotsStrategy';
5
5
  export * from './maxStrategy';
6
6
  export * from './priorityFeeSubscriber';
7
+ export * from './solanaPriorityFeeMethod';
8
+ export * from './heliusPriorityFeeMethod';
7
9
  export * from './types';
@@ -1,7 +1,8 @@
1
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
1
2
  import { PriorityFeeStrategy } from './types';
2
3
 
3
4
  export class MaxOverSlotsStrategy implements PriorityFeeStrategy {
4
- calculate(samples: { slot: number; prioritizationFee: number }[]): number {
5
+ calculate(samples: SolanaPriorityFeeResponse[]): number {
5
6
  if (samples.length === 0) {
6
7
  return 0;
7
8
  }
@@ -1,17 +1,31 @@
1
- import { Connection, PublicKey } from '@solana/web3.js';
2
- import { PriorityFeeStrategy } from './types';
1
+ import { Connection } from '@solana/web3.js';
2
+ import {
3
+ PriorityFeeMethod,
4
+ PriorityFeeStrategy,
5
+ PriorityFeeSubscriberConfig,
6
+ } from './types';
3
7
  import { AverageOverSlotsStrategy } from './averageOverSlotsStrategy';
4
8
  import { MaxOverSlotsStrategy } from './maxOverSlotsStrategy';
9
+ import { fetchSolanaPriorityFee } from './solanaPriorityFeeMethod';
10
+ import {
11
+ HeliusPriorityFeeLevels,
12
+ HeliusPriorityLevel,
13
+ fetchHeliusPriorityFee,
14
+ } from './heliusPriorityFeeMethod';
5
15
 
6
16
  export class PriorityFeeSubscriber {
7
17
  connection: Connection;
8
18
  frequencyMs: number;
9
- addresses: PublicKey[];
19
+ addresses: string[];
10
20
  customStrategy?: PriorityFeeStrategy;
11
21
  averageStrategy = new AverageOverSlotsStrategy();
12
22
  maxStrategy = new MaxOverSlotsStrategy();
23
+ priorityFeeMethod = PriorityFeeMethod.SOLANA;
13
24
  lookbackDistance: number;
14
25
 
26
+ heliusRpcUrl?: string;
27
+ lastHeliusSample?: HeliusPriorityFeeLevels;
28
+
15
29
  intervalId?: ReturnType<typeof setTimeout>;
16
30
 
17
31
  latestPriorityFee = 0;
@@ -20,32 +34,39 @@ export class PriorityFeeSubscriber {
20
34
  lastMaxStrategyResult = 0;
21
35
  lastSlotSeen = 0;
22
36
 
23
- /**
24
- * @param props
25
- * customStrategy : strategy to return the priority fee to use based on recent samples. defaults to AVERAGE.
26
- */
27
- public constructor({
28
- connection,
29
- frequencyMs,
30
- addresses,
31
- customStrategy,
32
- slotsToCheck = 10,
33
- }: {
34
- connection: Connection;
35
- frequencyMs: number;
36
- addresses: PublicKey[];
37
- customStrategy?: PriorityFeeStrategy;
38
- slotsToCheck?: number;
39
- }) {
40
- this.connection = connection;
41
- this.frequencyMs = frequencyMs;
42
- this.addresses = addresses;
43
- if (!customStrategy) {
44
- this.customStrategy = new AverageOverSlotsStrategy();
37
+ public constructor(config: PriorityFeeSubscriberConfig) {
38
+ this.connection = config.connection;
39
+ this.frequencyMs = config.frequencyMs;
40
+ this.addresses = config.addresses.map((address) => address.toBase58());
41
+ if (config.customStrategy) {
42
+ this.customStrategy = config.customStrategy;
45
43
  } else {
46
- this.customStrategy = customStrategy;
44
+ this.customStrategy = this.averageStrategy;
45
+ }
46
+ this.lookbackDistance = config.slotsToCheck ?? 50;
47
+ if (config.priorityFeeMethod) {
48
+ this.priorityFeeMethod = config.priorityFeeMethod;
49
+
50
+ if (this.priorityFeeMethod === PriorityFeeMethod.HELIUS) {
51
+ if (config.heliusRpcUrl === undefined) {
52
+ if (this.connection.rpcEndpoint.includes('helius')) {
53
+ this.heliusRpcUrl = this.connection.rpcEndpoint;
54
+ } else {
55
+ throw new Error(
56
+ 'Connection must be helius, or heliusRpcUrl must be provided to use PriorityFeeMethod.HELIUS'
57
+ );
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ if (this.priorityFeeMethod === PriorityFeeMethod.SOLANA) {
64
+ if (this.connection === undefined) {
65
+ throw new Error(
66
+ 'connection must be provided to use SOLANA priority fee API'
67
+ );
68
+ }
47
69
  }
48
- this.lookbackDistance = slotsToCheck;
49
70
  }
50
71
 
51
72
  public async subscribe(): Promise<void> {
@@ -53,43 +74,72 @@ export class PriorityFeeSubscriber {
53
74
  return;
54
75
  }
55
76
 
77
+ await this.load();
56
78
  this.intervalId = setInterval(this.load.bind(this), this.frequencyMs);
57
79
  }
58
80
 
59
- public async load(): Promise<void> {
60
- try {
61
- // @ts-ignore
62
- const rpcJSONResponse: any = await this.connection._rpcRequest(
63
- 'getRecentPrioritizationFees',
64
- [this.addresses]
65
- );
81
+ private async loadForSolana(): Promise<void> {
82
+ const samples = await fetchSolanaPriorityFee(
83
+ this.connection!,
84
+ this.lookbackDistance,
85
+ this.addresses
86
+ );
87
+ this.latestPriorityFee = samples[0].prioritizationFee;
88
+ this.lastSlotSeen = samples[0].slot;
66
89
 
67
- const results: { slot: number; prioritizationFee: number }[] =
68
- rpcJSONResponse?.result;
90
+ this.lastAvgStrategyResult = this.averageStrategy.calculate(samples);
91
+ this.lastMaxStrategyResult = this.maxStrategy.calculate(samples);
92
+ if (this.customStrategy) {
93
+ this.lastCustomStrategyResult = this.customStrategy.calculate(samples);
94
+ }
95
+ }
69
96
 
70
- if (!results.length) return;
97
+ private async loadForHelius(): Promise<void> {
98
+ const sample = await fetchHeliusPriorityFee(
99
+ this.heliusRpcUrl,
100
+ this.lookbackDistance,
101
+ this.addresses
102
+ );
103
+ this.lastHeliusSample = sample?.result?.priorityFeeLevels ?? undefined;
104
+ }
71
105
 
72
- // # Sort and filter results based on the slot lookback setting
73
- const descResults = results.sort((a, b) => b.slot - a.slot);
74
- const mostRecentResult = descResults[0];
75
- const cutoffSlot = mostRecentResult.slot - this.lookbackDistance;
106
+ public getHeliusPriorityFeeLevel(
107
+ level: HeliusPriorityLevel = HeliusPriorityLevel.MEDIUM
108
+ ): number {
109
+ if (this.lastHeliusSample === undefined) {
110
+ return 0;
111
+ }
112
+ return this.lastHeliusSample[level];
113
+ }
76
114
 
77
- const resultsToUse = descResults.filter(
78
- (result) => result.slot >= cutoffSlot
79
- );
115
+ public getCustomStrategyResult(): number {
116
+ return this.lastCustomStrategyResult;
117
+ }
80
118
 
81
- // # Handle results
82
- this.latestPriorityFee = mostRecentResult.prioritizationFee;
83
- this.lastSlotSeen = mostRecentResult.slot;
119
+ public getAvgStrategyResult(): number {
120
+ return this.lastAvgStrategyResult;
121
+ }
84
122
 
85
- this.lastAvgStrategyResult = this.averageStrategy.calculate(resultsToUse);
86
- this.lastMaxStrategyResult = this.maxStrategy.calculate(resultsToUse);
87
- if (this.customStrategy) {
88
- this.lastCustomStrategyResult =
89
- this.customStrategy.calculate(resultsToUse);
123
+ public getMaxStrategyResult(): number {
124
+ return this.lastMaxStrategyResult;
125
+ }
126
+
127
+ public async load(): Promise<void> {
128
+ try {
129
+ if (this.priorityFeeMethod === PriorityFeeMethod.SOLANA) {
130
+ await this.loadForSolana();
131
+ } else if (this.priorityFeeMethod === PriorityFeeMethod.HELIUS) {
132
+ await this.loadForHelius();
133
+ } else {
134
+ throw new Error(`${this.priorityFeeMethod} load not implemented`);
90
135
  }
91
136
  } catch (err) {
92
- // It's possible to get here with "TypeError: failed to fetch"
137
+ const e = err as Error;
138
+ console.error(
139
+ `Error loading priority fee ${this.priorityFeeMethod}: ${e.message}\n${
140
+ e.stack ? e.stack : ''
141
+ }`
142
+ );
93
143
  return;
94
144
  }
95
145
  }
@@ -0,0 +1,28 @@
1
+ import { Connection } from '@solana/web3.js';
2
+
3
+ export type SolanaPriorityFeeResponse = {
4
+ slot: number;
5
+ prioritizationFee: number;
6
+ };
7
+
8
+ export async function fetchSolanaPriorityFee(
9
+ connection: Connection,
10
+ lookbackDistance: number,
11
+ addresses: string[]
12
+ ): Promise<SolanaPriorityFeeResponse[]> {
13
+ // @ts-ignore
14
+ const rpcJSONResponse: any = await connection._rpcRequest(
15
+ 'getRecentPrioritizationFees',
16
+ [addresses]
17
+ );
18
+
19
+ const results: SolanaPriorityFeeResponse[] = rpcJSONResponse?.result;
20
+
21
+ if (!results.length) return;
22
+
23
+ // Sort and filter results based on the slot lookback setting
24
+ const descResults = results.sort((a, b) => b.slot - a.slot);
25
+ const cutoffSlot = descResults[0].slot - lookbackDistance;
26
+
27
+ return descResults.filter((result) => result.slot >= cutoffSlot);
28
+ }
@@ -1,5 +1,33 @@
1
+ import { Connection, PublicKey } from '@solana/web3.js';
2
+ import { SolanaPriorityFeeResponse } from './solanaPriorityFeeMethod';
3
+ import { HeliusPriorityFeeResponse } from './heliusPriorityFeeMethod';
4
+
1
5
  export interface PriorityFeeStrategy {
2
6
  // calculate the priority fee for a given set of samples.
3
7
  // expect samples to be sorted in descending order (by slot)
4
- calculate(samples: { slot: number; prioritizationFee: number }[]): number;
8
+ calculate(
9
+ samples: SolanaPriorityFeeResponse[] | HeliusPriorityFeeResponse
10
+ ): number;
5
11
  }
12
+
13
+ export enum PriorityFeeMethod {
14
+ SOLANA = 'solana',
15
+ HELIUS = 'helius',
16
+ }
17
+
18
+ export type PriorityFeeSubscriberConfig = {
19
+ /// rpc connection, optional if using priorityFeeMethod.HELIUS
20
+ connection?: Connection;
21
+ /// frequency to make RPC calls to update priority fee samples, in milliseconds
22
+ frequencyMs: number;
23
+ /// addresses you plan to write lock, used to determine priority fees
24
+ addresses: PublicKey[];
25
+ /// custom strategy to calculate priority fees, defaults to AVERAGE
26
+ customStrategy?: PriorityFeeStrategy;
27
+ /// method for fetching priority fee samples
28
+ priorityFeeMethod?: PriorityFeeMethod;
29
+ /// lookback window to determine priority fees, in slots.
30
+ slotsToCheck?: number;
31
+ /// url for helius rpc, required if using priorityFeeMethod.HELIUS
32
+ heliusRpcUrl?: string;
33
+ };
@@ -76,7 +76,7 @@ describe('PriorityFeeStrategy', () => {
76
76
  { slot: 1, prioritizationFee: 1000 },
77
77
  ];
78
78
  const maxOverSlots = maxOverSlotsStrategy.calculate(samples);
79
- expect(maxOverSlots).to.equal(832);
79
+ expect(maxOverSlots).to.equal(1000);
80
80
  });
81
81
 
82
82
  it('AverageOverSlotsStrategy should calculate the average prioritization fee over slots', () => {
@@ -90,6 +90,6 @@ describe('PriorityFeeStrategy', () => {
90
90
  { slot: 1, prioritizationFee: 1000 },
91
91
  ];
92
92
  const averageOverSlots = averageOverSlotsStrategy.calculate(samples);
93
- expect(averageOverSlots).to.equal(454.4);
93
+ expect(averageOverSlots).to.approximately(545.33333, 0.00001);
94
94
  });
95
95
  });