@drift-labs/sdk 2.57.0-beta.2 → 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 +1 -1
- package/lib/idl/drift.json +1 -1
- package/lib/priorityFee/averageOverSlotsStrategy.d.ts +2 -4
- package/lib/priorityFee/averageStrategy.d.ts +2 -4
- package/lib/priorityFee/ewmaStrategy.d.ts +2 -4
- package/lib/priorityFee/heliusPriorityFeeMethod.d.ts +20 -0
- package/lib/priorityFee/heliusPriorityFeeMethod.js +40 -0
- package/lib/priorityFee/index.d.ts +2 -0
- package/lib/priorityFee/index.js +2 -0
- package/lib/priorityFee/maxOverSlotsStrategy.d.ts +2 -4
- package/lib/priorityFee/priorityFeeSubscriber.d.ts +14 -14
- package/lib/priorityFee/priorityFeeSubscriber.js +72 -31
- package/lib/priorityFee/solanaPriorityFeeMethod.d.ts +6 -0
- package/lib/priorityFee/solanaPriorityFeeMethod.js +15 -0
- package/lib/priorityFee/types.d.ts +17 -4
- package/lib/priorityFee/types.js +6 -0
- package/package.json +1 -1
- package/src/idl/drift.json +1 -1
- package/src/priorityFee/averageOverSlotsStrategy.ts +2 -1
- package/src/priorityFee/averageStrategy.ts +2 -1
- package/src/priorityFee/ewmaStrategy.ts +2 -1
- package/src/priorityFee/heliusPriorityFeeMethod.ts +51 -0
- package/src/priorityFee/index.ts +2 -0
- package/src/priorityFee/maxOverSlotsStrategy.ts +2 -1
- package/src/priorityFee/priorityFeeSubscriber.ts +103 -53
- package/src/priorityFee/solanaPriorityFeeMethod.ts +28 -0
- package/src/priorityFee/types.ts +29 -1
- package/tests/tx/priorityFeeStrategy.ts +2 -2
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.58.0-beta.1
|
package/lib/idl/drift.json
CHANGED
|
@@ -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;
|
package/lib/priorityFee/index.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
23
|
-
this.customStrategy =
|
|
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 =
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/lib/priorityFee/types.js
CHANGED
|
@@ -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
package/src/idl/drift.json
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
+
}
|
package/src/priorityFee/index.ts
CHANGED
|
@@ -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:
|
|
5
|
+
calculate(samples: SolanaPriorityFeeResponse[]): number {
|
|
5
6
|
if (samples.length === 0) {
|
|
6
7
|
return 0;
|
|
7
8
|
}
|
|
@@ -1,17 +1,31 @@
|
|
|
1
|
-
import { Connection
|
|
2
|
-
import {
|
|
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:
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 =
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
115
|
+
public getCustomStrategyResult(): number {
|
|
116
|
+
return this.lastCustomStrategyResult;
|
|
117
|
+
}
|
|
80
118
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
public getAvgStrategyResult(): number {
|
|
120
|
+
return this.lastAvgStrategyResult;
|
|
121
|
+
}
|
|
84
122
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/priorityFee/types.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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.
|
|
93
|
+
expect(averageOverSlots).to.approximately(545.33333, 0.00001);
|
|
94
94
|
});
|
|
95
95
|
});
|