Skip to content

Commit

Permalink
Add priority fees
Browse files Browse the repository at this point in the history
  • Loading branch information
acheroncrypto committed Mar 13, 2024
1 parent f55792e commit dbf2837
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 11 deletions.
35 changes: 35 additions & 0 deletions client/src/settings/priority-fee/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useEffect, useMemo, useState } from "react";

import Select from "../../components/Select";
import { PgSettings } from "../../utils/pg";

const PriorityFee = () => {
const options = useMemo(
() => (["avg", "min", "max"] as const).map((c) => ({ value: c, label: c })),
[]
);

const [value, setValue] = useState<typeof options[number]>();

useEffect(() => {
const { dispose } = PgSettings.onDidChangeConnectionPriorityFee((fee) =>
setValue(options.find((o) => o.value === fee))
);
return () => dispose();
}, [options]);

return (
<Select
options={options}
value={value}
onChange={(newValue) => {
const newPriorityFee = newValue?.value;
if (newPriorityFee) {
PgSettings.connection.priorityFee = newPriorityFee;
}
}}
/>
);
};

export default PriorityFee;
1 change: 1 addition & 0 deletions client/src/settings/priority-fee/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { priorityFee } from "./priority-fee";
9 changes: 9 additions & 0 deletions client/src/settings/priority-fee/priority-fee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createSetting } from "../create";

export const priorityFee = createSetting({
name: "Priority fee",
tooltip: {
element: "Priority fee calculation method to use when sending transactions",
maxWidth: "16rem",
},
});
2 changes: 2 additions & 0 deletions client/src/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { endpoint } from "./endpoint";
import { font } from "./font";
import { improveBuildErrors } from "./improve-build-errors";
import { preflightChecks } from "./preflight-checks";
import { priorityFee } from "./priority-fee";
import { showTransactionDetails } from "./show-transaction-details";
import { showTransactionNotifications } from "./show-transaction-notifications";
import { theme } from "./theme";
Expand All @@ -14,6 +15,7 @@ export const SETTINGS = [
font,
endpoint,
commitment,
priorityFee,
preflightChecks,
automaticAirdrop,
showTransactionDetails,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,10 @@ export class BpfLoaderUpgradeable {
static BUFFER_PROGRAM_DATA_HEADER_SIZE: number = 45; // usize + Option<Pk>

/** Maximal chunk of the data per tx */
static WRITE_CHUNK_SIZE: number = PACKET_DATA_SIZE - 220; // Data with 1 signature
static WRITE_CHUNK_SIZE: number =
PACKET_DATA_SIZE - // Maximum transaction size
220 - // Data with 1 signature
44; // Priority fee instruction size

/** Get buffer account size. */
static getBufferAccountSize(programLen: number) {
Expand Down
3 changes: 3 additions & 0 deletions client/src/utils/pg/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface Settings {
commitment: "processed" | "confirmed" | "finalized";
/** Whether to enable preflight checks */
preflightChecks: boolean;
/** Priority fee calculcation method */
priorityFee: "avg" | "min" | "max";
};
/** Build settings */
// TODO: Re-evalute whether build settings should be stored in `PgProgramInfo`
Expand Down Expand Up @@ -52,6 +54,7 @@ const defaultState: Settings = {
endpoint: Endpoint.DEVNET,
commitment: "confirmed",
preflightChecks: true,
priorityFee: "avg",
},
build: {
flags: {
Expand Down
89 changes: 79 additions & 10 deletions client/src/utils/pg/tx/tx.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Commitment, Connection, Signer, Transaction } from "@solana/web3.js";
import {
Commitment,
ComputeBudgetProgram,
Connection,
Signer,
Transaction,
} from "@solana/web3.js";

import { ExplorerLink } from "./ExplorerLink";
import { PgCommon } from "../common";
Expand All @@ -8,12 +14,24 @@ import { PgView } from "../view";
import { ConnectionOption, PgConnection } from "../connection";
import { CurrentWallet, PgWallet, WalletOption } from "../wallet";

interface BlockhashInfo {
/** Latest blockhash */
blockhash: string;
type WithTimeStamp<T> = T & {
/** UNIX timestamp of when the blockhash was last cached */
timestamp: number;
}
};

type BlockhashInfo = WithTimeStamp<{
/** Latest blockhash */
blockhash: string;
}>;

type PriorityFeeInfo = WithTimeStamp<{
/** Average priority fee paid in the latest slots */
avg: number;
/** Minimum priority fee paid in the latest slots */
min: number;
/** Maximum priority fee paid in the latest slots */
max: number;
}>;

export class PgTx {
/**
Expand All @@ -38,6 +56,23 @@ export class PgTx {

const connection = opts?.connection ?? PgConnection.current;

// Set priority fees if the transaction doesn't already have it
const existingsetComputeUnitPriceIx = tx.instructions.find(
(ix) =>
ix.programId.equals(ComputeBudgetProgram.programId) &&
ix.data.at(0) === 3 // setComputeUnitPrice
);
if (!existingsetComputeUnitPriceIx) {
const priorityFeeInfo = await this._getPriorityFee(connection);
const priorityFee = priorityFeeInfo[PgSettings.connection.priorityFee];
if (priorityFee) {
const setComputeUnitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: priorityFee,
});
tx.instructions = [setComputeUnitPriceIx, ...tx.instructions];
}
}

tx.recentBlockhash = await this._getLatestBlockhash(
connection,
opts?.forceFetchLatestBlockhash
Expand Down Expand Up @@ -124,32 +159,66 @@ export class PgTx {
/** Cached blockhash to reduce the amount of requests to the RPC endpoint */
private static _cachedBlockhashInfo: BlockhashInfo | null = null;

/** Cached priority fee to reduce the amount of requests to the RPC endpoint */
private static _cachedPriorityFee: PriorityFeeInfo | null = null;

/**
* Get the latest blockhash from the cache or fetch the latest if the cached
* blockhash has expired
* blockhash has expired.
*
* @param conn Connection object to use
* @param conn `Connection` object to use
* @param force whether to force fetch the latest blockhash
*
* @returns the latest blockhash
*/
private static async _getLatestBlockhash(conn: Connection, force?: boolean) {
// Check whether the latest saved blockhash is still valid
const currentTs = PgCommon.getUnixTimstamp();
const timestamp = PgCommon.getUnixTimstamp();

// Blockhashes are valid for 150 slots, optimal block time is ~400ms
// For finalized: (150 - 32) * 0.4 = 47.2s ~= 45s (to be safe)
if (
force ||
!this._cachedBlockhashInfo ||
currentTs > this._cachedBlockhashInfo.timestamp + 45
timestamp > this._cachedBlockhashInfo.timestamp + 45
) {
this._cachedBlockhashInfo = {
blockhash: (await conn.getLatestBlockhash()).blockhash,
timestamp: currentTs,
timestamp,
};
}

return this._cachedBlockhashInfo.blockhash;
}

/**
* Get the priority fee information from the cache or fetch the latest if the
* cache has expired.
*
* @param conn `Connection` object to use
* @returns the priority fee information
*/
private static async _getPriorityFee(conn: Connection) {
// Check whether the priority fee info has expired
const timestamp = PgCommon.getUnixTimstamp();

// There is not a perfect way to estimate for how long the priority fee will
// be valid since it's a guess about the future based on the past data
if (
!this._cachedPriorityFee ||
timestamp > this._cachedPriorityFee.timestamp + 60
) {
const result = await conn.getRecentPrioritizationFees();
const fees = result.map((fee) => fee.prioritizationFee);

this._cachedPriorityFee = {
min: Math.min(...fees),
max: Math.max(...fees),
avg: Math.ceil(fees.reduce((acc, cur) => acc + cur) / fees.length),
timestamp,
};
}

return this._cachedPriorityFee;
}
}

0 comments on commit dbf2837

Please sign in to comment.