Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Solución de problemas de RPC13 min de lectura

Estimación de tarifas prioritarias en Solana: setComputeUnitPrice en producción

Deriva un precio por unidad de cómputo por transacción a partir de getRecentPrioritizationFees, adjunta instrucciones de ComputeBudget y verifica la elección mediante la tasa de aterrizaje.

TL;DR

Las tarifas prioritarias en Solana no son un número único que se copia de un rastreador; son una decisión por transacción tomada a partir de datos de RPC. La fórmula de la tarifa es ceil(límite de unidades de cómputo x precio por unidad de cómputo / 1,000,000) lamports, por lo que tanto el límite como el precio son entradas. getRecentPrioritizationFees devuelve un arreglo de muestras { slot, prioritizationFee } de bloques recientes, y pasar las cuentas de escritura de la transacción acota esa muestra a los slots donde esas cuentas fueron escritas. Lee el resultado como una distribución, toma un percentil alto en lugar de la media y convierte un presupuesto en lamports a un precio por unidad de cómputo. Luego cierra el ciclo registrando slot-submitted y slot-confirmed para medir la tasa de aterrizaje, ajustando el percentil con histéresis. Este artículo proporciona un estimador ejecutable en Node.js, una tabla de resultados para completar con tu propio endpoint y un manual de solución de problemas para muestras vacías, métodos no soportados y sobrepago.

La fórmula de tarifas de Solana y por qué el límite de unidades de cómputo es una entrada

Las tarifas de transacción en Solana se documentan como una tarifa base fija más una tarifa de priorización. La tarifa base se cobra por firma, mientras que la tarifa de priorización se calcula como ceil(límite de unidades de cómputo x precio por unidad de cómputo / 1,000,000) lamports. La referencia autorizada es la documentación de Solana sobre la estructura de tarifas en https://solana.com/docs/core/fees. Debido a que el límite de unidades de cómputo aparece en el numerador, un límite establecido muy por encima del uso real infla la tarifa que pagas por el mismo precio por unidad de cómputo.

Por eso importa el orden de las operaciones. Primero establece un límite de unidades de cómputo realista, normalmente a partir de una simulación, y solo entonces elige un precio por unidad de cómputo. Si omites el límite y dejas el valor predeterminado, puedes pagar una tarifa de priorización sobre un margen que tu transacción nunca consume. El programa ComputeBudget (ComputeBudget111111111111111111111111111111) expone las instrucciones setComputeUnitLimit y setComputeUnitPrice; la documentación JSON-RPC de Solana para estas instrucciones y para getFeeForMessage es autorizada sobre cómo se consulta la tarifa por mensaje.

Una consecuencia práctica es que dos transacciones con el mismo precio por unidad de cómputo pueden pagar tarifas de priorización diferentes. La que tiene el límite más ajustado paga menos. Trata el límite como un parámetro de corrección y costo, no como una formalidad.

La referencia autorizada para ambas mitades de esa fórmula es la página de estructura de tarifas de la documentación de Solana, y el contrato de muestra usado a continuación se especifica en la referencia del método getRecentPrioritizationFees. Trata esas dos páginas como la fuente de verdad y este artículo como el procedimiento operativo construido sobre ellas.

  • Tarifa base: fija por firma, independiente del cómputo.
  • Tarifa de priorización: ceil(límite de unidades de cómputo x precio por unidad de cómputo / 1,000,000) lamports.
  • Límite de unidades de cómputo: se establece a partir de la simulación antes de seleccionar un precio.
  • Precio por unidad de cómputo: la puja que adjuntas mediante el programa ComputeBudget.

getRecentPrioritizationFees como una muestra de tarifas pagadas, no una puja sugerida

El método getRecentPrioritizationFees devuelve un arreglo de objetos con forma { slot, prioritizationFee } extraídos de una ventana reciente de bloques. El contrato autorizado es la documentación JSON-RPC de Solana en https://solana.com/docs/rpc/http/getrecentprioritizationfees. De manera crítica, cada entrada reporta la tarifa de priorización pagada por transacciones en ese slot, no un precio recomendado. El método es descriptivo, no prescriptivo.

Debido a que la respuesta es una muestra, el modelo mental correcto es una distribución. La media es un resumen pobre porque un solo slot ocupado puede elevarla, y las muestras no están ponderadas entre slots. Ordenar el arreglo y tomar un percentil alto da un valor que una fracción significativa de slots recientes alcanzó o superó, lo cual es una base más estable para una puja.

