Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Rendimiento y optimización14 min de lectura

Tarifas prioritarias de Solana: crea un estimador local con getRecentPrioritizationFees

Crea un estimador local robusto de tarifas prioritarias a partir de getRecentPrioritizationFees de Solana, gestionando muestras cero, alcance, percentiles y obsolescencia.

TL;DR

getRecentPrioritizationFees devuelve una ventana de ranuras recientes con muestras por ranura, cada una un par { slot, prioritizationFee } donde prioritizationFee es la tarifa que una transacción pagadora estableció para esa ranura en micro-lamports por unidad de cómputo, no una tarifa total. Una llamada ingenua a toda la cadena puede devolver un arreglo de puros ceros o una muestra dominada por transacciones que no pagan, por lo que un estimador robusto debe manejar explícitamente la ausencia de muestras y los ceros en lugar de promediar a ciegas. Acotar la llamada a la cuenta caliente escribible de tu programa produce una señal más relevante que una muestra de toda la cadena, y seleccionar un percentil (p50 para sensibilidad al costo, p75–p90 para inclusión más rápida) a partir de valores ordenados es más robusto que la media. Combina los micro-lamports por CU elegidos con el límite de unidades de cómputo de la transacción para obtener los lamports extra reales, y vuelve a consultar en un intervalo corto porque las muestras cambian cada ranura. Este artículo crea un estimador local en Node.js, muestra cómo establecer SetComputeUnitPrice en una VersionedTransaction y proporciona una tabla de resultados reproducible y limitaciones honestas.

Qué devuelve realmente getRecentPrioritizationFees

El método JSON-RPC de Solana getRecentPrioritizationFees devuelve una ventana de ranuras recientes con muestras por ranura. Cada muestra es un par de { slot, prioritizationFee }, donde prioritizationFee es la tarifa que una transacción pagadora estableció para esa ranura en micro-lamports por unidad de cómputo — no una tarifa total en lamports. La referencia de Solana getRecentPrioritizationFees documenta los parámetros y las unidades, y la guía de tarifas prioritarias de Solana explica cómo se relacionan los micro-lamports por unidad de cómputo con SetComputeUnitPrice.

El método acepta un parámetro opcional addresses. Cuando se proporciona, el nodo acota la muestra a las ranuras cuyas transacciones tocaron esas cuentas escribibles. Esto importa porque una muestra de toda la cadena mezcla el comportamiento de tarifas de todos los programas, mientras que acotar a la cuenta caliente de tu programa da una señal que refleja la contención que tu transacción realmente enfrentará. El artículo Estimación de tarifas prioritarias y precio por unidad de cómputo en Solana enmarca el concepto de precio por unidad de cómputo; este artículo se centra en construir el estimador en sí.

Debido a que la respuesta es una ventana de ranuras recientes, es inherentemente una instantánea. Las muestras cambian cada ranura, por lo que cualquier valor en caché envejece rápidamente. Trata el arreglo como una observación de corta vida, no como un valor de configuración estable.

  • Cada muestra: { slot, prioritizationFee }.
  • Unidad de prioritizationFee: micro-lamports por unidad de cómputo.
  • El parámetro opcional addresses acota la muestra a las ranuras que tocan esas cuentas escribibles.
  • La ventana es reciente y cambia cada ranura.

Por qué una llamada ingenua a toda la cadena no es confiable

Una llamada ingenua sin el parámetro addresses puede devolver un arreglo de puros ceros o una muestra dominada por transacciones que no pagan. Esta es una trampa bien conocida: muchas transacciones no establecen tarifa prioritaria, por lo que la distribución de toda la cadena está fuertemente sesgada hacia cero. Promediar esa distribución produce un número que técnicamente se deriva de los datos pero que en la práctica es inútil para la inclusión bajo contención.

El segundo modo de fallo es un resultado vacío o de puros ceros. Si cada muestra es cero, la media es cero, y un estimador ingenuo establecerá una tarifa prioritaria de cero. Un estimador robusto debe manejar 'sin muestras' y 'todos ceros' explícitamente — por ejemplo, recurriendo a un piso configurado en lugar de emitir cero.

