@forgedevv/core 0.1.0 → 0.3.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/CHANGELOG.md +227 -4
- package/README.md +4 -3
- package/dist/addresses.d.ts +73 -7
- package/dist/addresses.js +96 -5
- package/dist/claims.d.ts +43 -6
- package/dist/claims.js +75 -16
- package/dist/config.d.ts +48 -37
- package/dist/config.js +282 -52
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +20 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +15 -4
- package/dist/jupiter-referral.d.ts +334 -0
- package/dist/jupiter-referral.js +666 -0
- package/dist/launch-metadata.d.ts +5 -1
- package/dist/launch-metadata.js +2 -0
- package/dist/meteora-referral.d.ts +119 -0
- package/dist/meteora-referral.js +188 -0
- package/dist/payments.d.ts +17 -0
- package/dist/payments.js +20 -2
- package/dist/pool.d.ts +108 -17
- package/dist/pool.js +237 -60
- package/dist/position-claims.d.ts +215 -0
- package/dist/position-claims.js +457 -0
- package/dist/priority-fee.d.ts +202 -0
- package/dist/priority-fee.js +322 -0
- package/dist/reads.d.ts +20 -0
- package/dist/reads.js +39 -2
- package/dist/settings.d.ts +73 -0
- package/dist/settings.js +119 -0
- package/dist/swap.d.ts +95 -23
- package/dist/swap.js +144 -30
- package/dist/tx.d.ts +2 -0
- package/dist/tx.js +4 -2
- package/dist/validate.d.ts +14 -1
- package/dist/validate.js +86 -6
- package/package.json +3 -2
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Priority fees of the transactions built by @forge/core (compute unit price).
|
|
3
|
+
*
|
|
4
|
+
* Under congestion a transaction without a compute unit price is dropped by the leaders long
|
|
5
|
+
* before its blockhash expires; a late payment then becomes a manual anomaly. Every builder that
|
|
6
|
+
* returns an unsigned transaction accepts an optional `priority` setting (`PriorityFeeSetting`):
|
|
7
|
+
* the price is read from the cluster, bounded, and two compute budget instructions are put first
|
|
8
|
+
* (`setComputeUnitLimit`, then `setComputeUnitPrice`; after the nonce advance of a durable nonce
|
|
9
|
+
* transaction).
|
|
10
|
+
*
|
|
11
|
+
* The price, in micro-lamports per compute unit (pure: `computePriorityFee`):
|
|
12
|
+
*
|
|
13
|
+
* p = the `percentile` (nearest rank) of the `prioritizationFee` values returned by
|
|
14
|
+
* `getRecentPrioritizationFees` for the transaction's writable accounts (up to 150
|
|
15
|
+
* recent slots, zeros included);
|
|
16
|
+
* price = min(max(p, floorMicroLamports), maxMicroLamports, floor(maxLamports x 10^6 / limit))
|
|
17
|
+
*
|
|
18
|
+
* so the priority fee of the whole transaction, `ceil(limit x price / 10^6)` lamports, is never
|
|
19
|
+
* above `maxLamports`, whatever the cluster answers. An empty answer, or an RPC error (the relay
|
|
20
|
+
* refusing the method, a timeout), falls back to the floor: the quote says so (`source`), nothing
|
|
21
|
+
* is hidden, and the transaction is still built.
|
|
22
|
+
*
|
|
23
|
+
* The compute unit limit is a per-transaction-kind constant measured on devnet with a margin
|
|
24
|
+
* (e.g. `DEFAULT_SWAP_COMPUTE_UNIT_LIMIT`, `LAUNCH_COIN_COMPUTE_UNITS`, `PAYMENT_COMPUTE_UNITS`):
|
|
25
|
+
* the price applies to the requested limit, not to what the transaction uses.
|
|
26
|
+
*
|
|
27
|
+
* Defaults (`DEFAULT_PRIORITY_FEE_POLICY`), sized for mainnet-beta and harmless on devnet: 75th
|
|
28
|
+
* percentile, floor 10,000 micro-lamports per CU, at most 1,000,000 micro-lamports per CU (1
|
|
29
|
+
* lamport) and at most 500,000 lamports (0.0005 SOL) per transaction. A setting can tighten or
|
|
30
|
+
* relax them within the hard ceilings `MAX_PRIORITY_FEE_MICRO_LAMPORTS` (10 lamports per CU) and
|
|
31
|
+
* `MAX_PRIORITY_FEE_LAMPORTS` (0.01 SOL), the bounds every FORGE verifier accepts.
|
|
32
|
+
*
|
|
33
|
+
* `checkComputeBudgetInstructions` is the matching check for transaction policies: compute
|
|
34
|
+
* budget instructions in their exact form only (no accounts, `SetComputeUnitLimit` or
|
|
35
|
+
* `SetComputeUnitPrice`, at most one of each), with bounded values.
|
|
36
|
+
*
|
|
37
|
+
* Browser-safe: only @solana/web3.js.
|
|
38
|
+
*/
|
|
39
|
+
import { PublicKey, type Connection, type TransactionInstruction } from '@solana/web3.js';
|
|
40
|
+
/** Solana's per-transaction compute ceiling (also the limit assumed when none is set). */
|
|
41
|
+
export declare const MAX_TRANSACTION_COMPUTE_UNITS = 1400000;
|
|
42
|
+
/** Hard ceiling of a compute unit price: 10 lamports per CU (`MAX_SWAP_PRIORITY_FEE_MICRO_LAMPORTS`). */
|
|
43
|
+
export declare const MAX_PRIORITY_FEE_MICRO_LAMPORTS = 10000000;
|
|
44
|
+
/** Hard ceiling of the priority fee of one transaction: 0.01 SOL. */
|
|
45
|
+
export declare const MAX_PRIORITY_FEE_LAMPORTS = 10000000n;
|
|
46
|
+
/**
|
|
47
|
+
* Most writable accounts passed to `getRecentPrioritizationFees` (the RPC accepts 128; the FORGE
|
|
48
|
+
* relay accepts this many).
|
|
49
|
+
*/
|
|
50
|
+
export declare const MAX_PRIORITY_FEE_ACCOUNTS = 32;
|
|
51
|
+
/** How the price is chosen and bounded. */
|
|
52
|
+
export interface PriorityFeePolicy {
|
|
53
|
+
/** Percentile of the recent fees, an integer from 0 (lowest) to 100 (highest). */
|
|
54
|
+
percentile: number;
|
|
55
|
+
/** Lowest price, micro-lamports per CU (also the price when the cluster gives none). */
|
|
56
|
+
floorMicroLamports: number;
|
|
57
|
+
/** Highest price, micro-lamports per CU. */
|
|
58
|
+
maxMicroLamports: number;
|
|
59
|
+
/** Highest priority fee of the whole transaction, lamports (the cost guard shown to users). */
|
|
60
|
+
maxLamports: bigint;
|
|
61
|
+
}
|
|
62
|
+
/** What a caller passes: any subset of the policy, the rest from `DEFAULT_PRIORITY_FEE_POLICY`. */
|
|
63
|
+
export type PriorityFeeSetting = Partial<PriorityFeePolicy>;
|
|
64
|
+
/** Mainnet-sized defaults (see the top of this file). */
|
|
65
|
+
export declare const DEFAULT_PRIORITY_FEE_POLICY: Readonly<PriorityFeePolicy>;
|
|
66
|
+
/** One entry of `getRecentPrioritizationFees`. */
|
|
67
|
+
export interface RecentPrioritizationFee {
|
|
68
|
+
slot: number;
|
|
69
|
+
/** Micro-lamports per CU paid by the cheapest transaction of the slot writing those accounts. */
|
|
70
|
+
prioritizationFee: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Where the price comes from: the recent fees, the floor because the cluster returned no fee, or
|
|
74
|
+
* the floor because the fees could not be read (`rpc_unavailable`).
|
|
75
|
+
*/
|
|
76
|
+
export type PriorityFeeSource = 'recent_fees' | 'no_recent_fees' | 'rpc_unavailable' | 'fixed';
|
|
77
|
+
/** The priority fee of one transaction, as the builders return it. */
|
|
78
|
+
export interface PriorityFeeQuote {
|
|
79
|
+
/** Compute unit limit the price applies to (the one set by the transaction). */
|
|
80
|
+
computeUnitLimit: number;
|
|
81
|
+
/** Compute unit price, micro-lamports per CU (0: no `setComputeUnitPrice` instruction). */
|
|
82
|
+
microLamports: number;
|
|
83
|
+
/** Priority fee the transaction pays at most: `ceil(limit x price / 10^6)` lamports. */
|
|
84
|
+
priorityFeeLamports: bigint;
|
|
85
|
+
/** The cost guard: no priority fee of this setting is ever above it (lamports). */
|
|
86
|
+
maxLamports: bigint;
|
|
87
|
+
source: PriorityFeeSource;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The full policy of a setting: defaults filled in, every value checked against the hard
|
|
91
|
+
* ceilings. @throws RangeError on an invalid value.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolvePriorityFeePolicy(setting?: PriorityFeeSetting): PriorityFeePolicy;
|
|
94
|
+
/**
|
|
95
|
+
* Nearest-rank percentile of `values` (0 = lowest, 100 = highest); 0 for an empty list.
|
|
96
|
+
* @throws RangeError on a percentile outside 0..100.
|
|
97
|
+
*/
|
|
98
|
+
export declare function feePercentile(values: readonly number[], percentile: number): number;
|
|
99
|
+
/** Highest price whose fee at `computeUnitLimit` stays within `maxLamports`. */
|
|
100
|
+
export declare function maxPriceForBudget(computeUnitLimit: number, maxLamports: bigint): number;
|
|
101
|
+
/**
|
|
102
|
+
* Pure: the price of a transaction of `computeUnitLimit` CU from the recent fees (see the top of
|
|
103
|
+
* this file). `recentFees: null` means the fees could not be read (`rpc_unavailable`); entries
|
|
104
|
+
* that are not non-negative safe integers are ignored.
|
|
105
|
+
*
|
|
106
|
+
* @throws RangeError on an invalid limit or policy.
|
|
107
|
+
*/
|
|
108
|
+
export declare function computePriorityFee(input: {
|
|
109
|
+
recentFees: readonly RecentPrioritizationFee[] | null;
|
|
110
|
+
computeUnitLimit: number;
|
|
111
|
+
policy?: PriorityFeeSetting;
|
|
112
|
+
}): PriorityFeeQuote;
|
|
113
|
+
/**
|
|
114
|
+
* The most a transaction of `computeUnitLimit` CU can pay as priority fee under `setting`, in
|
|
115
|
+
* lamports: its fee at the highest price the setting allows. The cost guard to show before the
|
|
116
|
+
* transaction is built (e.g. next to a swap quote).
|
|
117
|
+
*/
|
|
118
|
+
export declare function maxPriorityFeeLamports(computeUnitLimit: number, setting?: PriorityFeeSetting): bigint;
|
|
119
|
+
/**
|
|
120
|
+
* A quote for a fixed price (the builders' legacy `computeUnitPriceMicroLamports` /
|
|
121
|
+
* `priorityFeeMicroLamports` parameters): no RPC read, the given price as is.
|
|
122
|
+
*/
|
|
123
|
+
export declare function fixedPriorityFee(computeUnitLimit: number, microLamports: number): PriorityFeeQuote;
|
|
124
|
+
/**
|
|
125
|
+
* The compute budget instructions of a quote, to put first: `setComputeUnitLimit`, then
|
|
126
|
+
* `setComputeUnitPrice` when the price is not 0. `priceOnly`: the price alone (no limit), for a
|
|
127
|
+
* transaction too tight for both; its quote must then be computed for
|
|
128
|
+
* `MAX_TRANSACTION_COMPUTE_UNITS`, the limit the runtime may apply.
|
|
129
|
+
*/
|
|
130
|
+
export declare function priorityFeeInstructions(quote: Pick<PriorityFeeQuote, 'computeUnitLimit' | 'microLamports'>, options?: {
|
|
131
|
+
priceOnly?: boolean;
|
|
132
|
+
}): TransactionInstruction[];
|
|
133
|
+
/**
|
|
134
|
+
* The writable accounts of `instructions`, without duplicates, in order of appearance (at most
|
|
135
|
+
* `MAX_PRIORITY_FEE_ACCOUNTS`): what the leaders lock, so what the recent fees are read for.
|
|
136
|
+
*/
|
|
137
|
+
export declare function writableAccountsOf(instructions: readonly TransactionInstruction[]): PublicKey[];
|
|
138
|
+
/** The connection method the estimate uses (a `Connection` satisfies it). */
|
|
139
|
+
export type PriorityFeeReader = Pick<Connection, 'getRecentPrioritizationFees'>;
|
|
140
|
+
/**
|
|
141
|
+
* Reads the recent prioritization fees for `writableAccounts` (deduplicated, at most
|
|
142
|
+
* `MAX_PRIORITY_FEE_ACCOUNTS`). Throws what the connection throws.
|
|
143
|
+
*/
|
|
144
|
+
export declare function readRecentPriorityFees(connection: PriorityFeeReader, writableAccounts: readonly PublicKey[]): Promise<RecentPrioritizationFee[]>;
|
|
145
|
+
export interface EstimatePriorityFeeParams {
|
|
146
|
+
connection: PriorityFeeReader;
|
|
147
|
+
/** The transaction's writable accounts (`writableAccountsOf`). */
|
|
148
|
+
writableAccounts: readonly PublicKey[];
|
|
149
|
+
computeUnitLimit: number;
|
|
150
|
+
setting?: PriorityFeeSetting;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Reads the recent fees and computes the price (`computePriorityFee`). A failed read is not an
|
|
154
|
+
* error: the price is the floor and the quote says `rpc_unavailable`. Invalid settings throw
|
|
155
|
+
* (RangeError) before any network call.
|
|
156
|
+
*/
|
|
157
|
+
export declare function estimatePriorityFee(params: EstimatePriorityFeeParams): Promise<PriorityFeeQuote>;
|
|
158
|
+
export type ComputeBudgetCheckCode =
|
|
159
|
+
/** A compute budget instruction with accounts, or of another kind (heap frame, data size...). */
|
|
160
|
+
'unexpected_instruction'
|
|
161
|
+
/** Two unit limits or two unit prices. */
|
|
162
|
+
| 'duplicate_instruction' | 'limit_out_of_range' | 'price_too_high' | 'fee_too_high';
|
|
163
|
+
/** A compute budget instruction is not in the accepted form or above the bounds. */
|
|
164
|
+
export declare class ComputeBudgetCheckError extends Error {
|
|
165
|
+
readonly code: ComputeBudgetCheckCode;
|
|
166
|
+
constructor(code: ComputeBudgetCheckCode, message: string);
|
|
167
|
+
}
|
|
168
|
+
export interface ComputeBudgetBounds {
|
|
169
|
+
/** Highest unit limit (default `MAX_TRANSACTION_COMPUTE_UNITS`). */
|
|
170
|
+
maxComputeUnits?: number;
|
|
171
|
+
/** Highest unit price, micro-lamports per CU (default `MAX_PRIORITY_FEE_MICRO_LAMPORTS`). */
|
|
172
|
+
maxMicroLamports?: number | bigint;
|
|
173
|
+
/** Highest priority fee of the transaction, lamports (default `MAX_PRIORITY_FEE_LAMPORTS`). */
|
|
174
|
+
maxPriorityFeeLamports?: bigint;
|
|
175
|
+
}
|
|
176
|
+
export interface ComputeBudgetSummary {
|
|
177
|
+
/** The unit limit set, or null when there is none. */
|
|
178
|
+
computeUnitLimit: number | null;
|
|
179
|
+
/** The unit price set, micro-lamports per CU (0 when there is none). */
|
|
180
|
+
microLamports: bigint;
|
|
181
|
+
/**
|
|
182
|
+
* The highest priority fee the transaction can pay, lamports. Without a unit limit it is
|
|
183
|
+
* computed for `MAX_TRANSACTION_COMPUTE_UNITS` (the runtime's default depends on the
|
|
184
|
+
* instructions; this bound holds for any of them).
|
|
185
|
+
*/
|
|
186
|
+
priorityFeeLamports: bigint;
|
|
187
|
+
}
|
|
188
|
+
/** What `checkComputeBudgetInstructions` reads of an instruction (a web3.js instruction fits). */
|
|
189
|
+
export interface InspectedInstruction {
|
|
190
|
+
programId: PublicKey;
|
|
191
|
+
keys: readonly unknown[];
|
|
192
|
+
data: Uint8Array;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Checks every Compute Budget instruction of `instructions` (other programs are left to the
|
|
196
|
+
* caller's policy): no account, `SetComputeUnitLimit` (tag 2, u32) or `SetComputeUnitPrice`
|
|
197
|
+
* (tag 3, u64) only, at most one of each, limit within 1..`maxComputeUnits`, price within
|
|
198
|
+
* `maxMicroLamports`, priority fee within `maxPriorityFeeLamports`.
|
|
199
|
+
*
|
|
200
|
+
* @throws ComputeBudgetCheckError with a stable `code`.
|
|
201
|
+
*/
|
|
202
|
+
export declare function checkComputeBudgetInstructions(instructions: readonly InspectedInstruction[], bounds?: ComputeBudgetBounds): ComputeBudgetSummary;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Priority fees of the transactions built by @forge/core (compute unit price).
|
|
3
|
+
*
|
|
4
|
+
* Under congestion a transaction without a compute unit price is dropped by the leaders long
|
|
5
|
+
* before its blockhash expires; a late payment then becomes a manual anomaly. Every builder that
|
|
6
|
+
* returns an unsigned transaction accepts an optional `priority` setting (`PriorityFeeSetting`):
|
|
7
|
+
* the price is read from the cluster, bounded, and two compute budget instructions are put first
|
|
8
|
+
* (`setComputeUnitLimit`, then `setComputeUnitPrice`; after the nonce advance of a durable nonce
|
|
9
|
+
* transaction).
|
|
10
|
+
*
|
|
11
|
+
* The price, in micro-lamports per compute unit (pure: `computePriorityFee`):
|
|
12
|
+
*
|
|
13
|
+
* p = the `percentile` (nearest rank) of the `prioritizationFee` values returned by
|
|
14
|
+
* `getRecentPrioritizationFees` for the transaction's writable accounts (up to 150
|
|
15
|
+
* recent slots, zeros included);
|
|
16
|
+
* price = min(max(p, floorMicroLamports), maxMicroLamports, floor(maxLamports x 10^6 / limit))
|
|
17
|
+
*
|
|
18
|
+
* so the priority fee of the whole transaction, `ceil(limit x price / 10^6)` lamports, is never
|
|
19
|
+
* above `maxLamports`, whatever the cluster answers. An empty answer, or an RPC error (the relay
|
|
20
|
+
* refusing the method, a timeout), falls back to the floor: the quote says so (`source`), nothing
|
|
21
|
+
* is hidden, and the transaction is still built.
|
|
22
|
+
*
|
|
23
|
+
* The compute unit limit is a per-transaction-kind constant measured on devnet with a margin
|
|
24
|
+
* (e.g. `DEFAULT_SWAP_COMPUTE_UNIT_LIMIT`, `LAUNCH_COIN_COMPUTE_UNITS`, `PAYMENT_COMPUTE_UNITS`):
|
|
25
|
+
* the price applies to the requested limit, not to what the transaction uses.
|
|
26
|
+
*
|
|
27
|
+
* Defaults (`DEFAULT_PRIORITY_FEE_POLICY`), sized for mainnet-beta and harmless on devnet: 75th
|
|
28
|
+
* percentile, floor 10,000 micro-lamports per CU, at most 1,000,000 micro-lamports per CU (1
|
|
29
|
+
* lamport) and at most 500,000 lamports (0.0005 SOL) per transaction. A setting can tighten or
|
|
30
|
+
* relax them within the hard ceilings `MAX_PRIORITY_FEE_MICRO_LAMPORTS` (10 lamports per CU) and
|
|
31
|
+
* `MAX_PRIORITY_FEE_LAMPORTS` (0.01 SOL), the bounds every FORGE verifier accepts.
|
|
32
|
+
*
|
|
33
|
+
* `checkComputeBudgetInstructions` is the matching check for transaction policies: compute
|
|
34
|
+
* budget instructions in their exact form only (no accounts, `SetComputeUnitLimit` or
|
|
35
|
+
* `SetComputeUnitPrice`, at most one of each), with bounded values.
|
|
36
|
+
*
|
|
37
|
+
* Browser-safe: only @solana/web3.js.
|
|
38
|
+
*/
|
|
39
|
+
import { ComputeBudgetProgram, PublicKey, } from '@solana/web3.js';
|
|
40
|
+
import { computeBudgetInstructions, priorityFeeLamports } from './tx.js';
|
|
41
|
+
// ---------------------------------------------------------------------------------------------
|
|
42
|
+
// Constants
|
|
43
|
+
// ---------------------------------------------------------------------------------------------
|
|
44
|
+
/** Solana's per-transaction compute ceiling (also the limit assumed when none is set). */
|
|
45
|
+
export const MAX_TRANSACTION_COMPUTE_UNITS = 1_400_000;
|
|
46
|
+
/** Hard ceiling of a compute unit price: 10 lamports per CU (`MAX_SWAP_PRIORITY_FEE_MICRO_LAMPORTS`). */
|
|
47
|
+
export const MAX_PRIORITY_FEE_MICRO_LAMPORTS = 10_000_000;
|
|
48
|
+
/** Hard ceiling of the priority fee of one transaction: 0.01 SOL. */
|
|
49
|
+
export const MAX_PRIORITY_FEE_LAMPORTS = 10000000n;
|
|
50
|
+
/**
|
|
51
|
+
* Most writable accounts passed to `getRecentPrioritizationFees` (the RPC accepts 128; the FORGE
|
|
52
|
+
* relay accepts this many).
|
|
53
|
+
*/
|
|
54
|
+
export const MAX_PRIORITY_FEE_ACCOUNTS = 32;
|
|
55
|
+
/** Mainnet-sized defaults (see the top of this file). */
|
|
56
|
+
export const DEFAULT_PRIORITY_FEE_POLICY = Object.freeze({
|
|
57
|
+
percentile: 75,
|
|
58
|
+
floorMicroLamports: 10_000,
|
|
59
|
+
maxMicroLamports: 1_000_000,
|
|
60
|
+
maxLamports: 500000n,
|
|
61
|
+
});
|
|
62
|
+
// ---------------------------------------------------------------------------------------------
|
|
63
|
+
// Pure computation
|
|
64
|
+
// ---------------------------------------------------------------------------------------------
|
|
65
|
+
function assertInteger(name, value, min, max) {
|
|
66
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < min || value > max) {
|
|
67
|
+
throw new RangeError(`${name} must be an integer between ${min} and ${max}`);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The full policy of a setting: defaults filled in, every value checked against the hard
|
|
73
|
+
* ceilings. @throws RangeError on an invalid value.
|
|
74
|
+
*/
|
|
75
|
+
export function resolvePriorityFeePolicy(setting = {}) {
|
|
76
|
+
if (setting === null || typeof setting !== 'object') {
|
|
77
|
+
throw new RangeError('the priority fee setting must be an object');
|
|
78
|
+
}
|
|
79
|
+
const percentile = assertInteger('percentile', setting.percentile ?? DEFAULT_PRIORITY_FEE_POLICY.percentile, 0, 100);
|
|
80
|
+
const maxMicroLamports = assertInteger('maxMicroLamports', setting.maxMicroLamports ?? DEFAULT_PRIORITY_FEE_POLICY.maxMicroLamports, 0, MAX_PRIORITY_FEE_MICRO_LAMPORTS);
|
|
81
|
+
const floorMicroLamports = assertInteger('floorMicroLamports', setting.floorMicroLamports ??
|
|
82
|
+
Math.min(DEFAULT_PRIORITY_FEE_POLICY.floorMicroLamports, maxMicroLamports), 0, maxMicroLamports);
|
|
83
|
+
const maxLamports = setting.maxLamports ?? DEFAULT_PRIORITY_FEE_POLICY.maxLamports;
|
|
84
|
+
if (typeof maxLamports !== 'bigint' ||
|
|
85
|
+
maxLamports < 0n ||
|
|
86
|
+
maxLamports > MAX_PRIORITY_FEE_LAMPORTS) {
|
|
87
|
+
throw new RangeError(`maxLamports must be a bigint between 0 and ${MAX_PRIORITY_FEE_LAMPORTS} lamports`);
|
|
88
|
+
}
|
|
89
|
+
return { percentile, floorMicroLamports, maxMicroLamports, maxLamports };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Nearest-rank percentile of `values` (0 = lowest, 100 = highest); 0 for an empty list.
|
|
93
|
+
* @throws RangeError on a percentile outside 0..100.
|
|
94
|
+
*/
|
|
95
|
+
export function feePercentile(values, percentile) {
|
|
96
|
+
assertInteger('percentile', percentile, 0, 100);
|
|
97
|
+
if (values.length === 0)
|
|
98
|
+
return 0;
|
|
99
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
100
|
+
const rank = Math.ceil((percentile / 100) * sorted.length);
|
|
101
|
+
return sorted[Math.min(Math.max(rank, 1), sorted.length) - 1];
|
|
102
|
+
}
|
|
103
|
+
/** Highest price whose fee at `computeUnitLimit` stays within `maxLamports`. */
|
|
104
|
+
export function maxPriceForBudget(computeUnitLimit, maxLamports) {
|
|
105
|
+
assertInteger('computeUnitLimit', computeUnitLimit, 1, MAX_TRANSACTION_COMPUTE_UNITS);
|
|
106
|
+
const price = (maxLamports * 1000000n) / BigInt(computeUnitLimit);
|
|
107
|
+
return price > BigInt(MAX_PRIORITY_FEE_MICRO_LAMPORTS)
|
|
108
|
+
? MAX_PRIORITY_FEE_MICRO_LAMPORTS
|
|
109
|
+
: Number(price);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Pure: the price of a transaction of `computeUnitLimit` CU from the recent fees (see the top of
|
|
113
|
+
* this file). `recentFees: null` means the fees could not be read (`rpc_unavailable`); entries
|
|
114
|
+
* that are not non-negative safe integers are ignored.
|
|
115
|
+
*
|
|
116
|
+
* @throws RangeError on an invalid limit or policy.
|
|
117
|
+
*/
|
|
118
|
+
export function computePriorityFee(input) {
|
|
119
|
+
const policy = resolvePriorityFeePolicy(input.policy);
|
|
120
|
+
const units = assertInteger('computeUnitLimit', input.computeUnitLimit, 1, MAX_TRANSACTION_COMPUTE_UNITS);
|
|
121
|
+
const values = input.recentFees === null
|
|
122
|
+
? []
|
|
123
|
+
: input.recentFees
|
|
124
|
+
.map((entry) => entry?.prioritizationFee)
|
|
125
|
+
.filter((fee) => Number.isSafeInteger(fee) && fee >= 0);
|
|
126
|
+
const source = input.recentFees === null
|
|
127
|
+
? 'rpc_unavailable'
|
|
128
|
+
: values.length > 0
|
|
129
|
+
? 'recent_fees'
|
|
130
|
+
: 'no_recent_fees';
|
|
131
|
+
let price = Math.max(feePercentile(values, policy.percentile), policy.floorMicroLamports);
|
|
132
|
+
price = Math.min(price, policy.maxMicroLamports, maxPriceForBudget(units, policy.maxLamports));
|
|
133
|
+
return {
|
|
134
|
+
computeUnitLimit: units,
|
|
135
|
+
microLamports: price,
|
|
136
|
+
priorityFeeLamports: priorityFeeLamports({ units, microLamports: price }),
|
|
137
|
+
maxLamports: policy.maxLamports,
|
|
138
|
+
source,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The most a transaction of `computeUnitLimit` CU can pay as priority fee under `setting`, in
|
|
143
|
+
* lamports: its fee at the highest price the setting allows. The cost guard to show before the
|
|
144
|
+
* transaction is built (e.g. next to a swap quote).
|
|
145
|
+
*/
|
|
146
|
+
export function maxPriorityFeeLamports(computeUnitLimit, setting) {
|
|
147
|
+
return computePriorityFee({
|
|
148
|
+
recentFees: [{ slot: 0, prioritizationFee: MAX_PRIORITY_FEE_MICRO_LAMPORTS }],
|
|
149
|
+
computeUnitLimit,
|
|
150
|
+
...(setting ? { policy: setting } : {}),
|
|
151
|
+
}).priorityFeeLamports;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A quote for a fixed price (the builders' legacy `computeUnitPriceMicroLamports` /
|
|
155
|
+
* `priorityFeeMicroLamports` parameters): no RPC read, the given price as is.
|
|
156
|
+
*/
|
|
157
|
+
export function fixedPriorityFee(computeUnitLimit, microLamports) {
|
|
158
|
+
assertInteger('computeUnitLimit', computeUnitLimit, 1, MAX_TRANSACTION_COMPUTE_UNITS);
|
|
159
|
+
assertInteger('microLamports', microLamports, 0, Number.MAX_SAFE_INTEGER);
|
|
160
|
+
const fee = priorityFeeLamports({ units: computeUnitLimit, microLamports });
|
|
161
|
+
return {
|
|
162
|
+
computeUnitLimit,
|
|
163
|
+
microLamports,
|
|
164
|
+
priorityFeeLamports: fee,
|
|
165
|
+
maxLamports: fee,
|
|
166
|
+
source: 'fixed',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The compute budget instructions of a quote, to put first: `setComputeUnitLimit`, then
|
|
171
|
+
* `setComputeUnitPrice` when the price is not 0. `priceOnly`: the price alone (no limit), for a
|
|
172
|
+
* transaction too tight for both; its quote must then be computed for
|
|
173
|
+
* `MAX_TRANSACTION_COMPUTE_UNITS`, the limit the runtime may apply.
|
|
174
|
+
*/
|
|
175
|
+
export function priorityFeeInstructions(quote, options = {}) {
|
|
176
|
+
if (!options.priceOnly) {
|
|
177
|
+
return computeBudgetInstructions({
|
|
178
|
+
units: quote.computeUnitLimit,
|
|
179
|
+
microLamports: quote.microLamports,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
assertInteger('microLamports', quote.microLamports, 0, MAX_PRIORITY_FEE_MICRO_LAMPORTS);
|
|
183
|
+
return quote.microLamports > 0
|
|
184
|
+
? [ComputeBudgetProgram.setComputeUnitPrice({ microLamports: quote.microLamports })]
|
|
185
|
+
: [];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The writable accounts of `instructions`, without duplicates, in order of appearance (at most
|
|
189
|
+
* `MAX_PRIORITY_FEE_ACCOUNTS`): what the leaders lock, so what the recent fees are read for.
|
|
190
|
+
*/
|
|
191
|
+
export function writableAccountsOf(instructions) {
|
|
192
|
+
const seen = new Set();
|
|
193
|
+
const out = [];
|
|
194
|
+
for (const ix of instructions) {
|
|
195
|
+
for (const meta of ix.keys) {
|
|
196
|
+
if (!meta.isWritable)
|
|
197
|
+
continue;
|
|
198
|
+
const id = meta.pubkey.toBase58();
|
|
199
|
+
if (seen.has(id))
|
|
200
|
+
continue;
|
|
201
|
+
seen.add(id);
|
|
202
|
+
out.push(meta.pubkey);
|
|
203
|
+
if (out.length === MAX_PRIORITY_FEE_ACCOUNTS)
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Reads the recent prioritization fees for `writableAccounts` (deduplicated, at most
|
|
211
|
+
* `MAX_PRIORITY_FEE_ACCOUNTS`). Throws what the connection throws.
|
|
212
|
+
*/
|
|
213
|
+
export async function readRecentPriorityFees(connection, writableAccounts) {
|
|
214
|
+
const seen = new Set();
|
|
215
|
+
const accounts = [];
|
|
216
|
+
for (const account of writableAccounts) {
|
|
217
|
+
if (!(account instanceof PublicKey))
|
|
218
|
+
throw new TypeError('writable accounts must be PublicKeys');
|
|
219
|
+
const id = account.toBase58();
|
|
220
|
+
if (seen.has(id))
|
|
221
|
+
continue;
|
|
222
|
+
seen.add(id);
|
|
223
|
+
accounts.push(account);
|
|
224
|
+
if (accounts.length === MAX_PRIORITY_FEE_ACCOUNTS)
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
const fees = await connection.getRecentPrioritizationFees(accounts.length > 0 ? { lockedWritableAccounts: accounts } : undefined);
|
|
228
|
+
if (!Array.isArray(fees))
|
|
229
|
+
throw new TypeError('getRecentPrioritizationFees did not return a list');
|
|
230
|
+
return fees;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Reads the recent fees and computes the price (`computePriorityFee`). A failed read is not an
|
|
234
|
+
* error: the price is the floor and the quote says `rpc_unavailable`. Invalid settings throw
|
|
235
|
+
* (RangeError) before any network call.
|
|
236
|
+
*/
|
|
237
|
+
export async function estimatePriorityFee(params) {
|
|
238
|
+
// Validate first: a bad setting never reaches the network.
|
|
239
|
+
computePriorityFee({
|
|
240
|
+
recentFees: [],
|
|
241
|
+
computeUnitLimit: params.computeUnitLimit,
|
|
242
|
+
...(params.setting ? { policy: params.setting } : {}),
|
|
243
|
+
});
|
|
244
|
+
let recentFees;
|
|
245
|
+
try {
|
|
246
|
+
recentFees = await readRecentPriorityFees(params.connection, params.writableAccounts);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// Degraded, reported in the quote (`source`): the floor still gives the transaction a price.
|
|
250
|
+
recentFees = null;
|
|
251
|
+
}
|
|
252
|
+
return computePriorityFee({
|
|
253
|
+
recentFees,
|
|
254
|
+
computeUnitLimit: params.computeUnitLimit,
|
|
255
|
+
...(params.setting ? { policy: params.setting } : {}),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/** A compute budget instruction is not in the accepted form or above the bounds. */
|
|
259
|
+
export class ComputeBudgetCheckError extends Error {
|
|
260
|
+
code;
|
|
261
|
+
constructor(code, message) {
|
|
262
|
+
super(message);
|
|
263
|
+
this.name = 'ComputeBudgetCheckError';
|
|
264
|
+
this.code = code;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function readU32(data, offset) {
|
|
268
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(offset, true);
|
|
269
|
+
}
|
|
270
|
+
function readU64(data, offset) {
|
|
271
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength).getBigUint64(offset, true);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Checks every Compute Budget instruction of `instructions` (other programs are left to the
|
|
275
|
+
* caller's policy): no account, `SetComputeUnitLimit` (tag 2, u32) or `SetComputeUnitPrice`
|
|
276
|
+
* (tag 3, u64) only, at most one of each, limit within 1..`maxComputeUnits`, price within
|
|
277
|
+
* `maxMicroLamports`, priority fee within `maxPriorityFeeLamports`.
|
|
278
|
+
*
|
|
279
|
+
* @throws ComputeBudgetCheckError with a stable `code`.
|
|
280
|
+
*/
|
|
281
|
+
export function checkComputeBudgetInstructions(instructions, bounds = {}) {
|
|
282
|
+
const maxUnits = bounds.maxComputeUnits ?? MAX_TRANSACTION_COMPUTE_UNITS;
|
|
283
|
+
const maxPrice = BigInt(bounds.maxMicroLamports ?? MAX_PRIORITY_FEE_MICRO_LAMPORTS);
|
|
284
|
+
const maxFee = bounds.maxPriorityFeeLamports ?? MAX_PRIORITY_FEE_LAMPORTS;
|
|
285
|
+
let units = null;
|
|
286
|
+
let price = null;
|
|
287
|
+
for (const ix of instructions) {
|
|
288
|
+
if (!ix.programId.equals(ComputeBudgetProgram.programId))
|
|
289
|
+
continue;
|
|
290
|
+
const data = ix.data;
|
|
291
|
+
if (ix.keys.length !== 0) {
|
|
292
|
+
throw new ComputeBudgetCheckError('unexpected_instruction', 'compute budget instruction with accounts');
|
|
293
|
+
}
|
|
294
|
+
if (data[0] === 2 && data.length === 5) {
|
|
295
|
+
if (units !== null) {
|
|
296
|
+
throw new ComputeBudgetCheckError('duplicate_instruction', 'duplicate compute unit limit');
|
|
297
|
+
}
|
|
298
|
+
units = readU32(data, 1);
|
|
299
|
+
if (units < 1 || units > maxUnits) {
|
|
300
|
+
throw new ComputeBudgetCheckError('limit_out_of_range', 'compute unit limit out of range');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else if (data[0] === 3 && data.length === 9) {
|
|
304
|
+
if (price !== null) {
|
|
305
|
+
throw new ComputeBudgetCheckError('duplicate_instruction', 'duplicate compute unit price');
|
|
306
|
+
}
|
|
307
|
+
price = readU64(data, 1);
|
|
308
|
+
if (price > maxPrice) {
|
|
309
|
+
throw new ComputeBudgetCheckError('price_too_high', 'compute unit price too high');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
throw new ComputeBudgetCheckError('unexpected_instruction', 'compute budget instruction other than a unit limit or a unit price');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const microLamports = price ?? 0n;
|
|
317
|
+
const budget = BigInt(units ?? MAX_TRANSACTION_COMPUTE_UNITS);
|
|
318
|
+
const fee = (budget * microLamports + 999999n) / 1000000n;
|
|
319
|
+
if (fee > maxFee)
|
|
320
|
+
throw new ComputeBudgetCheckError('fee_too_high', 'priority fee too high');
|
|
321
|
+
return { computeUnitLimit: units, microLamports, priorityFeeLamports: fee };
|
|
322
|
+
}
|
package/dist/reads.d.ts
CHANGED
|
@@ -160,6 +160,26 @@ export declare function readCoin(connection: Connection, pool: PublicKey, option
|
|
|
160
160
|
* @throws DbcReadError when the config does not exist.
|
|
161
161
|
*/
|
|
162
162
|
export declare function listCoinsOfConfig(connection: Connection, config: PublicKey, options?: ReadOptions): Promise<CoinOfConfig[]>;
|
|
163
|
+
/**
|
|
164
|
+
* The decoded pools of a config with the config itself (what `listCoinsOfConfig` summarizes):
|
|
165
|
+
* one `getPoolsByConfig` scan (one `getProgramAccounts` per pool account variant, filtered on the
|
|
166
|
+
* config at offset 72) and one config read. A pool whose decoded `config` is not `config` is
|
|
167
|
+
* dropped (a wrong RPC answer is never shown). Sorted by pool address.
|
|
168
|
+
*
|
|
169
|
+
* The scan returns every pool of the config: callers should cache it (the FORGE RPC relay serves
|
|
170
|
+
* it to server-token callers only, with a response size cap).
|
|
171
|
+
* @throws DbcReadError when the config does not exist.
|
|
172
|
+
*/
|
|
173
|
+
export declare function listPoolStatesOfConfig(connection: Connection, config: PublicKey, options?: ReadOptions): Promise<DbcPoolState[]>;
|
|
174
|
+
/**
|
|
175
|
+
* Pure: spot price of one whole base token (the coin) in whole quote tokens (SOL for FORGE pools),
|
|
176
|
+
* from the pool's `sqrtPrice`: `sqrtPrice^2 / 2^128` quote units per base unit (the price the
|
|
177
|
+
* curve is at, before fees), times `10^(baseDecimals - quoteDecimals)`. A float, for display only:
|
|
178
|
+
* quotes and transactions use the exact bigint math of `quoteSwap`.
|
|
179
|
+
*/
|
|
180
|
+
export declare function poolSpotPrice(sqrtPrice: {
|
|
181
|
+
toString(): string;
|
|
182
|
+
} | bigint, baseDecimals: number, quoteDecimals?: number): number;
|
|
163
183
|
/**
|
|
164
184
|
* Byte offset of `creator` in a DBC pool account (`VirtualPool` and `TransferHookPool`: 8-byte
|
|
165
185
|
* account discriminator, then `PoolState` with `config` at 72, `creator` at 104, `baseMint` at 136).
|
package/dist/reads.js
CHANGED
|
@@ -165,6 +165,19 @@ export async function readCoin(connection, pool, options = {}) {
|
|
|
165
165
|
* @throws DbcReadError when the config does not exist.
|
|
166
166
|
*/
|
|
167
167
|
export async function listCoinsOfConfig(connection, config, options = {}) {
|
|
168
|
+
return (await listPoolStatesOfConfig(connection, config, options)).map(summarizeCoin);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The decoded pools of a config with the config itself (what `listCoinsOfConfig` summarizes):
|
|
172
|
+
* one `getPoolsByConfig` scan (one `getProgramAccounts` per pool account variant, filtered on the
|
|
173
|
+
* config at offset 72) and one config read. A pool whose decoded `config` is not `config` is
|
|
174
|
+
* dropped (a wrong RPC answer is never shown). Sorted by pool address.
|
|
175
|
+
*
|
|
176
|
+
* The scan returns every pool of the config: callers should cache it (the FORGE RPC relay serves
|
|
177
|
+
* it to server-token callers only, with a response size cap).
|
|
178
|
+
* @throws DbcReadError when the config does not exist.
|
|
179
|
+
*/
|
|
180
|
+
export async function listPoolStatesOfConfig(connection, config, options = {}) {
|
|
168
181
|
const state = stateService(connection, options.commitment ?? READ_COMMITMENT);
|
|
169
182
|
const configState = await retryRpc(() => state.getPoolConfig(config), options.retry);
|
|
170
183
|
if (!configState) {
|
|
@@ -173,8 +186,32 @@ export async function listCoinsOfConfig(connection, config, options = {}) {
|
|
|
173
186
|
const pools = await retryRpc(() => state.getPoolsByConfig(config), options.retry);
|
|
174
187
|
return pools
|
|
175
188
|
.filter((p) => p.account.poolState.config.equals(config))
|
|
176
|
-
.map((p) =>
|
|
177
|
-
.sort((a, b) => compareStrings(a.
|
|
189
|
+
.map((p) => ({ address: p.publicKey, virtualPool: p.account, config: configState }))
|
|
190
|
+
.sort((a, b) => compareStrings(a.address.toBase58(), b.address.toBase58()));
|
|
191
|
+
}
|
|
192
|
+
/** 2^128: a pool's `sqrtPrice` is a Q64.64 number, so its square is a Q128.128 price. */
|
|
193
|
+
const Q128 = 1n << 128n;
|
|
194
|
+
/** Fixed-point scale used to turn the bigint price into a float without losing small prices. */
|
|
195
|
+
const SPOT_PRICE_SCALE = 10n ** 18n;
|
|
196
|
+
/**
|
|
197
|
+
* Pure: spot price of one whole base token (the coin) in whole quote tokens (SOL for FORGE pools),
|
|
198
|
+
* from the pool's `sqrtPrice`: `sqrtPrice^2 / 2^128` quote units per base unit (the price the
|
|
199
|
+
* curve is at, before fees), times `10^(baseDecimals - quoteDecimals)`. A float, for display only:
|
|
200
|
+
* quotes and transactions use the exact bigint math of `quoteSwap`.
|
|
201
|
+
*/
|
|
202
|
+
export function poolSpotPrice(sqrtPrice, baseDecimals, quoteDecimals = 9) {
|
|
203
|
+
const sqrt = toBigint(sqrtPrice);
|
|
204
|
+
if (sqrt <= 0n)
|
|
205
|
+
return 0;
|
|
206
|
+
const shift = baseDecimals - quoteDecimals;
|
|
207
|
+
let numerator = sqrt * sqrt * SPOT_PRICE_SCALE;
|
|
208
|
+
let denominator = Q128;
|
|
209
|
+
if (shift >= 0)
|
|
210
|
+
numerator *= 10n ** BigInt(shift);
|
|
211
|
+
else
|
|
212
|
+
denominator *= 10n ** BigInt(-shift);
|
|
213
|
+
const scaled = numerator / denominator;
|
|
214
|
+
return Number(scaled) / Number(SPOT_PRICE_SCALE);
|
|
178
215
|
}
|
|
179
216
|
/**
|
|
180
217
|
* Byte offset of `creator` in a DBC pool account (`VirtualPool` and `TransferHookPool`: 8-byte
|