El argumento opcional addresses cambia el significado de la muestra. Cuando pasas las cuentas de escritura de la transacción, el método acota la muestra a los slots donde al menos una de esas cuentas fue escrita. Esa es la diferencia entre una distribución relevante para tu contención y un conjunto de tarifas no relacionadas.

  • Forma de la respuesta: arreglo de { slot, prioritizationFee }.
  • Semántica: tarifas pagadas en esos slots, no un precio sugerido.
  • Agregación: ordena y toma un percentil, no la media.
  • Acotación: pasa cuentas de escritura para que la muestra sea relevante.

Acotar la muestra con el argumento addresses

El argumento addresses es la parte menos utilizada del método. Sin él, la muestra agrupa tarifas de transacciones que tocan cuentas no relacionadas. En una cadena tranquila, ese conjunto puede legítimamente tener una mediana de cero, que es el síntoma visible detrás de los hilos recurrentes en Stack Exchange que preguntan por qué getRecentPrioritizationFees siempre devuelve 0. El método no está roto; la muestra simplemente no está acotada a nada que te importe.

Pasa las cuentas de escritura que tu transacción escribirá. La documentación de tarifas de Solana define una cuenta de escritura para este filtro, y la documentación del método confirma el comportamiento de acotación. Las cuentas de solo lectura no califican, por lo que una transacción que solo lee un programa popular no reducirá la muestra a través de la dirección de ese programa.

La acotación también hace que la estimación sea procesable. Una distribución de tarifas para las cuentas sobre las que compites te dice lo que otros escritores de esas cuentas pagaron recientemente. Esa es la población con la que tu transacción compite por inclusión.

  • Pasa cuentas de escritura, no cuentas de solo lectura.
  • Las muestras no acotadas pueden legítimamente ser cero en una cadena tranquila.
  • Las muestras acotadas reflejan la contención en las cuentas que escribes.
  • Si tu transacción no escribe nada, la acotación tiene un efecto limitado.

Leer una distribución: percentiles, ventanas y slots no ponderados

Una vez que tienes una muestra acotada, ordena los valores de prioritizationFee de forma ascendente e indexa un percentil. Un percentil alto, como el 75 o el 90, es un punto de partida común porque refleja un nivel que una gran fracción de slots recientes alcanzó. El percentil exacto es una decisión de política que ajustas contra la tasa de aterrizaje, no una constante universal.

Las muestras no están ponderadas entre slots, lo cual es una ventaja para las lecturas de percentiles. Un solo slot ocupado contribuye con una observación, por lo que no puede dominar el percentil como puede dominar una media. Esto hace que la selección de percentiles sea más robusta frente a valores atípicos.

La ventana reciente es una limitación. Si la ventana cubre un período poco representativo, como una calma antes de un evento conocido, el percentil subestimará la tarifa necesaria durante el evento. Trata la ventana como una estimación de historia reciente, no como un pronóstico.

  • Ordena de forma ascendente y luego indexa el percentil.
  • Los slots no ponderados limitan la influencia de cualquier slot ocupado individual.
  • La ventana reciente puede no representar un pico de congestión próximo.
  • La elección del percentil es una política ajustable, no una constante fija.

Un estimador ejecutable en Node.js usando @solana/web3.js

El estimador a continuación construye una transacción, la simula para obtener el límite de unidades de cómputo, llama a getRecentPrioritizationFees con las direcciones de escritura, toma un percentil configurable, convierte un presupuesto en lamports a un precio por unidad de cómputo y adjunta las instrucciones de ComputeBudget. Usa @solana/web3.js y asume una conexión a un endpoint RPC de Solana. Reemplaza las cuentas de marcador de posición y el endpoint con los tuyos.

La conversión de un presupuesto en lamports a un precio por unidad de cómputo invierte la fórmula de la tarifa: precio = ceil(presupuestoLamports x 1,000,000 / límiteUnidadesDeCómputo). Esto mantiene la tarifa de priorización en o por debajo de tu presupuesto para el límite elegido. Si prefieres derivar el presupuesto de la muestra de percentiles, usa directamente el prioritizationFee muestreado como presupuesto.

Ejecuta esto contra tu endpoint y registra las salidas. La siguiente sección convierte esas salidas en una medición de tasa de aterrizaje.

import {
  Connection,
  PublicKey,
  TransactionMessage,
  VersionedTransaction,
  ComputeBudgetProgram,
} from '@solana/web3.js';

const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
const connection = new Connection(RPC_URL, 'confirmed');

// Replace with the writable accounts your transaction contends on.
const writableAccounts = [
  new PublicKey('11111111111111111111111111111111'),
];

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

async function estimateComputeUnitPrice({ percentileTarget = 75, budgetLamports = null }) {
  const samples = await connection.getRecentPrioritizationFees({
    lockedWritableAccounts: writableAccounts,
  });

  const fees = samples
    .map((s) => s.prioritizationFee)
    .filter((f) => Number.isFinite(f))
    .sort((a, b) => a - b);

  const sampledFee = percentile(fees, percentileTarget);
  const budget = budgetLamports ?? sampledFee;

  return { samples, fees, sampledFee, budget };
}

async function buildWithComputeBudget({ instructions, payer, percentileTarget = 75 }) {
  const { budget } = await estimateComputeUnitPrice({ percentileTarget });

  const { blockhash } = await connection.getLatestBlockhash('confirmed');
  const message = new TransactionMessage({
    payerKey: payer,
    recentBlockhash: blockhash,
    instructions,
  }).compileToV0Message();

  const probe = new VersionedTransaction(message);
  const sim = await connection.simulateTransaction(probe, { replaceRecentBlockhash: true });
  const unitsConsumed = sim.value.unitsConsumed ?? 200_000;
  const computeUnitLimit = Math.ceil(unitsConsumed * 1.2);

  const computeUnitPrice = Math.ceil((budget * 1_000_000) / computeUnitLimit);

  const withBudget = new TransactionMessage({
    payerKey: payer,
    recentBlockhash: blockhash,
    instructions: [
      ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }),
      ComputeBudgetProgram.setComputeUnitPrice({ microLamports: computeUnitPrice }),
      ...instructions,
    ],
  }).compileToV0Message();

  return { tx: new VersionedTransaction(withBudget), computeUnitLimit, computeUnitPrice, budget };
}

export { estimateComputeUnitPrice, buildWithComputeBudget, percentile };

Cerrar el ciclo: medir la tasa de aterrizaje y ajustar con histéresis

Elegir un precio es solo la mitad de la tarea. La otra mitad es verificar que el precio hace aterrizar las transacciones a una tasa aceptable. Registra slot-submitted y slot-confirmed para cada transacción, luego calcula blocks-to-confirmation como la diferencia. Agrega en una ventana para obtener una tasa de aterrizaje y una mediana de blocks-to-confirmation.

Ajusta el percentil contra un objetivo. Si la tasa de aterrizaje está por debajo del objetivo, sube el percentil; si está por encima del objetivo y estás pagando de más, bájalo. Agrega histéresis para que el precio no oscile alrededor del objetivo: exige que la tasa de aterrizaje se desvíe por un margen antes de cambiar el percentil, y limita el cambio por ajuste.

Este ciclo cerrado convierte una estimación estática en un sistema de control. El percentil se convierte en la variable de control, la tasa de aterrizaje en la salida medida y la histéresis en el amortiguamiento. Sin él, estás adivinando un número y nunca aprendes si fue correcto.

  • Registra slot-submitted y slot-confirmed por transacción.
  • Calcula blocks-to-confirmation como la diferencia de slots.
  • Sube el percentil cuando la tasa de aterrizaje esté por debajo del objetivo.
  • Aplica histéresis para evitar oscilaciones alrededor del objetivo.

Tabla de resultados para completar con tu propio endpoint

La tabla a continuación es una plantilla. Complétala con tu propio endpoint RPC y carga de trabajo; no trates ninguna fila como un punto de referencia. El objetivo es ver cómo la elección del percentil se asigna al precio por unidad de cómputo, la tarifa pagada y los bloques hasta el aterrizaje para tus cuentas y tráfico específicos.

Ejecuta cada percentil sobre un número fijo de transacciones, registra la mediana y la dispersión, y compara. El endpoint que uses importa: un nodo dedicado puede devolver una muestra diferente que un endpoint público compartido. Si necesitas un muestreo consistente, revisa precios de RPC y considera un endpoint dedicado a través del servicio API.

  • Percentil: el percentil objetivo usado para la puja.
  • Precio por unidad de cómputo: el valor en microLamports adjunto.
  • Tarifa pagada: la tarifa de priorización en lamports, de la transacción confirmada.
  • Bloques hasta el aterrizaje: slot-confirmed menos slot-submitted.
  • Tasa de aterrizaje: fracción de transacciones enviadas confirmadas dentro de tu ventana.

Solución de problemas: muestras vacías, métodos no soportados y sobrepago

Un arreglo de muestras vacío o todo ceros es la queja más común. Si el arreglo está vacío, la ventana reciente puede no tener slots calificados para tus cuentas acotadas, o tu endpoint puede no soportar el argumento addresses. Si el arreglo es todo ceros, es probable que la muestra no esté acotada o que la cadena esté tranquila para esas cuentas. Vuelve a verificar que pasaste cuentas de escritura y que tu endpoint respeta el parámetro.

Si el endpoint no expone el método, la respuesta es un objeto de error JSON-RPC. La Especificación JSON-RPC 2.0 en https://jsonrpc.org/specification define el sobre de error: un código, un mensaje y datos opcionales. Decodifica ese objeto en lugar de asumir una falla de red. Un código de método no encontrado indica que el endpoint no implementa getRecentPrioritizationFees.

El sobrepago generalmente se remonta a un límite de unidades de cómputo establecido muy por encima del uso real. Debido a que el límite está en el numerador de la fórmula de la tarifa, un límite inflado infla la tarifa al mismo precio. Vuelve a simular y ajusta el límite. También recuerda que una falla de preflight significa que la transacción nunca se ejecuta, por lo que no se paga ninguna tarifa de priorización; una tarifa solo se paga en la ejecución. Para la semántica de preflight y reintentos, consulta Preflight de sendTransaction en Solana y reintentos seguros y Tiempos de espera y estrategia de reintentos en RPC de Solana.

  • Arreglo vacío: no hay slots calificados o el argumento addresses no es soportado.
  • Arreglo todo ceros: muestra no acotada o cuentas tranquilas.
  • Objeto de error: decodifica código y mensaje según JSON-RPC 2.0.
  • Sobrepago: ajusta el límite de unidades de cómputo a partir de la simulación.
  • Falla de preflight: sin ejecución, sin tarifa de priorización.

Limitaciones y compensaciones de la estimación basada en percentiles

La estimación basada en percentiles agrega llamadas RPC por transacción. Cada estimación requiere una llamada a getRecentPrioritizationFees, y construir la transacción puede requerir una simulación y una obtención de blockhash. A escala, estas llamadas agregan latencia y carga. Agrupa o almacena en caché donde tu carga de trabajo lo permita, y ten en cuenta que almacenar en caché un percentil obsoleto puede subvalorar durante un pico.

Un percentil no puede predecir un pico de congestión. Resume la historia reciente, y la ventana reciente puede no representar los próximos segundos. Si tu aplicación tiene transacciones sensibles a la latencia, considera un percentil más alto o un techo de presupuesto en lugar de depender solo de la muestra.

Pagar de más a escala es un costo real. Cada lamport de tarifa de priorización multiplicado por muchas transacciones se acumula. La compensación está entre la tasa de aterrizaje y el costo; no hay un percentil único correcto. Ajústalo contra tus propias mediciones y revísalo a medida que cambian las condiciones de la red.

  • Llamadas RPC adicionales por transacción agregan latencia y carga.
  • Un percentil resume la historia, no la congestión futura.
  • Pagar de más se acumula a través del volumen de transacciones.
  • Ajusta el percentil contra la tasa de aterrizaje medida.

Próximos pasos: conciliación de tarifas, reintentos y niveles de compromiso

Después de que una transacción aterriza, concilia la tarifa que pagaste contra el contenido del bloque. Recompensas de getBlock vs tarifas de transacción en Solana explica cómo la tarifa de priorización entra en un bloque producido, lo que cierra el ciclo entre tu puja y la tarifa observada. Combínalo con Niveles de compromiso y confirmación en Solana para elegir el compromiso en el que consideras final una transacción.

Para el comportamiento de envío y reintento después de elegir la tarifa, revisa Tiempos de espera y estrategia de reintentos en RPC de Solana y Preflight de sendTransaction en Solana y reintentos seguros. Si la simulación es parte de tu paso de establecimiento de límites, Decodificación de errores de simulateTransaction en Solana ayuda a interpretar fallas.

Para la selección de endpoint y el contexto de red, consulta la página de la red Solana y la guía de la API RPC de Solana (RPC Assistant). El centro de aprendizaje de OnFinality reúne los artículos adyacentes de esta serie.

  • Concilia las tarifas pagadas contra el contenido del bloque.
  • Elige un nivel de compromiso para la finalidad.
  • Maneja los reintentos después de elegir la tarifa.
  • Selecciona un endpoint que soporte el método y el argumento addresses.

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