El tercer modo de fallo es el desajuste de alcance. Una muestra de toda la cadena puede verse saludable mientras tu cuenta escribible específica está fría, o viceversa. Acotar a la cuenta que tu transacción escribirá da una señal más relevante, pero también puede devolver menos muestras, lo que hace que el manejo explícito de arreglos vacíos sea aún más importante.

  • Arreglos de puros ceros: comunes cuando pocas transacciones pagan tarifa prioritaria.
  • Dominancia de no pagadores: la media se arrastra hacia cero.
  • Desajuste de alcance: la señal de toda la cadena puede no reflejar la contención de tu cuenta.
  • El manejo explícito de sin muestras y todos ceros es obligatorio.

Acotar la muestra a tu cuenta caliente escribible

El parámetro addresses acepta una lista de cuentas escribibles. El nodo devuelve muestras solo para las ranuras cuyas transacciones tocaron esas cuentas. Para un programa con una sola cuenta de estado caliente, pasar esa cuenta enfoca la muestra en la contención que importa. Para un programa con múltiples cuentas calientes, puedes pasar varias, pero ten en cuenta que la ventana devuelta puede ser más dispersa.

Acotar no es gratis: un alcance estrecho puede devolver menos muestras y, en períodos tranquilos, puede no devolver ninguna. Por eso el estimador debe tratar un resultado acotado vacío como una señal para recurrir a algo — ya sea a un alcance más amplio o a un piso configurado — en lugar de como una tarifa cero.

Cuando operas contra un endpoint gestionado como la red Solana de OnFinality, la vista del nodo sobre las ranuras recientes es lo que refleja el método. Distintos endpoints pueden devolver muestras diferentes, por lo que el estimador debe validarse contra el endpoint por el que realmente envías.

  • Pasa la cuenta escribible que tu transacción tocará.
  • Un alcance estrecho puede devolver menos muestras o ninguna.
  • Recurre a un alcance más amplio o a un piso cuando las muestras acotadas estén vacías.
  • Valida contra el endpoint por el que envías.

Elegir un percentil en lugar de la media

Una vez que tienes una muestra no vacía y que no es de puros ceros, ordena los valores de prioritizationFee y selecciona un percentil. La media es sensible a valores atípicos y a la cola cargada de ceros; un percentil es un resumen más estable de la distribución que te importa. p50 es apropiado cuando domina la sensibilidad al costo y aceptas una inclusión más lenta. p75 a p90 es apropiado cuando la inclusión más rápida importa más que el costo.

Después de seleccionar un percentil, aplica un piso y un techo. El piso evita que un resultado cero o casi cero produzca una transacción que no se incluirá bajo contención. El techo evita que una sola muestra atípica produzca una tarifa absurda. Ambos límites deben ser valores de configuración que puedas ajustar por programa, no constantes codificadas enterradas en el estimador.

El valor elegido está en micro-lamports por unidad de cómputo. Para convertirlo a los lamports extra que paga el usuario, multiplícalo por el límite de unidades de cómputo de la transacción y divídelo entre un millón. Esa cantidad extra se suma a la tarifa base, y es el número que debe aparecer en cualquier estimación de cara al usuario.

  • Ordena los valores de prioritizationFee y luego selecciona p50, p75 o p90.
  • Aplica un piso para evitar transacciones con tarifa cero.
  • Aplica un techo para acotar la influencia de valores atípicos.
  • Lamports extra = micro-lamports por CU × límite de unidades de cómputo ÷ 1,000,000.

Recencia, cadencia y detección de obsolescencia

Las muestras cambian cada ranura, por lo que almacenar en caché una tarifa durante minutos es un error de corrección, no una optimización. Vuelve a consultar en un intervalo corto o inmediatamente antes de cada envío. Si debes usar caché, hazlo por una duración medida en ranuras, no en minutos, e invalídala de forma agresiva.

Detecta una ventana de muestras obsoleta leyendo la ranura actual y comparándola con la ranura máxima de las muestras devueltas. Si la diferencia supera tu tolerancia, trata la ventana como obsoleta y vuelve a consultar. El artículo Niveles de compromiso y confirmación de transacciones en Solana explica cómo el compromiso afecta lo que el nodo considera reciente.

La cadencia interactúa con los límites de tasa. Si vuelves a consultar antes de cada envío, tu volumen de solicitudes escala con tu volumen de transacciones. Los artículos Tiempos de espera y reintentos de RPC en Solana y Guía de latencia de RPC en Solana cubren el lado operativo; el estimador debe tratar una consulta fallida como una razón para usar el último valor bueno conocido más un piso, no para enviar una tarifa cero.

  • Vuelve a consultar en un intervalo corto o antes de cada envío.
  • Compara la ranura actual con la ranura máxima de la muestra para detectar obsolescencia.
  • Ante un fallo de consulta, usa el último valor bueno conocido más el piso.
  • El volumen de solicitudes escala con el volumen de transacciones.

