@orca-so/tx-sender
Version:
Send transactions to the Solana blockchain with auto priority fees.
553 lines (543 loc) • 18.1 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
DEFAULT_COMPUTE_UNIT_MARGIN_MULTIPLIER: () => DEFAULT_COMPUTE_UNIT_MARGIN_MULTIPLIER,
DEFAULT_PRIORITIZATION: () => DEFAULT_PRIORITIZATION,
buildAndSendTransaction: () => buildAndSendTransaction,
buildTransaction: () => buildTransaction,
getComputeUnitMarginMultiplier: () => getComputeUnitMarginMultiplier,
getJitoBlockEngineUrl: () => getJitoBlockEngineUrl,
getJitoConfig: () => getJitoConfig,
getPriorityFeeConfig: () => getPriorityFeeConfig,
getRpcConfig: () => getRpcConfig,
rpcFromUrl: () => rpcFromUrl,
sendTransaction: () => sendTransaction,
setComputeUnitMarginMultiplier: () => setComputeUnitMarginMultiplier,
setJitoBlockEngineUrl: () => setJitoBlockEngineUrl,
setJitoFeePercentile: () => setJitoFeePercentile,
setJitoTipSetting: () => setJitoTipSetting,
setPriorityFeePercentile: () => setPriorityFeePercentile,
setPriorityFeeSetting: () => setPriorityFeeSetting,
setRpc: () => setRpc
});
module.exports = __toCommonJS(index_exports);
// src/compatibility.ts
var import_rpc = require("@solana/rpc");
var import_kit = require("@solana/kit");
function rpcFromUrl(url) {
const api = (0, import_rpc.createSolanaRpcApi)({
defaultCommitment: "confirmed"
});
const transport = (0, import_kit.createDefaultRpcTransport)({ url });
const rpc = (0, import_rpc.createRpc)({ api, transport });
return rpc;
}
function normalizeAddresses(addresses) {
return addresses?.map((addr) => (0, import_kit.address)(addr)) ?? [];
}
// src/config.ts
var globalConfig = {};
var DEFAULT_COMPUTE_UNIT_MARGIN_MULTIPLIER = 1.1;
var DEFAULT_PRIORITIZATION = {
priorityFee: {
type: "dynamic",
maxCapLamports: BigInt(4e6),
// 0.004 SOL
priorityFeePercentile: "50"
},
jito: {
type: "dynamic",
maxCapLamports: BigInt(4e6),
// 0.004 SOL
priorityFeePercentile: "50"
},
computeUnitMarginMultiplier: DEFAULT_COMPUTE_UNIT_MARGIN_MULTIPLIER,
jitoBlockEngineUrl: "https://bundles.jito.wtf"
};
var getRpcConfig = () => {
const rpcConfig = globalConfig.rpcConfig;
if (!rpcConfig?.rpcUrl) {
throw new Error("Connection not initialized. Call setRpc() first");
}
return rpcConfig;
};
var getPriorityConfig = () => {
if (!globalConfig.transactionConfig) {
return DEFAULT_PRIORITIZATION;
}
return globalConfig.transactionConfig;
};
var getJitoConfig = () => {
return getPriorityConfig().jito;
};
var getPriorityFeeConfig = () => {
return getPriorityConfig().priorityFee;
};
var getComputeUnitMarginMultiplier = () => {
return getPriorityConfig().computeUnitMarginMultiplier;
};
var getJitoBlockEngineUrl = () => {
return getPriorityConfig().jitoBlockEngineUrl;
};
var setGlobalConfig = (config) => {
globalConfig = {
transactionConfig: config.transactionConfig || DEFAULT_PRIORITIZATION,
rpcConfig: config.rpcConfig
};
};
async function setRpc(url, options = {}) {
const rpc = rpcFromUrl(url);
const chainId = await getChainIdFromGenesisHash(rpc);
setGlobalConfig({
...globalConfig,
rpcConfig: {
rpcUrl: url,
supportsPriorityFeePercentile: options.supportsPriorityFeePercentile ?? false,
chainId,
pollIntervalMs: options.pollIntervalMs ?? 0,
resendOnPoll: options.resendOnPoll ?? true
}
});
const nonThenableRpc = new Proxy(rpc, {
get(target, prop, receiver) {
if (prop === "then") {
return void 0;
}
return Reflect.get(target, prop, receiver);
}
});
return nonThenableRpc;
}
async function getChainIdFromGenesisHash(rpc) {
try {
const genesisHash = await rpc.getGenesisHash().send();
const genesisHashToChainId = {
"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d": "solana",
EAQLJCV2mh23BsK2P9oYpV5CHVLDNHTxYss3URrNmg3s: "eclipse",
EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG: "solana-devnet",
CX4huckiV9QNAkKNVKi5Tj8nxzBive5kQimd94viMKsU: "eclipse-testnet"
};
return genesisHashToChainId[genesisHash] || "unknown";
} catch (error) {
console.warn("Error getting chain ID from genesis hash", error);
return "unknown";
}
}
async function setJitoBlockEngineUrl(url) {
setGlobalConfig({
...globalConfig,
transactionConfig: {
...getPriorityConfig(),
jitoBlockEngineUrl: url
}
});
}
function setPriorityFeeSetting(priorityFee) {
setGlobalConfig({
...globalConfig,
transactionConfig: {
...getPriorityConfig(),
priorityFee
}
});
}
function setJitoTipSetting(jito) {
setGlobalConfig({
...globalConfig,
transactionConfig: {
...getPriorityConfig(),
jito
}
});
}
function setComputeUnitMarginMultiplier(multiplier) {
setGlobalConfig({
...globalConfig,
transactionConfig: {
...getPriorityConfig(),
computeUnitMarginMultiplier: multiplier
}
});
}
function setJitoFeePercentile(percentile) {
const jito = getPriorityConfig().jito;
setGlobalConfig({
...globalConfig,
transactionConfig: {
...getPriorityConfig(),
jito: {
...jito,
priorityFeePercentile: percentile
}
}
});
}
function setPriorityFeePercentile(percentile) {
const priorityConfig = getPriorityConfig();
const priorityFee = priorityConfig.priorityFee;
setGlobalConfig({
...globalConfig,
transactionConfig: {
...priorityConfig,
priorityFee: {
...priorityFee,
priorityFeePercentile: percentile
}
}
});
}
// src/buildTransaction.ts
var import_kit4 = require("@solana/kit");
var import_address_lookup_table = require("@solana-program/address-lookup-table");
// src/priorityFees.ts
var import_compute_budget2 = require("@solana-program/compute-budget");
// src/jito.ts
var import_system = require("@solana-program/system");
var import_kit2 = require("@solana/kit");
async function processJitoTipForTxMessage(message, signer, jito, chainId) {
if (chainId !== "solana") {
console.warn("Jito tip is not supported on this chain. Skipping jito tip.");
return message;
}
let jitoTipLamports = BigInt(0);
if (jito.type === "exact") {
jitoTipLamports = jito.amountLamports;
} else if (jito.type === "dynamic") {
jitoTipLamports = await recentJitoTip(jito.priorityFeePercentile);
}
if (jitoTipLamports > 0) {
return (0, import_kit2.prependTransactionMessageInstruction)(
(0, import_system.getTransferSolInstruction)({
source: signer,
destination: getJitoTipAddress(),
amount: jitoTipLamports
}),
message
);
} else {
return message;
}
}
async function recentJitoTip(priorityFeePercentile) {
const blockEngineUrl = getJitoBlockEngineUrl();
const response = await fetch(`${blockEngineUrl}/api/v1/bundles/tip_floor`);
if (!response.ok) {
return BigInt(0);
}
const data = await response.json().then((res) => res[0]);
const percentileToKey = {
"25": "landed_tips_25th_percentile",
"50": "landed_tips_50th_percentile",
"75": "landed_tips_75th_percentile",
"95": "landed_tips_95th_percentile",
"99": "landed_tips_99th_percentile",
"50ema": "ema_landed_tips_50th_percentile"
};
const key = percentileToKey[priorityFeePercentile ?? "50"];
if (!key || !data[key]) {
return BigInt(0);
}
return (0, import_kit2.lamports)(BigInt(Math.floor(Number(data[key]) * 10 ** 9))).valueOf();
}
var jitoTipAddresses = [
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT"
];
function getJitoTipAddress() {
return (0, import_kit2.address)(
jitoTipAddresses[Math.floor(Math.random() * jitoTipAddresses.length)]
);
}
// src/computeBudget.ts
var import_compute_budget = require("@solana-program/compute-budget");
var import_kit3 = require("@solana/kit");
async function processComputeBudgetForTxMessage(message, computeUnits) {
const { rpcUrl, supportsPriorityFeePercentile } = getRpcConfig();
const priorityFee = getPriorityFeeConfig();
const computeUnitMarginMultiplier = getComputeUnitMarginMultiplier();
let priorityFeeMicroLamports = BigInt(0);
if (priorityFee.type === "exact") {
priorityFeeMicroLamports = priorityFee.amountLamports * BigInt(1e6) / BigInt(computeUnits);
} else if (priorityFee.type === "dynamic") {
const estimatedPriorityFee = await calculateDynamicPriorityFees(
message.instructions,
rpcUrl,
supportsPriorityFeePercentile,
priorityFee.priorityFeePercentile ?? "50"
);
if (!priorityFee.maxCapLamports) {
priorityFeeMicroLamports = estimatedPriorityFee;
} else {
const maxCapMicroLamports = priorityFee.maxCapLamports * BigInt(1e6) / BigInt(computeUnits);
priorityFeeMicroLamports = maxCapMicroLamports > estimatedPriorityFee ? estimatedPriorityFee : maxCapMicroLamports;
}
}
if (priorityFeeMicroLamports > 0) {
message = (0, import_kit3.prependTransactionMessageInstruction)(
(0, import_compute_budget.getSetComputeUnitPriceInstruction)({
microLamports: priorityFeeMicroLamports
}),
message
);
}
message = (0, import_kit3.prependTransactionMessageInstruction)(
(0, import_compute_budget.getSetComputeUnitLimitInstruction)({
units: Math.ceil(
computeUnits * (computeUnitMarginMultiplier ?? DEFAULT_COMPUTE_UNIT_MARGIN_MULTIPLIER)
)
}),
message
);
return message;
}
function getWritableAccounts(ixs) {
const writable = /* @__PURE__ */ new Set();
ixs.forEach((ix) => {
if (ix.accounts) {
ix.accounts.forEach((acc) => {
if ((0, import_kit3.isWritableRole)(acc.role)) writable.add(acc.address);
});
}
});
return Array.from(writable);
}
async function calculateDynamicPriorityFees(instructions, rpcUrl, supportsPercentile, percentile) {
const writableAccounts = getWritableAccounts(instructions);
if (supportsPercentile) {
return await getRecentPrioritizationFeesWithPercentile(
rpcUrl,
writableAccounts,
percentile
);
} else {
const rpc = rpcFromUrl(rpcUrl);
const recent = await rpc.getRecentPrioritizationFees(writableAccounts).send();
const nonZero = recent.filter((pf) => pf.prioritizationFee > 0).map((pf) => pf.prioritizationFee);
const sorted = nonZero.sort((a, b) => Number(a - b));
return sorted[Math.floor(sorted.length * (parseInt(percentile) / 100))] || BigInt(0);
}
}
async function getRecentPrioritizationFeesWithPercentile(rpcEndpoint, writableAccounts, percentile) {
const response = await fetch(rpcEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "getRecentPrioritizationFees",
params: [
{
lockedWritableAccounts: writableAccounts,
percentile: parseInt(percentile) * 100
}
]
})
});
const data = await response.json();
if (data.error) {
throw new Error(`RPC error: ${data.error.message}`);
}
const last150Slots = data.result;
last150Slots.sort((a, b) => Number(a.slot - b.slot));
const last50Slots = last150Slots.slice(-50);
const nonZeroFees = last50Slots.filter((slot) => slot.prioritizationFee > 0);
if (nonZeroFees.length === 0) return BigInt(0);
const sorted = nonZeroFees.map((slot) => slot.prioritizationFee).sort((a, b) => Number(a - b));
const medianIndex = Math.floor(sorted.length / 2);
return sorted[medianIndex];
}
// src/priorityFees.ts
async function addPriorityInstructions(message, signer) {
const { rpcUrl, chainId } = getRpcConfig();
const jito = getJitoConfig();
const rpc = rpcFromUrl(rpcUrl);
if (jito.type !== "none") {
message = await processJitoTipForTxMessage(message, signer, jito, chainId);
}
let computeUnits = await getComputeUnitsForTxMessage(rpc, message);
return processComputeBudgetForTxMessage(message, computeUnits);
}
async function getComputeUnitsForTxMessage(rpc, txMessage) {
const estimator = (0, import_compute_budget2.estimateComputeUnitLimitFactory)({
rpc
});
try {
const estimate = await estimator(txMessage);
return estimate;
} catch {
console.warn(
"Transaction simulation failed, using 1,400,000 compute units"
);
return 14e5;
}
}
// src/buildTransaction.ts
async function buildTransaction(instructions, feePayer, lookupTableAddresses) {
return buildTransactionMessage(
instructions,
feePayer,
normalizeAddresses(lookupTableAddresses)
);
}
async function buildTransactionMessage(instructions, feePayer, lookupTableAddresses) {
const { rpcUrl } = getRpcConfig();
const rpc = rpcFromUrl(rpcUrl);
let message = await prepareTransactionMessage(instructions, rpc, feePayer);
if (lookupTableAddresses?.length) {
const lookupTableAccounts = await (0, import_address_lookup_table.fetchAllMaybeAddressLookupTable)(
rpc,
lookupTableAddresses
);
const tables = lookupTableAccounts.reduce(
(prev, account) => {
if (account.exists) {
(0, import_kit4.assertAccountDecoded)(account);
prev[account.address] = account.data.addresses;
}
return prev;
},
{}
);
message = (0, import_kit4.compressTransactionMessageUsingAddressLookupTables)(
message,
tables
);
}
const messageWithPriorityFees = await addPriorityInstructions(
message,
feePayer
);
return await (0, import_kit4.partiallySignTransactionMessageWithSigners)(
messageWithPriorityFees
);
}
async function prepareTransactionMessage(instructions, rpc, feePayer) {
const { value: blockhash } = await rpc.getLatestBlockhash({
commitment: "confirmed"
}).send();
return (0, import_kit4.pipe)(
(0, import_kit4.createTransactionMessage)({ version: 0 }),
(tx) => (0, import_kit4.setTransactionMessageLifetimeUsingBlockhash)(blockhash, tx),
(tx) => (0, import_kit4.setTransactionMessageFeePayerSigner)(feePayer, tx),
(tx) => (0, import_kit4.appendTransactionMessageInstructions)(instructions, tx)
);
}
// src/sendTransaction.ts
var import_kit5 = require("@solana/kit");
async function buildAndSendTransaction(instructions, payer, lookupTableAddresses, commitment = "confirmed") {
const tx = await buildTransaction(instructions, payer, lookupTableAddresses);
(0, import_kit5.assertIsFullySignedTransaction)(tx);
return sendTransaction(tx, commitment);
}
async function sendTransaction(transaction, commitment = "confirmed") {
(0, import_kit5.assertIsFullySignedTransaction)(transaction);
const { rpcUrl, pollIntervalMs, resendOnPoll } = getRpcConfig();
const rpc = rpcFromUrl(rpcUrl);
const txHash = getTxHash(transaction);
const encodedTransaction = (0, import_kit5.getBase64EncodedWireTransaction)(transaction);
const simResult = await rpc.simulateTransaction(encodedTransaction, {
encoding: "base64"
}).send();
if (simResult.value.err) {
throw new Error(
`Transaction simulation failed: ${JSON.stringify(
simResult.value.err,
(key, value) => typeof value === "bigint" ? value.toString() : value
)}`
);
}
const sendTx = async () => {
await rpc.sendTransaction(encodedTransaction, {
skipPreflight: true,
encoding: "base64",
...resendOnPoll && { maxRetries: BigInt(0) }
}).send();
};
try {
await sendTx();
} catch (error) {
throw new Error(`Failed to send transaction: ${error}`);
}
const expiryTime = Date.now() + 9e4;
while (Date.now() < expiryTime) {
const iterationStart = Date.now();
try {
const { value } = await rpc.getSignatureStatuses([txHash]).send();
const status = value[0];
if (status?.confirmationStatus === commitment) {
if (status.err) {
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
}
return txHash;
}
} catch {
}
if (resendOnPoll) {
try {
await sendTx();
} catch {
}
}
if (pollIntervalMs > 0) {
const elapsed = Date.now() - iterationStart;
const remainingDelay = pollIntervalMs - elapsed;
if (remainingDelay > 0) {
await new Promise((resolve) => setTimeout(resolve, remainingDelay));
}
}
}
throw new Error("Transaction confirmation timeout");
}
function getTxHash(transaction) {
const [signature] = Object.values(transaction.signatures);
const txHash = (0, import_kit5.getBase58Decoder)().decode(signature);
return txHash;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
DEFAULT_COMPUTE_UNIT_MARGIN_MULTIPLIER,
DEFAULT_PRIORITIZATION,
buildAndSendTransaction,
buildTransaction,
getComputeUnitMarginMultiplier,
getJitoBlockEngineUrl,
getJitoConfig,
getPriorityFeeConfig,
getRpcConfig,
rpcFromUrl,
sendTransaction,
setComputeUnitMarginMultiplier,
setJitoBlockEngineUrl,
setJitoFeePercentile,
setJitoTipSetting,
setPriorityFeePercentile,
setPriorityFeeSetting,
setRpc
});
//# sourceMappingURL=index.cjs.map