fema-pf-calc 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.
- package/README.md +265 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +35 -0
- package/dist/config.js.map +1 -0
- package/dist/engine/feeCalculator.d.ts +19 -0
- package/dist/engine/feeCalculator.d.ts.map +1 -0
- package/dist/engine/feeCalculator.js +98 -0
- package/dist/engine/feeCalculator.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +114 -0
- package/dist/index.js.map +1 -0
- package/dist/protocols.d.ts +10 -0
- package/dist/protocols.d.ts.map +1 -0
- package/dist/protocols.js +86 -0
- package/dist/protocols.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +61 -0
- package/dist/server.js.map +1 -0
- package/dist/services/cache.d.ts +8 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +25 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/database.d.ts +6 -0
- package/dist/services/database.d.ts.map +1 -0
- package/dist/services/database.js +30 -0
- package/dist/services/database.js.map +1 -0
- package/dist/services/solana.d.ts +24 -0
- package/dist/services/solana.d.ts.map +1 -0
- package/dist/services/solana.js +168 -0
- package/dist/services/solana.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# FEMA — Fee Estimation & Management for Solana
|
|
2
|
+
|
|
3
|
+
FEMA is a TypeScript SDK that calculates optimal priority fees for Solana transactions in real time. Instead of hardcoding a fee or guessing, developers install FEMA and call one function to get the right fee based on what the network is actually paying right now.
|
|
4
|
+
|
|
5
|
+
Supports **mainnet-beta** and **devnet**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The Problem
|
|
10
|
+
|
|
11
|
+
Every Solana transaction can include a priority fee — a tip paid to validators to land the transaction faster. Set it too low and your transaction gets stuck or dropped. Set it too high and you overpay.
|
|
12
|
+
|
|
13
|
+
The fee you need also depends on what kind of transaction you are sending. A Jupiter swap competes with other Jupiter swaps. An NFT mint competes with other NFT mints. They are not in the same queue, so they should not use the same fee.
|
|
14
|
+
|
|
15
|
+
FEMA solves this by scanning recent blocks, building a real fee distribution, and giving you a number you can use directly.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## How It Works
|
|
20
|
+
|
|
21
|
+
1. FEMA connects to a Solana RPC and fetches data from the last 30 blocks
|
|
22
|
+
2. For each transaction in those blocks it extracts the fee paid in microLamports per compute unit
|
|
23
|
+
3. It sorts these into a distribution and computes percentiles (p25, p50, p75)
|
|
24
|
+
4. If you specify a protocol or pass your transaction, it filters the data to only transactions that used the same programs as yours
|
|
25
|
+
5. It returns the fee at the percentile matching your chosen speed and strategy
|
|
26
|
+
|
|
27
|
+
Results are cached for 5–10 seconds to avoid hammering the RPC on every call.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install fema-pf-calc
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { init, estimateFee } from "fema-pf-calc";
|
|
43
|
+
import { ComputeBudgetProgram } from "@solana/web3.js";
|
|
44
|
+
|
|
45
|
+
// Configure once at app startup
|
|
46
|
+
init({
|
|
47
|
+
cluster: "mainnet-beta", // or "devnet"
|
|
48
|
+
rpcUrl: "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY", // optional override
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Get the recommended fee
|
|
52
|
+
const { fee } = await estimateFee({ speed: "fast", strategy: "balanced" });
|
|
53
|
+
|
|
54
|
+
// Apply it to your transaction
|
|
55
|
+
const priorityIx = ComputeBudgetProgram.setComputeUnitPrice({
|
|
56
|
+
microLamports: fee,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That is all a developer needs to do. No RPC calls, no math, no guessing.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Options
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const result = await estimateFee({
|
|
68
|
+
speed: "slow" | "medium" | "fast", // how urgently you need it to land
|
|
69
|
+
strategy: "cheap" | "balanced" | "aggressive", // how competitive to be
|
|
70
|
+
protocol: "jupiter" | "raydium" | "orca" | "nft" | "pumpfun" | "openbook" | "system" | "token",
|
|
71
|
+
transaction: myTransaction, // pass your actual tx for automatic protocol detection
|
|
72
|
+
maxFee: 500_000, // optional cap in microLamports
|
|
73
|
+
includeStats: true, // include full distribution in response
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Speed
|
|
78
|
+
|
|
79
|
+
| Speed | Percentile | What it means |
|
|
80
|
+
|-------|-----------|---------------|
|
|
81
|
+
| `slow` | p25 | Cheaper. May take a few blocks to land. |
|
|
82
|
+
| `medium` | p50 | Balanced. Lands within a couple of blocks. |
|
|
83
|
+
| `fast` | p75 | More expensive. Designed to land in the next block. |
|
|
84
|
+
|
|
85
|
+
### Strategy
|
|
86
|
+
|
|
87
|
+
| Strategy | Adjustment | What it means |
|
|
88
|
+
|----------|-----------|---------------|
|
|
89
|
+
| `cheap` | −10 percentile points | Slightly undercut the target. Risk: might not land if the network spikes. |
|
|
90
|
+
| `balanced` | none | Hit the target exactly. Safe default for most use cases. |
|
|
91
|
+
| `aggressive` | +15 percentile points | Slightly overpay to guarantee landing. |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Protocol-Aware Estimation
|
|
96
|
+
|
|
97
|
+
The most powerful feature of FEMA. When you specify a protocol, FEMA filters the fee data to only transactions that used the same programs — giving you a fee based on your actual competition, not the whole network.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// Jupiter swap
|
|
101
|
+
const { fee: swapFee } = await estimateFee({ speed: "fast", protocol: "jupiter" });
|
|
102
|
+
|
|
103
|
+
// NFT mint
|
|
104
|
+
const { fee: nftFee } = await estimateFee({ speed: "fast", protocol: "nft" });
|
|
105
|
+
|
|
106
|
+
// Pump.fun trade
|
|
107
|
+
const { fee: pumpFee } = await estimateFee({ speed: "fast", protocol: "pumpfun" });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Alternatively, pass your actual transaction and FEMA will detect the protocol automatically:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const { fee } = await estimateFee({
|
|
114
|
+
speed: "fast",
|
|
115
|
+
transaction: mySwapTransaction,
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Supported Protocols
|
|
120
|
+
|
|
121
|
+
| Protocol | Programs Included |
|
|
122
|
+
|----------|------------------|
|
|
123
|
+
| `jupiter` | Jupiter V4, Jupiter V6 |
|
|
124
|
+
| `raydium` | Raydium AMM V4, AMM V5, CLMM |
|
|
125
|
+
| `orca` | Orca Whirlpool, Orca V1 |
|
|
126
|
+
| `nft` | Token Metadata, Candy Machine V2/V3, Auction House |
|
|
127
|
+
| `pumpfun` | Pump.fun |
|
|
128
|
+
| `openbook` | OpenBook V1, OpenBook V2 |
|
|
129
|
+
| `system` | System Program (SOL transfers) |
|
|
130
|
+
| `token` | Token Program, Token 2022, Associated Token Program |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Response
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
{
|
|
138
|
+
fee: 253637, // microLamports per CU — pass directly to setComputeUnitPrice
|
|
139
|
+
congestion: "high", // "low" | "medium" | "high"
|
|
140
|
+
percentileUsed: 75, // which percentile was used
|
|
141
|
+
sampleSize: 1203, // number of transactions analyzed
|
|
142
|
+
programFiltered: true, // true = fee is based on your protocol's txs specifically
|
|
143
|
+
|
|
144
|
+
// only included when includeStats: true
|
|
145
|
+
stats: {
|
|
146
|
+
avgFee: 180000,
|
|
147
|
+
medianFee: 28844,
|
|
148
|
+
distribution: {
|
|
149
|
+
p10: 1200,
|
|
150
|
+
p25: 5011,
|
|
151
|
+
p50: 28844,
|
|
152
|
+
p75: 253637,
|
|
153
|
+
p90: 480000,
|
|
154
|
+
p95: 820000,
|
|
155
|
+
p99: 2100000,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The `fee` value is in **microLamports per compute unit** — the exact unit that `ComputeBudgetProgram.setComputeUnitPrice` expects. No conversion needed.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Configuration
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
init({
|
|
169
|
+
cluster: "mainnet-beta", // "mainnet-beta" | "devnet" (default: "mainnet-beta")
|
|
170
|
+
rpcUrl: "https://your-rpc.com", // optional — overrides the cluster default
|
|
171
|
+
cacheDurationMs: 7000, // how long to cache results in ms (default: 7000)
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Cluster RPC defaults
|
|
176
|
+
|
|
177
|
+
| Cluster | Default RPC |
|
|
178
|
+
|---------|-------------|
|
|
179
|
+
| `mainnet-beta` | `https://api.mainnet-beta.solana.com` |
|
|
180
|
+
| `devnet` | `https://api.devnet.solana.com` |
|
|
181
|
+
|
|
182
|
+
Or configure via `.env`:
|
|
183
|
+
|
|
184
|
+
```env
|
|
185
|
+
# Network: mainnet-beta (default) or devnet
|
|
186
|
+
SOLANA_CLUSTER=mainnet-beta
|
|
187
|
+
|
|
188
|
+
# Optional: override the RPC URL for the selected cluster
|
|
189
|
+
# SOLANA_RPC_URL=https://your-custom-rpc.com
|
|
190
|
+
|
|
191
|
+
# How long to cache fee data in milliseconds (default: 7000)
|
|
192
|
+
CACHE_DURATION_MS=7000
|
|
193
|
+
|
|
194
|
+
# Express server port (default: 3000)
|
|
195
|
+
PORT=3000
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Test Script
|
|
201
|
+
|
|
202
|
+
Run a live fee report from the terminal:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Mainnet (default)
|
|
206
|
+
ts-node test-fees.ts
|
|
207
|
+
|
|
208
|
+
# Devnet
|
|
209
|
+
ts-node test-fees.ts --network devnet
|
|
210
|
+
ts-node test-fees.ts --network=devnet
|
|
211
|
+
|
|
212
|
+
# Via env var
|
|
213
|
+
SOLANA_CLUSTER=devnet ts-node test-fees.ts
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Optional HTTP Server
|
|
219
|
+
|
|
220
|
+
FEMA includes an Express server for teams who want to expose fee estimation over HTTP instead of importing the package directly.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm run server
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
| Endpoint | Description |
|
|
227
|
+
|----------|-------------|
|
|
228
|
+
| `GET /health` | Check server status and active RPC URL |
|
|
229
|
+
| `GET /estimate-fee?speed=fast&strategy=balanced` | Get a fee recommendation |
|
|
230
|
+
| `GET /stats` | Get fee with full distribution stats |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## RPC Recommendations
|
|
235
|
+
|
|
236
|
+
The public Solana RPC endpoints rate-limit aggressively. For reliable results use a private RPC:
|
|
237
|
+
|
|
238
|
+
- [Helius](https://helius.dev) — free tier available
|
|
239
|
+
- [QuickNode](https://quicknode.com)
|
|
240
|
+
- [Alchemy](https://alchemy.com)
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Project Structure
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
src/
|
|
248
|
+
├── index.ts # SDK entry point — estimateFee() and init()
|
|
249
|
+
├── server.ts # Optional Express HTTP server
|
|
250
|
+
├── types.ts # TypeScript types and interfaces
|
|
251
|
+
├── config.ts # Configuration and cluster RPC resolution
|
|
252
|
+
├── protocols.ts # Known protocol registry (Jupiter, Raydium, etc.)
|
|
253
|
+
├── engine/
|
|
254
|
+
│ └── feeCalculator.ts # Percentile math, strategy modifiers, outlier filtering
|
|
255
|
+
└── services/
|
|
256
|
+
├── solana.ts # RPC data fetching and block scanning
|
|
257
|
+
├── cache.ts # In-memory TTL cache
|
|
258
|
+
└── database.ts # In-memory snapshot store and fallback
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FemaConfig, SolanaCluster } from "./types";
|
|
2
|
+
declare const CLUSTER_RPC: Record<SolanaCluster, string>;
|
|
3
|
+
export declare function init(overrides: Partial<FemaConfig>): void;
|
|
4
|
+
export { CLUSTER_RPC };
|
|
5
|
+
export declare function getConfig(): FemaConfig;
|
|
6
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEpD,QAAA,MAAM,WAAW,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAG9C,CAAC;AAsBF,wBAAgB,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAIzD;AAED,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,wBAAgB,SAAS,IAAI,UAAU,CAEtC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CLUSTER_RPC = void 0;
|
|
4
|
+
exports.init = init;
|
|
5
|
+
exports.getConfig = getConfig;
|
|
6
|
+
const CLUSTER_RPC = {
|
|
7
|
+
"mainnet-beta": "https://api.mainnet-beta.solana.com",
|
|
8
|
+
"devnet": "https://api.devnet.solana.com",
|
|
9
|
+
};
|
|
10
|
+
exports.CLUSTER_RPC = CLUSTER_RPC;
|
|
11
|
+
const DEFAULT_CACHE_MS = 7000;
|
|
12
|
+
function resolveCluster() {
|
|
13
|
+
const raw = process.env.SOLANA_CLUSTER ?? "mainnet-beta";
|
|
14
|
+
if (raw === "devnet")
|
|
15
|
+
return "devnet";
|
|
16
|
+
return "mainnet-beta";
|
|
17
|
+
}
|
|
18
|
+
function resolveRpc(cluster) {
|
|
19
|
+
return process.env.SOLANA_RPC_URL ?? CLUSTER_RPC[cluster];
|
|
20
|
+
}
|
|
21
|
+
const defaultCluster = resolveCluster();
|
|
22
|
+
let _config = {
|
|
23
|
+
cluster: defaultCluster,
|
|
24
|
+
rpcUrl: resolveRpc(defaultCluster),
|
|
25
|
+
cacheDurationMs: Number(process.env.CACHE_DURATION_MS ?? DEFAULT_CACHE_MS),
|
|
26
|
+
};
|
|
27
|
+
function init(overrides) {
|
|
28
|
+
const cluster = overrides.cluster ?? _config.cluster;
|
|
29
|
+
const rpcUrl = overrides.rpcUrl ?? CLUSTER_RPC[cluster];
|
|
30
|
+
_config = { ..._config, ...overrides, cluster, rpcUrl };
|
|
31
|
+
}
|
|
32
|
+
function getConfig() {
|
|
33
|
+
return _config;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;AA2BA,oBAIC;AAID,8BAEC;AAnCD,MAAM,WAAW,GAAkC;IACjD,cAAc,EAAE,qCAAqC;IACrD,QAAQ,EAAE,+BAA+B;CAC1C,CAAC;AA4BO,kCAAW;AA1BpB,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,SAAS,cAAc;IACrB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,cAAc,CAAC;IACzD,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACtC,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,SAAS,UAAU,CAAC,OAAsB;IACxC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,cAAc,GAAG,cAAc,EAAE,CAAC;AAExC,IAAI,OAAO,GAAe;IACxB,OAAO,EAAE,cAAc;IACvB,MAAM,EAAE,UAAU,CAAC,cAAc,CAAC;IAClC,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,gBAAgB,CAAC;CAC3E,CAAC;AAEF,SAAgB,IAAI,CAAC,SAA8B;IACjD,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IACrD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IACxD,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC1D,CAAC;AAID,SAAgB,SAAS;IACvB,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Speed, Strategy, FeeSnapshot, FeeEntry } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Filter a fee entry dataset to entries that share at least one program
|
|
4
|
+
* with the target transaction. Falls back to the full dataset if too few matches.
|
|
5
|
+
*/
|
|
6
|
+
export declare function filterByPrograms(entries: FeeEntry[], programIds: string[]): {
|
|
7
|
+
fees: number[];
|
|
8
|
+
programFiltered: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function percentile(sorted: number[], p: number): number;
|
|
11
|
+
export declare function average(values: number[]): number;
|
|
12
|
+
export interface CalcResult {
|
|
13
|
+
fee: number;
|
|
14
|
+
percentileUsed: number;
|
|
15
|
+
snapshot: FeeSnapshot;
|
|
16
|
+
rawFees: number[];
|
|
17
|
+
}
|
|
18
|
+
export declare function calculateFee(rawFees: number[], speed: Speed, strategy: Strategy, maxFee?: number, computeUnits?: number): CalcResult;
|
|
19
|
+
//# sourceMappingURL=feeCalculator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feeCalculator.d.ts","sourceRoot":"","sources":["../../src/engine/feeCalculator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,QAAQ,EAER,WAAW,EACX,QAAQ,EACT,MAAM,UAAU,CAAC;AAIlB;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,QAAQ,EAAE,EACnB,UAAU,EAAE,MAAM,EAAE,GACnB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAgB9C;AAID,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAO9D;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAGhD;AA6BD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,WAAW,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EAAE,EACjB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,MAAM,GACpB,UAAU,CA6CZ"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterByPrograms = filterByPrograms;
|
|
4
|
+
exports.percentile = percentile;
|
|
5
|
+
exports.average = average;
|
|
6
|
+
exports.calculateFee = calculateFee;
|
|
7
|
+
const MIN_PROGRAM_SAMPLES = 15;
|
|
8
|
+
/**
|
|
9
|
+
* Filter a fee entry dataset to entries that share at least one program
|
|
10
|
+
* with the target transaction. Falls back to the full dataset if too few matches.
|
|
11
|
+
*/
|
|
12
|
+
function filterByPrograms(entries, programIds) {
|
|
13
|
+
if (programIds.length === 0) {
|
|
14
|
+
return { fees: entries.map((e) => e.fee), programFiltered: false };
|
|
15
|
+
}
|
|
16
|
+
const targetSet = new Set(programIds);
|
|
17
|
+
const matched = entries.filter((e) => e.programs.some((p) => targetSet.has(p)));
|
|
18
|
+
if (matched.length >= MIN_PROGRAM_SAMPLES) {
|
|
19
|
+
return { fees: matched.map((e) => e.fee), programFiltered: true };
|
|
20
|
+
}
|
|
21
|
+
// Not enough program-specific samples — fall back to full network data
|
|
22
|
+
return { fees: entries.map((e) => e.fee), programFiltered: false };
|
|
23
|
+
}
|
|
24
|
+
// ─── Percentile helpers ───────────────────────────────────────────────────────
|
|
25
|
+
function percentile(sorted, p) {
|
|
26
|
+
if (sorted.length === 0)
|
|
27
|
+
return 0;
|
|
28
|
+
const idx = (p / 100) * (sorted.length - 1);
|
|
29
|
+
const lower = Math.floor(idx);
|
|
30
|
+
const upper = Math.ceil(idx);
|
|
31
|
+
if (lower === upper)
|
|
32
|
+
return sorted[lower];
|
|
33
|
+
return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
|
|
34
|
+
}
|
|
35
|
+
function average(values) {
|
|
36
|
+
if (values.length === 0)
|
|
37
|
+
return 0;
|
|
38
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
39
|
+
}
|
|
40
|
+
// ─── Speed → base percentile ─────────────────────────────────────────────────
|
|
41
|
+
const SPEED_PERCENTILE = {
|
|
42
|
+
slow: 25,
|
|
43
|
+
medium: 50,
|
|
44
|
+
fast: 75,
|
|
45
|
+
};
|
|
46
|
+
// ─── Strategy → percentile delta ─────────────────────────────────────────────
|
|
47
|
+
const STRATEGY_DELTA = {
|
|
48
|
+
cheap: -10,
|
|
49
|
+
balanced: 0,
|
|
50
|
+
aggressive: 15,
|
|
51
|
+
};
|
|
52
|
+
// ─── Congestion thresholds (microLamports avg) ────────────────────────────────
|
|
53
|
+
function deriveCongestion(medianFee, txCount) {
|
|
54
|
+
// Use median (not avg) so MEV outliers don't inflate congestion level
|
|
55
|
+
if (medianFee > 100000 || txCount > 1000)
|
|
56
|
+
return "high";
|
|
57
|
+
if (medianFee > 10000 || txCount > 300)
|
|
58
|
+
return "medium";
|
|
59
|
+
return "low";
|
|
60
|
+
}
|
|
61
|
+
function calculateFee(rawFees, speed, strategy, maxFee, computeUnits) {
|
|
62
|
+
let fees = rawFees.length > 0 ? [...rawFees].sort((a, b) => a - b) : [1];
|
|
63
|
+
// Strip MEV/bot outliers above p99 so they don't distort p75/avg
|
|
64
|
+
if (fees.length >= 10) {
|
|
65
|
+
const cap = percentile(fees, 99);
|
|
66
|
+
fees = fees.filter((f) => f <= cap);
|
|
67
|
+
}
|
|
68
|
+
const basePercentile = SPEED_PERCENTILE[speed];
|
|
69
|
+
const delta = STRATEGY_DELTA[strategy];
|
|
70
|
+
const targetPercentile = Math.max(1, Math.min(99, basePercentile + delta));
|
|
71
|
+
let fee = Math.round(percentile(fees, targetPercentile));
|
|
72
|
+
const avgFee = Math.round(average(fees));
|
|
73
|
+
const medianFee = Math.round(percentile(fees, 50));
|
|
74
|
+
const p25Fee = Math.round(percentile(fees, 25));
|
|
75
|
+
const p75Fee = Math.round(percentile(fees, 75));
|
|
76
|
+
// Scale by compute units if provided (fee is per-CU; default CU = 200_000)
|
|
77
|
+
if (computeUnits && computeUnits !== 200000) {
|
|
78
|
+
fee = Math.round(fee * (computeUnits / 200000));
|
|
79
|
+
}
|
|
80
|
+
// Apply maxFee cap
|
|
81
|
+
if (maxFee !== undefined && fee > maxFee) {
|
|
82
|
+
fee = maxFee;
|
|
83
|
+
}
|
|
84
|
+
// Ensure a sensible minimum (1 microLamport)
|
|
85
|
+
fee = Math.max(1, fee);
|
|
86
|
+
const congestionLevel = deriveCongestion(medianFee, fees.length);
|
|
87
|
+
const snapshot = {
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
avgFee,
|
|
90
|
+
medianFee,
|
|
91
|
+
p25Fee,
|
|
92
|
+
p75Fee,
|
|
93
|
+
congestionLevel,
|
|
94
|
+
txCount: fees.length,
|
|
95
|
+
};
|
|
96
|
+
return { fee, percentileUsed: targetPercentile, snapshot, rawFees: fees };
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=feeCalculator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feeCalculator.js","sourceRoot":"","sources":["../../src/engine/feeCalculator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FemaConfig, EstimateFeeOptions, EstimateFeeResult } from "./types";
|
|
2
|
+
export type { EstimateFeeOptions, EstimateFeeResult, FemaConfig, FeeSnapshot, FeeStats, FeeEntry, Speed, Strategy, CongestionLevel, SolanaCluster, } from "./types";
|
|
3
|
+
export type { Protocol } from "./protocols";
|
|
4
|
+
export { PROTOCOLS } from "./protocols";
|
|
5
|
+
export declare function init(config: Partial<FemaConfig>): void;
|
|
6
|
+
export declare function estimateFee(options?: EstimateFeeOptions): Promise<EstimateFeeResult>;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,iBAAiB,EAA6B,MAAM,SAAS,CAAC;AAiBvG,YAAY,EACV,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,eAAe,EACf,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAMxC,wBAAgB,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAQtD;AAID,wBAAsB,WAAW,CAC/B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAsE5B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROTOCOLS = void 0;
|
|
4
|
+
exports.init = init;
|
|
5
|
+
exports.estimateFee = estimateFee;
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const cache_1 = require("./services/cache");
|
|
8
|
+
const solana_1 = require("./services/solana");
|
|
9
|
+
const protocols_1 = require("./protocols");
|
|
10
|
+
const database_1 = require("./services/database");
|
|
11
|
+
const feeCalculator_1 = require("./engine/feeCalculator");
|
|
12
|
+
var protocols_2 = require("./protocols");
|
|
13
|
+
Object.defineProperty(exports, "PROTOCOLS", { enumerable: true, get: function () { return protocols_2.PROTOCOLS; } });
|
|
14
|
+
// ─── SDK initialisation ───────────────────────────────────────────────────────
|
|
15
|
+
let _started = false;
|
|
16
|
+
function init(config) {
|
|
17
|
+
(0, config_1.init)(config);
|
|
18
|
+
(0, solana_1.resetConnection)();
|
|
19
|
+
if (!_started) {
|
|
20
|
+
(0, database_1.startPersistedInterval)(() => (0, database_1.getLatestSnapshotSync)(), 15000);
|
|
21
|
+
_started = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// ─── Primary API ──────────────────────────────────────────────────────────────
|
|
25
|
+
async function estimateFee(options = {}) {
|
|
26
|
+
const { speed = "medium", strategy = "balanced", includeStats = false, maxFee, transaction, protocol, } = options;
|
|
27
|
+
// 1. Resolve program IDs from protocol shorthand or raw transaction
|
|
28
|
+
let programIds = [];
|
|
29
|
+
if (protocol) {
|
|
30
|
+
programIds = (0, protocols_1.getProtocolProgramIds)(protocol);
|
|
31
|
+
}
|
|
32
|
+
else if (transaction) {
|
|
33
|
+
const txPrograms = (0, solana_1.extractProgramIds)(transaction);
|
|
34
|
+
programIds = txPrograms;
|
|
35
|
+
// Auto-detect protocol from the transaction's programs (for logging)
|
|
36
|
+
const detected = (0, protocols_1.detectProtocol)(txPrograms);
|
|
37
|
+
if (detected) {
|
|
38
|
+
// Expand to full protocol program list for better coverage
|
|
39
|
+
programIds = (0, protocols_1.getProtocolProgramIds)(detected);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const needsProgramData = programIds.length > 0;
|
|
43
|
+
const computeUnits = transaction ? (0, solana_1.estimateComputeUnits)(transaction) : undefined;
|
|
44
|
+
// 2. Try in-memory cache first (only reuse if it has program data when needed)
|
|
45
|
+
const cached = (0, cache_1.getCached)();
|
|
46
|
+
const cacheHasProgramData = cached?.entries.some((e) => e.programs.length > 0) ?? false;
|
|
47
|
+
if (cached && (!needsProgramData || cacheHasProgramData)) {
|
|
48
|
+
const { fees, programFiltered } = (0, feeCalculator_1.filterByPrograms)(cached.entries, programIds);
|
|
49
|
+
const { fee, percentileUsed, snapshot, rawFees } = (0, feeCalculator_1.calculateFee)(fees, speed, strategy, maxFee, computeUnits);
|
|
50
|
+
return buildResult(fee, snapshot, percentileUsed, rawFees, includeStats, programFiltered);
|
|
51
|
+
}
|
|
52
|
+
// 3. Fetch fresh data from Solana RPC
|
|
53
|
+
let entries = [];
|
|
54
|
+
let fromFallback = false;
|
|
55
|
+
try {
|
|
56
|
+
entries = await (0, solana_1.fetchRecentPriorityFees)(30, needsProgramData);
|
|
57
|
+
if (entries.length === 0)
|
|
58
|
+
throw new Error("No fee data returned from RPC");
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.warn("[FEMA] RPC fetch failed, using fallback:", err.message);
|
|
62
|
+
fromFallback = true;
|
|
63
|
+
}
|
|
64
|
+
// 4. Fallback: most recent in-memory snapshot
|
|
65
|
+
if (fromFallback || entries.length === 0) {
|
|
66
|
+
const cached = (0, database_1.getLatestSnapshotSync)();
|
|
67
|
+
const fallbackFees = cached
|
|
68
|
+
? reconstructFees(cached.p25Fee, cached.medianFee, cached.p75Fee)
|
|
69
|
+
: [1000];
|
|
70
|
+
entries = fallbackFees.map((fee) => ({ fee, programs: [] }));
|
|
71
|
+
}
|
|
72
|
+
// 5. Filter to program-relevant transactions (if transaction was provided)
|
|
73
|
+
const { fees, programFiltered } = (0, feeCalculator_1.filterByPrograms)(entries, programIds);
|
|
74
|
+
// 6. Calculate
|
|
75
|
+
const { fee, percentileUsed, snapshot, rawFees } = (0, feeCalculator_1.calculateFee)(fees, speed, strategy, maxFee, computeUnits);
|
|
76
|
+
// 7. Update cache & latest snapshot reference
|
|
77
|
+
(0, cache_1.setCache)(snapshot, entries);
|
|
78
|
+
(0, database_1.setLatestSnapshot)(snapshot);
|
|
79
|
+
return buildResult(fee, snapshot, percentileUsed, rawFees, includeStats, programFiltered);
|
|
80
|
+
}
|
|
81
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
82
|
+
function buildResult(fee, snapshot, percentileUsed, rawFees, includeStats, programFiltered) {
|
|
83
|
+
const result = {
|
|
84
|
+
fee,
|
|
85
|
+
congestion: snapshot.congestionLevel,
|
|
86
|
+
percentileUsed,
|
|
87
|
+
sampleSize: rawFees.length,
|
|
88
|
+
programFiltered,
|
|
89
|
+
};
|
|
90
|
+
if (includeStats) {
|
|
91
|
+
result.stats = {
|
|
92
|
+
avgFee: snapshot.avgFee,
|
|
93
|
+
medianFee: snapshot.medianFee,
|
|
94
|
+
distribution: buildDistribution(rawFees),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
function buildDistribution(sortedFees) {
|
|
100
|
+
const p = (n) => Math.round((0, feeCalculator_1.percentile)(sortedFees, n));
|
|
101
|
+
return { p10: p(10), p25: p(25), p50: p(50), p75: p(75), p90: p(90), p95: p(95), p99: p(99) };
|
|
102
|
+
}
|
|
103
|
+
function reconstructFees(p25, median, p75) {
|
|
104
|
+
return [
|
|
105
|
+
Math.round(p25 * 0.5),
|
|
106
|
+
p25,
|
|
107
|
+
Math.round((p25 + median) / 2),
|
|
108
|
+
median,
|
|
109
|
+
Math.round((median + p75) / 2),
|
|
110
|
+
p75,
|
|
111
|
+
Math.round(p75 * 1.5),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAoCA,oBAQC;AAID,kCAwEC;AAvHD,qCAA+C;AAC/C,4CAAuD;AACvD,8CAK2B;AAC3B,2CAA+E;AAC/E,kDAI6B;AAC7B,0DAAoF;AAepF,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAElB,iFAAiF;AAEjF,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,SAAgB,IAAI,CAAC,MAA2B;IAC9C,IAAA,aAAW,EAAC,MAAM,CAAC,CAAC;IACpB,IAAA,wBAAe,GAAE,CAAC;IAElB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAA,iCAAsB,EAAC,GAAG,EAAE,CAAC,IAAA,gCAAqB,GAAE,EAAE,KAAM,CAAC,CAAC;QAC9D,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;AACH,CAAC;AAED,iFAAiF;AAE1E,KAAK,UAAU,WAAW,CAC/B,UAA8B,EAAE;IAEhC,MAAM,EACJ,KAAK,GAAG,QAAQ,EAChB,QAAQ,GAAG,UAAU,EACrB,YAAY,GAAG,KAAK,EACpB,MAAM,EACN,WAAW,EACX,QAAQ,GACT,GAAG,OAAO,CAAC;IAEZ,oEAAoE;IACpE,IAAI,UAAU,GAAa,EAAE,CAAC;IAC9B,IAAI,QAAQ,EAAE,CAAC;QACb,UAAU,GAAG,IAAA,iCAAqB,EAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;SAAM,IAAI,WAAW,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,IAAA,0BAAiB,EAAC,WAAW,CAAC,CAAC;QAClD,UAAU,GAAG,UAAU,CAAC;QACxB,qEAAqE;QACrE,MAAM,QAAQ,GAAG,IAAA,0BAAc,EAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,QAAQ,EAAE,CAAC;YACb,2DAA2D;YAC3D,UAAU,GAAG,IAAA,iCAAqB,EAAC,QAAQ,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,IAAA,6BAAoB,EAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEjF,+EAA+E;IAC/E,MAAM,MAAM,GAAG,IAAA,iBAAS,GAAE,CAAC;IAC3B,MAAM,mBAAmB,GAAG,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC;IAExF,IAAI,MAAM,IAAI,CAAC,CAAC,gBAAgB,IAAI,mBAAmB,CAAC,EAAE,CAAC;QACzD,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,IAAA,gCAAgB,EAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAC/E,MAAM,EAAE,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAA,4BAAY,EAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;QAC7G,OAAO,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;IAC5F,CAAC;IAED,sCAAsC;IACtC,IAAI,OAAO,GAAe,EAAE,CAAC;IAC7B,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,IAAA,gCAAuB,EAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,0CAA0C,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;QACjF,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,8CAA8C;IAC9C,IAAI,YAAY,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,IAAA,gCAAqB,GAAE,CAAC;QACvC,MAAM,YAAY,GAAG,MAAM;YACzB,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC;YACjE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACX,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,2EAA2E;IAC3E,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,IAAA,gCAAgB,EAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAExE,eAAe;IACf,MAAM,EAAE,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAA,4BAAY,EAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAE7G,8CAA8C;IAC9C,IAAA,gBAAQ,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5B,IAAA,4BAAiB,EAAC,QAAQ,CAAC,CAAC;IAE5B,OAAO,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;AAC5F,CAAC;AAED,iFAAiF;AAEjF,SAAS,WAAW,CAClB,GAAW,EACX,QAAqD,EACrD,cAAsB,EACtB,OAAiB,EACjB,YAAqB,EACrB,eAAwB;IAExB,MAAM,MAAM,GAAsB;QAChC,GAAG;QACH,UAAU,EAAE,QAAQ,CAAC,eAAe;QACpC,cAAc;QACd,UAAU,EAAE,OAAO,CAAC,MAAM;QAC1B,eAAe;KAChB,CAAC;IAEF,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,CAAC,KAAK,GAAG;YACb,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,YAAY,EAAE,iBAAiB,CAAC,OAAO,CAAC;SACzC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,iBAAiB,CAAC,UAAoB;IAC7C,MAAM,CAAC,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAA,0BAAU,EAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAChG,CAAC;AAED,SAAS,eAAe,CAAC,GAAW,EAAE,MAAc,EAAE,GAAW;IAC/D,OAAO;QACL,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;QACrB,GAAG;QACH,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM;QACN,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,GAAG;QACH,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;KACtB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Protocol = "jupiter" | "raydium" | "orca" | "nft" | "pumpfun" | "openbook" | "system" | "token";
|
|
2
|
+
export interface ProtocolInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
programIds: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare const PROTOCOLS: Record<Protocol, ProtocolInfo>;
|
|
8
|
+
export declare function getProtocolProgramIds(protocol: Protocol): string[];
|
|
9
|
+
export declare function detectProtocol(programIds: string[]): Protocol | null;
|
|
10
|
+
//# sourceMappingURL=protocols.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocols.d.ts","sourceRoot":"","sources":["../src/protocols.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAChB,SAAS,GACT,SAAS,GACT,MAAM,GACN,KAAK,GACL,SAAS,GACT,UAAU,GACV,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,YAAY,CA0EpD,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAElE;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,QAAQ,GAAG,IAAI,CAUpE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROTOCOLS = void 0;
|
|
4
|
+
exports.getProtocolProgramIds = getProtocolProgramIds;
|
|
5
|
+
exports.detectProtocol = detectProtocol;
|
|
6
|
+
exports.PROTOCOLS = {
|
|
7
|
+
jupiter: {
|
|
8
|
+
name: "Jupiter Aggregator",
|
|
9
|
+
description: "Token swaps via Jupiter V6",
|
|
10
|
+
programIds: [
|
|
11
|
+
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", // Jupiter V6
|
|
12
|
+
"JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB", // Jupiter V4
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
raydium: {
|
|
16
|
+
name: "Raydium",
|
|
17
|
+
description: "Swaps and liquidity on Raydium AMM / CLMM",
|
|
18
|
+
programIds: [
|
|
19
|
+
"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", // Raydium AMM V4
|
|
20
|
+
"CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", // Raydium CLMM
|
|
21
|
+
"5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h", // Raydium AMM V5
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
orca: {
|
|
25
|
+
name: "Orca Whirlpools",
|
|
26
|
+
description: "Swaps on Orca concentrated liquidity pools",
|
|
27
|
+
programIds: [
|
|
28
|
+
"whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", // Orca Whirlpool
|
|
29
|
+
"9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP", // Orca V1
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
nft: {
|
|
33
|
+
name: "NFT / Metaplex",
|
|
34
|
+
description: "NFT mints, transfers, and metadata via Metaplex",
|
|
35
|
+
programIds: [
|
|
36
|
+
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", // Token Metadata
|
|
37
|
+
"cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ", // Candy Machine V3
|
|
38
|
+
"CndyV3LdqHUfDLmd1X2Rxm9e6CFBe8F6Qkx6kWX6YMEZ", // Candy Machine V2
|
|
39
|
+
"hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk", // Auction House
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
pumpfun: {
|
|
43
|
+
name: "Pump.fun",
|
|
44
|
+
description: "Token launches and trades on Pump.fun",
|
|
45
|
+
programIds: [
|
|
46
|
+
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", // Pump.fun program
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
openbook: {
|
|
50
|
+
name: "OpenBook",
|
|
51
|
+
description: "Order book trading on OpenBook (formerly Serum)",
|
|
52
|
+
programIds: [
|
|
53
|
+
"srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX", // OpenBook V1
|
|
54
|
+
"opnb2LAfJYbRMAHHvqjCwQxanZn7n734bNfWLcQnB8c", // OpenBook V2
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
system: {
|
|
58
|
+
name: "System Program",
|
|
59
|
+
description: "SOL transfers and basic account operations",
|
|
60
|
+
programIds: [
|
|
61
|
+
"11111111111111111111111111111111", // System Program
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
token: {
|
|
65
|
+
name: "Token Program",
|
|
66
|
+
description: "SPL token transfers and token account operations",
|
|
67
|
+
programIds: [
|
|
68
|
+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // Token Program
|
|
69
|
+
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", // Token 2022
|
|
70
|
+
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJe1bj", // Associated Token Program
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
function getProtocolProgramIds(protocol) {
|
|
75
|
+
return exports.PROTOCOLS[protocol].programIds;
|
|
76
|
+
}
|
|
77
|
+
function detectProtocol(programIds) {
|
|
78
|
+
const idSet = new Set(programIds);
|
|
79
|
+
for (const [protocol, info] of Object.entries(exports.PROTOCOLS)) {
|
|
80
|
+
if (info.programIds.some((id) => idSet.has(id))) {
|
|
81
|
+
return protocol;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=protocols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocols.js","sourceRoot":"","sources":["../src/protocols.ts"],"names":[],"mappings":";;;AA4FA,sDAEC;AAED,wCAUC;AA1FY,QAAA,SAAS,GAAmC;IACvD,OAAO,EAAE;QACP,IAAI,EAAE,oBAAoB;QAC1B,WAAW,EAAE,4BAA4B;QACzC,UAAU,EAAE;YACV,6CAA6C,EAAE,aAAa;YAC5D,6CAA6C,EAAG,aAAa;SAC9D;KACF;IAED,OAAO,EAAE;QACP,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,2CAA2C;QACxD,UAAU,EAAE;YACV,8CAA8C,EAAE,iBAAiB;YACjE,8CAA8C,EAAE,eAAe;YAC/D,8CAA8C,EAAE,iBAAiB;SAClE;KACF;IAED,IAAI,EAAE;QACJ,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,4CAA4C;QACzD,UAAU,EAAE;YACV,6CAA6C,EAAG,iBAAiB;YACjE,8CAA8C,EAAE,UAAU;SAC3D;KACF;IAED,GAAG,EAAE;QACH,IAAI,EAAE,gBAAgB;QACtB,WAAW,EAAE,iDAAiD;QAC9D,UAAU,EAAE;YACV,6CAA6C,EAAG,iBAAiB;YACjE,6CAA6C,EAAG,mBAAmB;YACnE,8CAA8C,EAAE,mBAAmB;YACnE,6CAA6C,EAAG,gBAAgB;SACjE;KACF;IAED,OAAO,EAAE;QACP,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,uCAAuC;QACpD,UAAU,EAAE;YACV,6CAA6C,EAAG,mBAAmB;SACpE;KACF;IAED,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,iDAAiD;QAC9D,UAAU,EAAE;YACV,6CAA6C,EAAG,cAAc;YAC9D,6CAA6C,EAAG,cAAc;SAC/D;KACF;IAED,MAAM,EAAE;QACN,IAAI,EAAE,gBAAgB;QACtB,WAAW,EAAE,4CAA4C;QACzD,UAAU,EAAE;YACV,kCAAkC,EAAgB,iBAAiB;SACpE;KACF;IAED,KAAK,EAAE;QACL,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,kDAAkD;QAC/D,UAAU,EAAE;YACV,6CAA6C,EAAG,gBAAgB;YAChE,6CAA6C,EAAG,aAAa;YAC7D,6CAA6C,EAAG,2BAA2B;SAC5E;KACF;CACF,CAAC;AAEF,SAAgB,qBAAqB,CAAC,QAAkB;IACtD,OAAO,iBAAS,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC;AACxC,CAAC;AAED,SAAgB,cAAc,CAAC,UAAoB;IACjD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAElC,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,iBAAS,CAA+B,EAAE,CAAC;QACvF,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAChD,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,GAAG,6CAAY,CAAC;AAiDtB,eAAe,GAAG,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
const express_1 = __importDefault(require("express"));
|
|
7
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
+
const index_1 = require("./index");
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
dotenv_1.default.config();
|
|
11
|
+
// Initialise SDK from environment
|
|
12
|
+
(0, index_1.init)({
|
|
13
|
+
cacheDurationMs: process.env.CACHE_DURATION_MS
|
|
14
|
+
? Number(process.env.CACHE_DURATION_MS)
|
|
15
|
+
: undefined,
|
|
16
|
+
});
|
|
17
|
+
const app = (0, express_1.default)();
|
|
18
|
+
app.use(express_1.default.json());
|
|
19
|
+
// ─── GET /health ──────────────────────────────────────────────────────────────
|
|
20
|
+
app.get("/health", (_req, res) => {
|
|
21
|
+
res.json({ status: "ok", rpcUrl: (0, config_1.getConfig)().rpcUrl });
|
|
22
|
+
});
|
|
23
|
+
// ─── GET /estimate-fee ────────────────────────────────────────────────────────
|
|
24
|
+
app.get("/estimate-fee", async (req, res, next) => {
|
|
25
|
+
try {
|
|
26
|
+
const { speed, strategy, includeStats, maxFee } = req.query;
|
|
27
|
+
const result = await (0, index_1.estimateFee)({
|
|
28
|
+
speed: speed ?? "medium",
|
|
29
|
+
strategy: strategy ?? "balanced",
|
|
30
|
+
includeStats: includeStats === "true",
|
|
31
|
+
maxFee: maxFee ? Number(maxFee) : undefined,
|
|
32
|
+
});
|
|
33
|
+
res.json(result);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
next(err);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// ─── GET /stats ───────────────────────────────────────────────────────────────
|
|
40
|
+
// Returns a comprehensive snapshot with stats
|
|
41
|
+
app.get("/stats", async (_req, res, next) => {
|
|
42
|
+
try {
|
|
43
|
+
const result = await (0, index_1.estimateFee)({ speed: "medium", includeStats: true });
|
|
44
|
+
res.json(result);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
next(err);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// ─── Error handler ────────────────────────────────────────────────────────────
|
|
51
|
+
app.use((err, _req, res, _next) => {
|
|
52
|
+
console.error("[FEMA server error]", err.message);
|
|
53
|
+
res.status(500).json({ error: err.message });
|
|
54
|
+
});
|
|
55
|
+
const PORT = Number(process.env.PORT ?? 3000);
|
|
56
|
+
app.listen(PORT, () => {
|
|
57
|
+
console.log(`[FEMA] Server running on http://localhost:${PORT}`);
|
|
58
|
+
console.log(`[FEMA] RPC endpoint: ${(0, config_1.getConfig)().rpcUrl}`);
|
|
59
|
+
});
|
|
60
|
+
exports.default = app;
|
|
61
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FeeSnapshot, FeeEntry } from "../types";
|
|
2
|
+
export declare function getCached(): {
|
|
3
|
+
snapshot: FeeSnapshot;
|
|
4
|
+
entries: FeeEntry[];
|
|
5
|
+
} | null;
|
|
6
|
+
export declare function setCache(snapshot: FeeSnapshot, entries: FeeEntry[]): void;
|
|
7
|
+
export declare function clearCache(): void;
|
|
8
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/services/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAWjD,wBAAgB,SAAS,IAAI;IAAE,QAAQ,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,QAAQ,EAAE,CAAA;CAAE,GAAG,IAAI,CAKjF;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAOzE;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCached = getCached;
|
|
4
|
+
exports.setCache = setCache;
|
|
5
|
+
exports.clearCache = clearCache;
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
let _entry = null;
|
|
8
|
+
function getCached() {
|
|
9
|
+
if (_entry && Date.now() < _entry.expiresAt) {
|
|
10
|
+
return { snapshot: _entry.snapshot, entries: _entry.entries };
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function setCache(snapshot, entries) {
|
|
15
|
+
const { cacheDurationMs } = (0, config_1.getConfig)();
|
|
16
|
+
_entry = {
|
|
17
|
+
snapshot,
|
|
18
|
+
entries,
|
|
19
|
+
expiresAt: Date.now() + cacheDurationMs,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function clearCache() {
|
|
23
|
+
_entry = null;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/services/cache.ts"],"names":[],"mappings":";;AAWA,8BAKC;AAED,4BAOC;AAED,gCAEC;AA5BD,sCAAsC;AAQtC,IAAI,MAAM,GAAsB,IAAI,CAAC;AAErC,SAAgB,SAAS;IACvB,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,QAAQ,CAAC,QAAqB,EAAE,OAAmB;IACjE,MAAM,EAAE,eAAe,EAAE,GAAG,IAAA,kBAAS,GAAE,CAAC;IACxC,MAAM,GAAG;QACP,QAAQ;QACR,OAAO;QACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe;KACxC,CAAC;AACJ,CAAC;AAED,SAAgB,UAAU;IACxB,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FeeSnapshot } from "../types";
|
|
2
|
+
export declare function setLatestSnapshot(s: FeeSnapshot): void;
|
|
3
|
+
export declare function getLatestSnapshotSync(): FeeSnapshot | null;
|
|
4
|
+
export declare function startPersistedInterval(getSnapshot: () => FeeSnapshot | null, intervalMs?: number): void;
|
|
5
|
+
export declare function stopPersistedInterval(): void;
|
|
6
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/services/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAKvC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAEtD;AAED,wBAAgB,qBAAqB,IAAI,WAAW,GAAG,IAAI,CAE1D;AAED,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,EACrC,UAAU,SAAS,GAClB,IAAI,CAON;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setLatestSnapshot = setLatestSnapshot;
|
|
4
|
+
exports.getLatestSnapshotSync = getLatestSnapshotSync;
|
|
5
|
+
exports.startPersistedInterval = startPersistedInterval;
|
|
6
|
+
exports.stopPersistedInterval = stopPersistedInterval;
|
|
7
|
+
let _latestSnapshot = null;
|
|
8
|
+
let _persistInterval = null;
|
|
9
|
+
function setLatestSnapshot(s) {
|
|
10
|
+
_latestSnapshot = s;
|
|
11
|
+
}
|
|
12
|
+
function getLatestSnapshotSync() {
|
|
13
|
+
return _latestSnapshot;
|
|
14
|
+
}
|
|
15
|
+
function startPersistedInterval(getSnapshot, intervalMs = 15000) {
|
|
16
|
+
if (_persistInterval)
|
|
17
|
+
return;
|
|
18
|
+
_persistInterval = setInterval(() => {
|
|
19
|
+
const snapshot = getSnapshot();
|
|
20
|
+
if (snapshot)
|
|
21
|
+
setLatestSnapshot(snapshot);
|
|
22
|
+
}, intervalMs);
|
|
23
|
+
}
|
|
24
|
+
function stopPersistedInterval() {
|
|
25
|
+
if (_persistInterval) {
|
|
26
|
+
clearInterval(_persistInterval);
|
|
27
|
+
_persistInterval = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/services/database.ts"],"names":[],"mappings":";;AAKA,8CAEC;AAED,sDAEC;AAED,wDAUC;AAED,sDAKC;AA5BD,IAAI,eAAe,GAAuB,IAAI,CAAC;AAC/C,IAAI,gBAAgB,GAA0C,IAAI,CAAC;AAEnE,SAAgB,iBAAiB,CAAC,CAAc;IAC9C,eAAe,GAAG,CAAC,CAAC;AACtB,CAAC;AAED,SAAgB,qBAAqB;IACnC,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,SAAgB,sBAAsB,CACpC,WAAqC,EACrC,UAAU,GAAG,KAAM;IAEnB,IAAI,gBAAgB;QAAE,OAAO;IAE7B,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;QAClC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,IAAI,QAAQ;YAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC,EAAE,UAAU,CAAC,CAAC;AACjB,CAAC;AAED,SAAgB,qBAAqB;IACnC,IAAI,gBAAgB,EAAE,CAAC;QACrB,aAAa,CAAC,gBAAgB,CAAC,CAAC;QAChC,gBAAgB,GAAG,IAAI,CAAC;IAC1B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Transaction, VersionedTransaction } from "@solana/web3.js";
|
|
2
|
+
import { FeeEntry } from "../types";
|
|
3
|
+
export declare function resetConnection(): void;
|
|
4
|
+
/**
|
|
5
|
+
* Fetch fee entries from recent blocks.
|
|
6
|
+
*
|
|
7
|
+
* - needsProgramData = false (no protocol/transaction passed):
|
|
8
|
+
* Uses getRecentPrioritizationFees — fast, no program info needed.
|
|
9
|
+
*
|
|
10
|
+
* - needsProgramData = true (protocol or transaction passed):
|
|
11
|
+
* Uses full block scan so each entry carries program IDs for accurate filtering.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fetchRecentPriorityFees(blockCount?: number, needsProgramData?: boolean): Promise<FeeEntry[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Extract program IDs from a user-supplied Transaction or VersionedTransaction.
|
|
16
|
+
* This is what we use to filter the fee dataset to relevant transactions.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractProgramIds(tx: Transaction | VersionedTransaction): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Estimate the compute units a transaction is likely to consume.
|
|
21
|
+
* Parses ComputeBudgetProgram instructions if present; otherwise returns a default.
|
|
22
|
+
*/
|
|
23
|
+
export declare function estimateComputeUnits(tx: Transaction | VersionedTransaction): number;
|
|
24
|
+
//# sourceMappingURL=solana.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana.d.ts","sourceRoot":"","sources":["../../src/services/solana.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EACX,oBAAoB,EAGrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAWpC,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,GAAE,MAAW,EACvB,gBAAgB,GAAE,OAAe,GAChC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAqBrB;AAsGD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,WAAW,GAAG,oBAAoB,GACrC,MAAM,EAAE,CAmBV;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,WAAW,GAAG,oBAAoB,GACrC,MAAM,CAgCR"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resetConnection = resetConnection;
|
|
4
|
+
exports.fetchRecentPriorityFees = fetchRecentPriorityFees;
|
|
5
|
+
exports.extractProgramIds = extractProgramIds;
|
|
6
|
+
exports.estimateComputeUnits = estimateComputeUnits;
|
|
7
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
let _connection = null;
|
|
10
|
+
function getConnection() {
|
|
11
|
+
if (!_connection) {
|
|
12
|
+
_connection = new web3_js_1.Connection((0, config_1.getConfig)().rpcUrl, "confirmed");
|
|
13
|
+
}
|
|
14
|
+
return _connection;
|
|
15
|
+
}
|
|
16
|
+
function resetConnection() {
|
|
17
|
+
_connection = null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Fetch fee entries from recent blocks.
|
|
21
|
+
*
|
|
22
|
+
* - needsProgramData = false (no protocol/transaction passed):
|
|
23
|
+
* Uses getRecentPrioritizationFees — fast, no program info needed.
|
|
24
|
+
*
|
|
25
|
+
* - needsProgramData = true (protocol or transaction passed):
|
|
26
|
+
* Uses full block scan so each entry carries program IDs for accurate filtering.
|
|
27
|
+
*/
|
|
28
|
+
async function fetchRecentPriorityFees(blockCount = 30, needsProgramData = false) {
|
|
29
|
+
const connection = getConnection();
|
|
30
|
+
if (!needsProgramData) {
|
|
31
|
+
// Fast path — just need general fee distribution
|
|
32
|
+
try {
|
|
33
|
+
const recentFees = await connection.getRecentPrioritizationFees();
|
|
34
|
+
if (recentFees.length > 0) {
|
|
35
|
+
const entries = recentFees
|
|
36
|
+
.slice(0, blockCount)
|
|
37
|
+
.filter((e) => e.prioritizationFee > 0)
|
|
38
|
+
.map((e) => ({ fee: e.prioritizationFee, programs: [] }));
|
|
39
|
+
if (entries.length >= 10)
|
|
40
|
+
return entries;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// fall through to block scan
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Program-aware path — full block scan with program IDs per transaction
|
|
48
|
+
return scanBlocks(connection, blockCount);
|
|
49
|
+
}
|
|
50
|
+
async function scanBlocks(connection, blockCount) {
|
|
51
|
+
const slot = await connection.getSlot();
|
|
52
|
+
const targetSlots = [];
|
|
53
|
+
for (let i = 0; i < blockCount; i++) {
|
|
54
|
+
targetSlots.push(slot - i);
|
|
55
|
+
}
|
|
56
|
+
const entries = [];
|
|
57
|
+
await Promise.all(targetSlots.map(async (s) => {
|
|
58
|
+
try {
|
|
59
|
+
const block = await connection.getBlock(s, {
|
|
60
|
+
maxSupportedTransactionVersion: 0,
|
|
61
|
+
rewards: false,
|
|
62
|
+
transactionDetails: "full",
|
|
63
|
+
});
|
|
64
|
+
if (!block?.transactions)
|
|
65
|
+
return;
|
|
66
|
+
for (const tx of block.transactions) {
|
|
67
|
+
const meta = tx.meta;
|
|
68
|
+
if (!meta)
|
|
69
|
+
continue;
|
|
70
|
+
const totalFeeLamports = meta.fee ?? 0;
|
|
71
|
+
const cuConsumed = meta.computeUnitsConsumed ?? 0;
|
|
72
|
+
const sigCount = "signatures" in tx.transaction
|
|
73
|
+
? tx.transaction.signatures.length
|
|
74
|
+
: 1;
|
|
75
|
+
const baseFee = 5000 * sigCount;
|
|
76
|
+
const priorityFeeLamports = totalFeeLamports - baseFee;
|
|
77
|
+
if (priorityFeeLamports <= 0 || cuConsumed <= 0)
|
|
78
|
+
continue;
|
|
79
|
+
const microLamportsPerCU = Math.round((priorityFeeLamports * 1000000) / cuConsumed);
|
|
80
|
+
if (microLamportsPerCU <= 0)
|
|
81
|
+
continue;
|
|
82
|
+
// Extract program IDs from the transaction's instructions
|
|
83
|
+
const programs = extractProgramIdsFromBlock(tx.transaction);
|
|
84
|
+
entries.push({ fee: microLamportsPerCU, programs });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// skip failed blocks silently
|
|
89
|
+
}
|
|
90
|
+
}));
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
/** Pull program IDs out of a raw block transaction (legacy or versioned). */
|
|
94
|
+
function extractProgramIdsFromBlock(tx) {
|
|
95
|
+
try {
|
|
96
|
+
const t = tx;
|
|
97
|
+
const msg = t.message;
|
|
98
|
+
if (!msg)
|
|
99
|
+
return [];
|
|
100
|
+
// Account key list (legacy)
|
|
101
|
+
const keys = (msg.accountKeys ?? msg.staticAccountKeys ?? []).map((k) => (typeof k.toBase58 === "function"
|
|
102
|
+
? k.toBase58()
|
|
103
|
+
: String(k)));
|
|
104
|
+
const ixs = msg.instructions ?? msg.compiledInstructions ?? [];
|
|
105
|
+
const programIds = new Set();
|
|
106
|
+
for (const ix of ixs) {
|
|
107
|
+
const id = keys[ix.programIdIndex];
|
|
108
|
+
if (id)
|
|
109
|
+
programIds.add(id);
|
|
110
|
+
}
|
|
111
|
+
return Array.from(programIds);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract program IDs from a user-supplied Transaction or VersionedTransaction.
|
|
119
|
+
* This is what we use to filter the fee dataset to relevant transactions.
|
|
120
|
+
*/
|
|
121
|
+
function extractProgramIds(tx) {
|
|
122
|
+
try {
|
|
123
|
+
if (tx instanceof web3_js_1.Transaction) {
|
|
124
|
+
return [
|
|
125
|
+
...new Set(tx.instructions.map((ix) => ix.programId.toBase58())),
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
// VersionedTransaction
|
|
129
|
+
const msg = tx.message;
|
|
130
|
+
const keys = msg.staticAccountKeys.map((k) => k.toBase58());
|
|
131
|
+
return [
|
|
132
|
+
...new Set(msg.compiledInstructions.map((ix) => keys[ix.programIdIndex])),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Estimate the compute units a transaction is likely to consume.
|
|
141
|
+
* Parses ComputeBudgetProgram instructions if present; otherwise returns a default.
|
|
142
|
+
*/
|
|
143
|
+
function estimateComputeUnits(tx) {
|
|
144
|
+
const DEFAULT_CU = 200000;
|
|
145
|
+
try {
|
|
146
|
+
const instructions = tx instanceof web3_js_1.Transaction
|
|
147
|
+
? tx.instructions
|
|
148
|
+
: tx.message.compiledInstructions;
|
|
149
|
+
const COMPUTE_BUDGET_PROGRAM_ID = web3_js_1.ComputeBudgetProgram.programId;
|
|
150
|
+
for (const ix of instructions) {
|
|
151
|
+
const programId = "programId" in ix
|
|
152
|
+
? ix.programId
|
|
153
|
+
: null;
|
|
154
|
+
if (programId && programId.equals(COMPUTE_BUDGET_PROGRAM_ID)) {
|
|
155
|
+
const data = "data" in ix ? ix.data : new Uint8Array();
|
|
156
|
+
// Instruction type 2 = SetComputeUnitLimit
|
|
157
|
+
if (data[0] === 2 && data.length >= 5) {
|
|
158
|
+
return new DataView(data.buffer, data.byteOffset).getUint32(1, true);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// ignore parse errors
|
|
165
|
+
}
|
|
166
|
+
return DEFAULT_CU;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=solana.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana.js","sourceRoot":"","sources":["../../src/services/solana.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Transaction, VersionedTransaction } from "@solana/web3.js";
|
|
2
|
+
import { Protocol } from "./protocols";
|
|
3
|
+
export type SolanaCluster = "mainnet-beta" | "devnet";
|
|
4
|
+
export interface FemaConfig {
|
|
5
|
+
rpcUrl: string;
|
|
6
|
+
cluster: SolanaCluster;
|
|
7
|
+
cacheDurationMs: number;
|
|
8
|
+
}
|
|
9
|
+
export type Speed = "slow" | "medium" | "fast";
|
|
10
|
+
export type Strategy = "cheap" | "balanced" | "aggressive";
|
|
11
|
+
export type CongestionLevel = "low" | "medium" | "high";
|
|
12
|
+
export interface EstimateFeeOptions {
|
|
13
|
+
speed?: Speed;
|
|
14
|
+
strategy?: Strategy;
|
|
15
|
+
includeStats?: boolean;
|
|
16
|
+
maxFee?: number;
|
|
17
|
+
transaction?: Transaction | VersionedTransaction;
|
|
18
|
+
protocol?: Protocol;
|
|
19
|
+
}
|
|
20
|
+
export interface FeeDistribution {
|
|
21
|
+
p10: number;
|
|
22
|
+
p25: number;
|
|
23
|
+
p50: number;
|
|
24
|
+
p75: number;
|
|
25
|
+
p90: number;
|
|
26
|
+
p95: number;
|
|
27
|
+
p99: number;
|
|
28
|
+
}
|
|
29
|
+
export interface FeeStats {
|
|
30
|
+
avgFee: number;
|
|
31
|
+
medianFee: number;
|
|
32
|
+
distribution: FeeDistribution;
|
|
33
|
+
}
|
|
34
|
+
export interface EstimateFeeResult {
|
|
35
|
+
fee: number;
|
|
36
|
+
congestion: CongestionLevel;
|
|
37
|
+
percentileUsed: number;
|
|
38
|
+
sampleSize: number;
|
|
39
|
+
programFiltered: boolean;
|
|
40
|
+
stats?: FeeStats;
|
|
41
|
+
}
|
|
42
|
+
export interface FeeEntry {
|
|
43
|
+
fee: number;
|
|
44
|
+
programs: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface FeeSnapshot {
|
|
47
|
+
timestamp: string;
|
|
48
|
+
avgFee: number;
|
|
49
|
+
medianFee: number;
|
|
50
|
+
p25Fee: number;
|
|
51
|
+
p75Fee: number;
|
|
52
|
+
congestionLevel: CongestionLevel;
|
|
53
|
+
txCount: number;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvC,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,QAAQ,CAAC;AAEtD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAID,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC/C,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,YAAY,CAAC;AAC3D,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,WAAW,GAAG,oBAAoB,CAAC;IACjD,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,eAAe,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,eAAe,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAID,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAID,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fema-pf-calc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Intelligent Solana priority fee estimation engine",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=16"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"dev": "ts-node test-fees.ts",
|
|
24
|
+
"server": "ts-node src/server.ts",
|
|
25
|
+
"start": "node dist/server.js",
|
|
26
|
+
"lint": "eslint src/**/*.ts"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"solana",
|
|
30
|
+
"priority-fee",
|
|
31
|
+
"web3",
|
|
32
|
+
"blockchain"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@solana/web3.js": "^1.x"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@solana/web3.js": "^1.98.0",
|
|
40
|
+
"@types/express": "^5.0.1",
|
|
41
|
+
"@types/node": "^22.15.2",
|
|
42
|
+
"dotenv": "^16.5.0",
|
|
43
|
+
"express": "^4.21.2",
|
|
44
|
+
"ts-node": "^10.9.2",
|
|
45
|
+
"typescript": "^5.8.3"
|
|
46
|
+
}
|
|
47
|
+
}
|