Estimador ejecutable en Node.js con y sin alcance de direcciones

El siguiente ejemplo llama a getRecentPrioritizationFees dos veces — una a toda la cadena y otra acotada a una cuenta escribible — filtra las muestras cero de referencia, calcula p50, p75 y p90, e imprime los resultados. Usa la forma de solicitud estándar JSON-RPC 2.0 descrita en la especificación JSON-RPC 2.0 y las convenciones JSON-RPC de Ethereum para la estructura de método/parámetros donde corresponda.

Reemplaza el endpoint y la dirección acotada por los tuyos. El ejemplo deliberadamente no afirma ningún número de latencia o rendimiento; solo demuestra la solicitud y el cálculo del percentil.

const ENDPOINT = process.env.SOLANA_RPC_URL || 'https://your-endpoint.example';
const SCOPED_ADDRESS = process.env.SCOPED_ADDRESS || 'YourWritableAccountPubkey';

async function rpc(method, params) {
  const res = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(JSON.stringify(json.error));
  return json.result;
}

function percentile(sorted, p) {
  if (sorted.length === 0) return null;
  const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));
  return sorted[idx];
}

function summarize(samples) {
  const fees = samples
    .map(s => s.prioritizationFee)
    .filter(f => typeof f === 'number' && f > 0)
    .sort((a, b) => a - b);
  if (fees.length === 0) return { count: 0, p50: null, p75: null, p90: null };
  return {
    count: fees.length,
    p50: percentile(fees, 50),
    p75: percentile(fees, 75),
    p90: percentile(fees, 90)
  };
}

(async () => {
  const chainWide = await rpc('getRecentPrioritizationFees', []);
  const scoped = await rpc('getRecentPrioritizationFees', [[SCOPED_ADDRESS]]);
  console.log('chain-wide', summarize(chainWide));
  console.log('scoped', summarize(scoped));
})();

Establecer SetComputeUnitPrice en una VersionedTransaction

El valor elegido de micro-lamports por CU se aplica a la transacción mediante la instrucción SetComputeUnitPrice, que forma parte del programa de presupuesto de cómputo. La guía de tarifas prioritarias de Solana documenta esta instrucción. El ejemplo a continuación construye una VersionedTransaction, establece el límite de unidades de cómputo y el precio por unidad de cómputo, e imprime los micro-lamports por CU resultantes.

Ten en cuenta que el límite de unidades de cómputo y el precio por unidad de cómputo son ajustes separados. El límite acota cuánto cómputo puede consumir la transacción; el precio establece cuánto pagas por unidad. Los lamports extra que paga el usuario son el producto de ambos, dividido entre un millón.

const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = require('@solana/web3.js');

const ENDPOINT = process.env.SOLANA_RPC_URL || 'https://your-endpoint.example';
const connection = new Connection(ENDPOINT, 'confirmed');

async function buildTx(payer, instructions, microLamportsPerCU, computeUnitLimit) {
  const { blockhash } = await connection.getLatestBlockhash('confirmed');
  const message = new TransactionMessage({
    payerKey: payer,
    recentBlockhash: blockhash,
    instructions: [
      ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }),
      ComputeBudgetProgram.setComputeUnitPrice({ microLamports: microLamportsPerCU }),
      ...instructions
    ]
  }).compileToV0Message();
  const tx = new VersionedTransaction(message);
  console.log('micro-lamports per CU:', microLamportsPerCU);
  console.log('compute unit limit:', computeUnitLimit);
  console.log('extra lamports:', Math.floor((microLamportsPerCU * computeUnitLimit) / 1_000_000));
  return tx;
}

module.exports = { buildTx };

Tabla de resultados reproducible para tu endpoint

Debido a que el método refleja la vista del nodo local sobre las ranuras recientes, distintos endpoints pueden devolver muestras diferentes. Para comparar endpoints o ajustar tu estimador, completa la tabla a continuación con tus propias mediciones. No trates ningún número publicado como sustituto de medir contra el endpoint por el que realmente envías.

