Skip to content

Instantly share code, notes, and snippets.

@metaproph3t
Created December 16, 2024 05:05
Show Gist options
  • Save metaproph3t/a63c824bcd784ee2ac341a66ae53cda6 to your computer and use it in GitHub Desktop.
Save metaproph3t/a63c824bcd784ee2ac341a66ae53cda6 to your computer and use it in GitHub Desktop.
import { WalletContextState } from "@solana/wallet-adapter-react";
import {
Transaction,
Connection,
SignatureStatus,
ComputeBudgetProgram,
} from "@solana/web3.js";
// Taken from:
// https://github.com/rpcpool/solana-prioritization-fees-api/
import {
getMedianPrioritizationFeeByPercentile,
PriotitizationFeeLevels,
} from "./grpf";
import { backOff } from "exponential-backoff";
// A minute of retries, with 2 second intervals
const RETRY_INTERVAL_MS = 2000;
const MAX_RETRIES = 30;
// 10k CUs is 0.03 cents, assuming 150k CUs and $250 SOL
const MIN_CU_PRICE = 10_000;
// 10M CUs is $0.38, assuming 150k CUs and $250 SOL
const MAX_CU_PRICE = 10_000_000;
type TxStatusUpdate =
| { status: "created" }
| { status: "signed" }
| { status: "sent"; signature: string }
| { status: "confirmed"; result: SignatureStatus };
// Sign and send a transaction with a priority fee. Assumes that the caller has already set the compute unit limit.
export async function signAndSendTransaction(
connection: Connection,
wallet: WalletContextState,
txn: Transaction,
onStatusUpdate?: (status: TxStatusUpdate) => void,
) {
if (!wallet.publicKey) {
throw new Error("Wallet not connected");
}
if (!wallet.signTransaction) {
throw new Error("Wallet does not support signing transactions");
}
let latestBlockhash;
try {
latestBlockhash = await backOff(
async () => (await connection.getLatestBlockhash("confirmed")).blockhash,
);
} catch (e) {
throw new Error("Unable to get latest blockhash");
}
txn.recentBlockhash = latestBlockhash;
txn.feePayer = wallet.publicKey;
let medianPriorityFee;
try {
medianPriorityFee = await backOff(
async () =>
await getMedianPrioritizationFeeByPercentile(connection, {
percentile: PriotitizationFeeLevels.HIGH,
}),
);
} catch (e) {
throw new Error("Unable to get median priority fee");
}
const priorityFee = Math.min(
Math.max(medianPriorityFee, MIN_CU_PRICE),
MAX_CU_PRICE,
);
txn.add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: priorityFee,
}),
);
onStatusUpdate?.({ status: "created" });
let signedTx;
try {
signedTx = await wallet.signTransaction(txn);
} catch (e) {
throw new Error("Wallet rejected transaction");
}
onStatusUpdate?.({ status: "signed" });
let retries = 0;
let signature: string | null = null;
let status: SignatureStatus | null = null;
while (retries < MAX_RETRIES) {
await Promise.all([
(async () => {
try {
const isFirstSend = signature === null;
signature = await connection.sendRawTransaction(
signedTx.serialize(),
{
skipPreflight: true,
preflightCommitment: "confirmed",
maxRetries: 0,
},
);
if (isFirstSend) {
onStatusUpdate?.({ status: "sent", signature });
}
} catch (e) {
console.error(e);
}
})(),
(async () => {
if (signature) {
try {
const response = await connection.getSignatureStatus(signature);
if (response.value) {
status = response.value;
}
} catch (e) {
console.error(e);
}
}
})(),
]);
retries++;
if (
status &&
((status as SignatureStatus).confirmationStatus === "confirmed" ||
(status as SignatureStatus).confirmationStatus === "finalized")
) {
onStatusUpdate?.({ status: "confirmed", result: status });
break;
}
await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL_MS));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment