En Monad, el protocolo reserva el límite de gas del remitente de una transacción contra su saldo antes de la ejecución para que una transacción no pueda fallar a mitad por fondos insuficientes. Como resultado, el valor devuelto por eth_getBalance es el saldo total de la cuenta, no el importe inmediatamente gastable: mientras las transacciones están pendientes o en vuelo, parte de ese saldo está reservado y no disponible. Por lo tanto, una comprobación ingenua de saldo >= valor discrepa de la cadena y puede producir errores espurios de 'fondos insuficientes' o un saldo en la interfaz inferior al que el usuario espera. Monad expone una superficie RPC adicional de saldo de reserva a las carteras para que el importe reservado pueda leerse explícitamente en lugar de inferirse. Este artículo explica el mecanismo, muestra una comprobación ejecutable en Node.js que calcula el MON gastable con un respaldo para endpoints que no exponen el método de reserva, y proporciona una tabla de resultados para medir contra tu propio endpoint.
El problema de ejecución paralela que resuelve el saldo de reserva
Monad ejecuta transacciones en paralelo, lo que significa que el resultado de una transacción debe ser conocido antes de que se ejecute. Si una transacción pudiera descubrir a mitad de la ejecución que el remitente ya no tiene saldo suficiente para pagar el gas, la cadena tendría que deshacer trabajo del que otras transacciones ya podrían depender. El mecanismo de saldo de reserva de Monad evita eso al cargar el límite de gas del remitente contra un saldo reservado antes de que comience la ejecución, garantizando que los fondos estén disponibles. La documentación de Monad sobre el saldo de reserva describe esto como la garantía a nivel de protocolo de que una transacción no puede fallar por fondos insuficientes a mitad de camino.
Esta es una decisión de diseño deliberada, no una peculiaridad contable. En una cadena secuencial, una transacción que se queda sin gas simplemente revierte y el remitente conserva la parte no gastada. En una cadena de ejecución paralela, el protocolo debe comprometer el coste máximo posible por adelantado para que las transacciones concurrentes no compitan por los mismos fondos. La reserva es el mecanismo que hace explícito ese compromiso.
Para los desarrolladores de backend y los integradores de carteras, la consecuencia práctica es que la noción de 'saldo disponible' de la cadena es más estrecha que el saldo total. El total es lo que devuelve eth_getBalance; el importe gastable es el total menos lo que esté reservado en ese momento. Entender esa distinción es la diferencia entre una comprobación de saldo que concuerda con Monad y una que discrepa silenciosamente.
El mecanismo está documentado por la propia Monad en la documentación de saldo de reserva y en la guía de integración de carteras, mientras que el método estándar que extiende está definido por la especificación JSON-RPC de Ethereum para eth_getBalance. Lee la página de Monad para la semántica de reserva y la especificación para el contrato base, porque solo la primera explica por qué ambos coinciden en el número bruto pero difieren en el importe gastable.
- El saldo de reserva es una garantía del protocolo: el límite de gas del remitente se compromete antes de la ejecución.
- Existe porque la ejecución paralela no puede tolerar un fallo de financiación a mitad de transacción.
- El importe reservado se retiene contra transacciones pendientes y en vuelo y se libera a medida que se liquidan.
Por qué eth_getBalance es una respuesta incompleta para el saldo gastable
La especificación JSON-RPC de Ethereum define eth_getBalance como el retorno del saldo de la cuenta en wei. Monad implementa ese contrato, por lo que el método devuelve el saldo total de la cuenta. Lo que la especificación no define es una cifra separada de 'gastable', porque en una cadena secuencial ambas son efectivamente lo mismo en el momento de la llamada. En Monad pueden diferir siempre que haya transacciones en vuelo o la cuenta mantenga una reserva.
Por lo tanto, una comprobación ingenua como saldo >= valor pasa cuando el saldo total de la cuenta es suficiente pero su saldo gastable no lo es. La transacción se rechaza entonces, o la cartera muestra un error que el usuario no puede reconciliar con el saldo mostrado en la interfaz. La Guía de integración para desarrolladores de carteras de la documentación de Monad trata esto como una preocupación de integración de primer nivel: se espera que las carteras lean la superficie de reserva en lugar de inferir el saldo gastable solo a partir de eth_getBalance.
La brecha no es un error en eth_getBalance. Es el método estándar respondiendo a una pregunta diferente de la que hace el cliente. La pregunta estándar es 'cuánto tiene esta cuenta'; la pregunta del cliente es 'cuánto puede gastar esta cuenta ahora mismo'. En Monad esas son distintas, y la superficie de reserva es cómo se responde a la segunda pregunta.
- eth_getBalance devuelve el saldo total, según la especificación JSON-RPC de Ethereum.
- El saldo gastable es igual al saldo total menos el importe reservado en ese momento.
- Ambos divergen cuando hay transacciones pendientes o en vuelo, o cuando se mantiene una reserva.
Contabilidad del saldo de reserva a lo largo del ciclo de vida de la transacción
El saldo reservado se retiene contra transacciones pendientes y en vuelo y se libera a medida que se liquidan. Cuando se envía una transacción, el protocolo reserva el límite de gas del remitente; cuando la transacción se liquida, la parte no utilizada de esa reserva se libera de nuevo al saldo gastable. Esto significa que el saldo gastable es una cantidad en movimiento que solo se recupera después de la liquidación, no en el momento del envío.
La interacción con el ciclo de vida importa para la lógica de sondeo. Un cliente que lee eth_getBalance inmediatamente después de enviar una transacción verá el saldo total sin cambios, porque la reserva no reduce el total. Un cliente que lee la superficie de reserva verá aumentar el importe reservado, y caer el saldo gastable, hasta que la transacción se liquide. La guía Ciclo de vida de transacciones en Monad y recibos de ejecución asíncrona explica por qué el momento de la liquidación no siempre es inmediato, y eth_getTransactionReceipt devuelve null y sondeo de recibos cubre el patrón de sondeo que confirma cuándo se ha producido la liberación.
Para un backend que envía varias transacciones desde una misma cuenta, la contabilidad se acumula: cada transacción en vuelo retiene su propia reserva de límite de gas, por lo que el saldo gastable se reduce por la suma de todas las reservas pendientes. Este es el mecanismo detrás del síntoma visible en el que una interfaz muestra un saldo inferior al que el usuario espera: parte del saldo está reservado contra los límites de gas de las transacciones en vuelo.
- La reserva se produce en el envío; la liberación se produce en la liquidación.
- El saldo total no cambia por la reserva; el saldo gastable se reduce.
- Varias transacciones en vuelo reservan acumulativamente contra la misma cuenta.
La superficie RPC para leer la reserva explícitamente
En lugar de inferir la reserva a partir de la diferencia entre dos lecturas de saldo, una cartera o un backend debería leer la superficie de reserva directamente. La Descripción general de JSON-RPC de la documentación de Monad documenta una superficie RPC adicional de saldo de reserva expuesta a las carteras junto con los métodos estándar. El nombre exacto del método y la forma de la respuesta están documentados por Monad; trata el método como específico de Monad y verifícalo contra la documentación actual para tu red objetivo antes de confiar en él en producción.
Las llamadas estándar siguen siendo necesarias. eth_getBalance proporciona el saldo total, y eth_getTransactionCount proporciona el nonce utilizado para la construcción y el reemplazo de transacciones. La superficie de reserva añade la tercera entrada que falta: el importe reservado actualmente. El saldo gastable es entonces el total menos lo reservado. La guía Gestión de nonce en EVM con eth_getTransactionCount cubre el lado del nonce de ese trío, lo cual importa porque un nonce atascado puede mantener una reserva pendiente más tiempo del esperado.
Que el método de reserva esté disponible depende del endpoint. Algunos proveedores lo exponen; otros no. Esta es una distinción documentada / varía según el proveedor: el mecanismo es comportamiento del protocolo Monad, pero la disponibilidad de la superficie RPC es una propiedad del endpoint. Un cliente robusto debería sondear el método y recurrir a la inferencia cuando esté ausente, y debería tratar la inferencia como una aproximación en lugar de una cifra autoritativa.
- Lee el saldo total con eth_getBalance y el nonce con eth_getTransactionCount.
- Lee el importe reservado desde la superficie RPC de saldo de reserva de Monad cuando esté disponible.
- Calcula gastable = total - reservado; recurre a la inferencia solo cuando el método esté ausente.
Límites de gas, eth_estimateGas y saldo gastable en paralelo
Como se reserva todo el límite de gas, un límite sobreestimado inmoviliza saldo gastable hasta que la transacción se liquida. Si una transacción consumiría realmente 40.000 de gas pero el cliente establece un límite de 200.000, el protocolo reserva contra 200.000 durante ese tiempo. La diferencia no se pierde —se libera en la liquidación—, pero no está disponible mientras tanto, lo que reduce cuánto puede gastar la cuenta en paralelo.
Esto conecta eth_estimateGas directamente con el saldo gastable. Una estimación ajustada y precisa minimiza el importe reservado y maximiza el saldo disponible para transacciones concurrentes. Una estimación con margen es más segura contra reversiones por falta de gas pero cuesta margen gastable. El compromiso es real y debe tomarse deliberadamente: para una cartera que envía una transacción a la vez, el margen es barato; para un backend que envía muchas transacciones desde una cuenta, el margen se acumula en cada transacción en vuelo.
La guía Estimación del precio del gas con eth_feeHistory cubre el lado de la tarifa en la construcción de transacciones. El lado del límite de gas es donde vive la interacción con la reserva, y merece la pena medirlo: envía una transacción con una sobreestimación conocida y observa cómo se comporta el saldo gastable hasta la liquidación.
- Se reserva todo el límite de gas, no el gas realmente consumido.
- Los límites sobreestimados reducen el saldo gastable en paralelo hasta la liquidación.
- Las estimaciones más ajustadas aumentan el margen gastable pero elevan el riesgo de quedarse sin gas.
Una comprobación ejecutable de saldo gastable en Node.js
El siguiente script lee el saldo total, intenta leer la superficie de reserva e informa del importe gastable. Utiliza solo la API fetch estándar y un cuerpo POST JSON-RPC, por lo que se ejecuta en Node.js 18 o posterior sin dependencias. Reemplaza la URL del endpoint por tu propio endpoint RPC de Monad; la página Endpoints RPC de Monad (RPC Assistant) enumera opciones, y Monad mainnet cubre la red en sí.
El script sondea el método de reserva llamándolo y comprobando si hay un error JSON-RPC. Si el método no está implementado, el endpoint devuelve un objeto de error y el script recurre a informar del saldo total con una nota explícita de que no se pudo determinar el saldo gastable. Este es el comportamiento correcto: nunca informes silenciosamente del saldo total como gastable cuando la superficie de reserva no está disponible.
// spendable-balance.js — Node.js 18+
// Usage: node spendable-balance.js <rpcUrl> <address>
const rpcUrl = process.argv[2];
const address = process.argv[3];
if (!rpcUrl || !address) {
console.error('Usage: node spendable-balance.js <rpcUrl> <address>');
process.exit(1);
}
async function rpc(method, params) {
const res = await fetch(rpcUrl, {
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) {
const err = new Error(json.error.message || 'rpc error');
err.code = json.error.code;
throw err;
}
return json.result;
}
function toMon(weiHex) {
return Number(BigInt(weiHex)) / 1e18;
}
(async () => {
const totalHex = await rpc('eth_getBalance', [address, 'latest']);
const total = toMon(totalHex);
console.log('total balance (MON):', total);
// Probe the Monad reserve-balance surface.
// Method name and params are documented by Monad; verify against current docs.
let reserved = null;
try {
const reservedHex = await rpc('eth_getReserveBalance', [address, 'latest']);
reserved = toMon(reservedHex);
console.log('reserved (MON):', reserved);
console.log('spendable (MON):', total - reserved);
} catch (e) {
console.warn('reserve surface unavailable on this endpoint:', e.message);
console.warn('spendable balance cannot be determined; total shown above.');
}
})();Una sonda curl para la disponibilidad de la superficie de reserva del endpoint
Antes de integrar la superficie de reserva en un cliente de producción, confirma que tu endpoint la implementa. El siguiente comando curl envía una única solicitud JSON-RPC e imprime la respuesta sin procesar. Una respuesta correcta contiene un campo result con una cantidad hexadecimal; un endpoint que no implementa el método devuelve un objeto de error con un código de método no encontrado. Ejecútalo contra cada endpoint que pretendas usar, porque la disponibilidad es una propiedad documentada / varía según el proveedor.
El mismo patrón de sonda se aplica a cualquier método específico de Monad. Mantén la sonda en tu lista de verificación de despliegue para que un cambio de proveedor no degrade silenciosamente tu cálculo de saldo gastable a una inferencia.
curl -s -X POST "$MONAD_RPC_URL" \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getReserveBalance",
"params": ["0xYourAddressHere", "latest"]
}'Tabla de resultados: medir el comportamiento de la reserva en tu endpoint
La siguiente tabla es una plantilla para medir tu propio endpoint. Rellénala ejecutando el script de Node.js y la sonda curl contra cada endpoint que uses, y luego repitiendo las lecturas de saldo mientras hay una transacción pendiente. El objetivo es establecer, para tu infraestructura, si la superficie de reserva está presente y cómo se comporta el saldo gastable bajo carga. No te fíes de las cifras de este artículo; mide contra tu propio endpoint.
Registra la etiqueta del endpoint, si eth_getBalance está presente, si la superficie de reserva está presente, el saldo total, el importe reservado y el importe gastable bajo una transacción pendiente. Una fila en la que la superficie de reserva está ausente no es un fallo del endpoint: significa que tu cliente debe recurrir a la inferencia y debería indicarlo en su interfaz o registros.
- Etiqueta del endpoint: un nombre que reconozcas más tarde.
- eth_getBalance presente: sí/no.
- Superficie de reserva presente: sí/no (de la sonda curl).
- Saldo total (MON): de eth_getBalance.
- Reservado (MON): de la superficie de reserva, o 'n/a'.
- Gastable bajo transacción pendiente (MON): total menos reservado, o 'indeterminado'.
Modos de fallo y solución de problemas
El síntoma más común es que una cartera o un backend informen de 'fondos insuficientes' cuando eth_getBalance parece suficiente. Esto ocurre cuando el cliente compara el saldo total contra el valor sin restar la reserva. La solución es calcular el saldo gastable y comparar contra él. Si la superficie de reserva no está disponible, el cliente debería mostrar la incertidumbre en lugar de afirmar suficiencia.
Un segundo síntoma es un saldo gastable que solo se recupera después de la liquidación. Este es el comportamiento esperado, no un estado atascado: la reserva se libera cuando la transacción se liquida. Si la recuperación parece lenta, comprueba si la transacción se está liquidando realmente sondeando el recibo, como se describe en eth_getTransactionReceipt devuelve null y sondeo de recibos pendientes. Una transacción que nunca se liquida —por ejemplo, una atascada detrás de un hueco de nonce— mantendrá su reserva indefinidamente, por lo que la gestión de nonces importa.
Un tercer síntoma es un endpoint que no implementa el método de reserva, lo que obliga a la inferencia. Inferir significa leer el saldo total y restar una estimación de la reserva derivada de tus propias transacciones pendientes. Esa estimación es tan buena como tu visión del mempool, que no es autoritativa. Trata el saldo gastable inferido como un límite inferior y prefiere endpoints que expongan la superficie de reserva para cualquier flujo de trabajo donde la distinción importe.
- Falsos 'fondos insuficientes': compara contra el gastable, no contra el total.
- Recuperación lenta: confirma la liquidación mediante sondeo de recibos antes de asumir un fallo.
- Método de reserva ausente: recurre a la inferencia y etiqueta el resultado como aproximado.
Limitaciones y compromisos del modelo de reserva
La reserva es una garantía del protocolo con un coste de experiencia de usuario. Hace segura la ejecución paralela, pero significa que el saldo que ve un usuario y el saldo que puede gastar no son el mismo número mientras hay transacciones en vuelo. Las carteras que ignoran la distinción discreparán del propio comportamiento de cartera de Monad, y los usuarios lo notarán.
La superficie de reserva es específica de Monad y no es portátil a otras cadenas EVM. El código escrito contra ella no se ejecutará sin cambios en cadenas que carecen del mecanismo, y el código escrito para esas cadenas subestimará el saldo gastable en Monad. Si mantienes un cliente multicadena, aísla la lógica de reserva detrás de una comprobación de capacidad en lugar de asumirla en todas partes.
Por último, la disponibilidad de la superficie de reserva varía según el endpoint. El mecanismo es comportamiento documentado de Monad; la superficie RPC es una propiedad del endpoint. Diseña tu cliente para que un método de reserva ausente se degrade con elegancia, y para que un cambio de proveedor no convierta silenciosamente una cifra autoritativa de saldo gastable en una inferencia. Para cargas de trabajo en producción, revisa las opciones de precios de RPC y servicio de API con la disponibilidad de la superficie de reserva como requisito explícito.
- La reserva mejora la seguridad de ejecución a costa de un modelo de saldo más complejo.
- La superficie de reserva es específica de Monad y no es portátil entre cadenas EVM.
- La disponibilidad del endpoint varía; degrada con elegancia y etiqueta los valores inferidos.
Próximos pasos para integrar el saldo gastable
Empieza sondeando tus endpoints para la superficie de reserva con el comando curl anterior, y registra los resultados en la tabla. Luego integra la comprobación de Node.js en tus rutas de visualización de saldo y validación previa, reemplazando cualquier comparación saldo >= valor por una comparación gastable >= valor. Donde la superficie de reserva esté ausente, registra el respaldo para que puedas ver con qué frecuencia se usa la inferencia.
A continuación, ajusta tu estrategia de límite de gas. Revisa dónde se llama a eth_estimateGas y si el margen está justificado para tu patrón de envío. Para cuentas que envían de forma concurrente, mide cuánto margen gastable consume el padding y ajústalo. La guía Tiempos de espera de RPC en Monad y patrones de reintento fiables es útil aquí, porque los reintentos pueden crear transacciones en vuelo adicionales y, por tanto, reservas adicionales.
Por último, ten presente el mecanismo al depurar discrepancias de saldo. El centro de aprendizaje de OnFinality recopila guías relacionadas sobre el comportamiento de RPC de Monad, y la página Endpoints RPC de Monad (RPC Assistant) es el punto de partida para la selección de endpoints. Trata el saldo gastable como una cantidad de primer nivel en tu cliente, no como una ocurrencia tardía derivada.
- Sondea los endpoints y registra la disponibilidad de la superficie de reserva.
- Reemplaza saldo >= valor por gastable >= valor en las rutas de validación.
- Revisa el padding del límite de gas y el comportamiento de reintento por su efecto en las reservas.