Ejecuta el estimador con una cadencia fija, registra el recuento de muestras y los percentiles, y anota la ranura actual y la ranura máxima de la muestra para poder calcular la obsolescencia. Repite en varios intervalos para ver la varianza.

  • Endpoint: la URL de RPC que llamaste.
  • Alcance: toda la cadena o la cuenta escribible utilizada.
  • Recuento de muestras: número de valores de prioritizationFee distintos de cero.
  • p50 / p75 / p90: micro-lamports por CU.
  • Ranura actual y ranura máxima de la muestra: para la obsolescencia.
  • Tarifa elegida y piso/techo: lo que realmente estableciste.

Limitaciones y compensaciones

Los valores están en micro-lamports por unidad de cómputo y no deben confundirse con lamports totales. Una tarifa entre p50 y p90 aún no garantiza la inclusión bajo congestión; es una heurística, no un contrato. El método refleja la vista del nodo local sobre las ranuras recientes, por lo que distintos endpoints pueden devolver muestras diferentes, y una llamada acotada puede devolver menos muestras que una llamada a toda la cadena.

La preverificación mediante simulateTransaction es una señal separada del mercado de tarifas. Una simulación exitosa dice que la transacción probablemente se ejecutará; no dice nada sobre si la tarifa es competitiva. A la inversa, una tarifa competitiva no garantiza que la transacción se simule con éxito. Trátalas como entradas independientes.

Por último, el estimador es tan bueno como su cadencia. Una ventana obsoleta produce una tarifa obsoleta, y un respaldo de tarifa cero produce una transacción que puede que nunca se confirme. El piso y el techo son los rieles de seguridad que evitan que el estimador emita valores patológicos.

  • Micro-lamports por CU ≠ lamports totales.
  • p50–p90 no garantiza la inclusión.
  • La vista del endpoint difiere; las muestras acotadas pueden ser escasas.
  • La preverificación y el mercado de tarifas son señales independientes.
  • Las ventanas obsoletas y los respaldos de cero son los principales modos de fallo.

Solución de problemas comunes del estimador

Cuando el estimador devuelve cero, primero verifica si la respuesta sin procesar fue un arreglo vacío o un arreglo de puros ceros. Si estaba vacío, tu alcance puede ser demasiado estrecho o el endpoint puede estar atrasado. Si era de puros ceros, la muestra está dominada por transacciones que no pagan; amplía el alcance o sube el piso.

Cuando el estimador devuelve un valor que parece demasiado alto, busca una sola muestra atípica y confirma que se aplica el techo. Cuando la transacción no se incluye a pesar de una tarifa competitiva, revisa el límite de unidades de cómputo — un límite subestimado puede hacer que la transacción falle antes de que la tarifa importe. La guía de la API de Solana (RPC Assistant) cubre diagnósticos a nivel de endpoint, y el centro de aprendizaje de OnFinality reúne guías operativas relacionadas.

Cuando los resultados difieren entre endpoints, eso es esperado. Registra ambos en la tabla de resultados y decide por qué endpoint enviarás. Si necesitas un endpoint gestionado con comportamiento consistente, revisa las tarifas de RPC y las opciones del servicio de API.

  • Resultado cero: distingue entre arreglo vacío y arreglo de puros ceros.
  • Demasiado alto: busca valores atípicos y confirma el techo.
  • No incluida: revisa el límite de unidades de cómputo y la preverificación.
  • Diferencias entre endpoints: registra ambos y elige tu ruta de envío.

Próximos pasos para el despliegue en producción

Coloca el estimador detrás de un pequeño servicio que vuelva a consultar en un intervalo corto, aplique el piso y el techo, y exponga los micro-lamports por CU elegidos a tu constructor de transacciones. Registra el recuento de muestras, los percentiles y la obsolescencia para poder diagnosticar regresiones. Valida contra el endpoint por el que envías, y revisa el piso y el techo a medida que cambia la contención de tu programa.

Para temas operativos relacionados, consulta el artículo Estimación de tarifas prioritarias y precio por unidad de cómputo en Solana para el marco conceptual, y los artículos Tiempos de espera y reintentos de RPC en Solana y Guía de latencia de RPC en Solana para el lado del transporte. Cuando estés listo para comparar endpoints, la página de la red Solana de OnFinality y las tarifas de RPC son los puntos de partida.

  • Envuelve el estimador en un pequeño servicio que vuelva a consultar.
  • Registra el recuento de muestras, los percentiles y la obsolescencia.
  • Valida contra tu endpoint de envío.
  • Revisa el piso y el techo a medida que cambia la contención.

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar