Para desarrolladores / JSON-RPC
Enviar una transacción
Enviar es ordinario. Leer el resultado no lo es: un recibo existe antes que el bloque, y un revert llega como texto que su biblioteca no puede decodificar.
Tres cosas que pueden ser ciertas
En la mayoría de las cadenas, "enviada", "minada" y "final" colapsan en una sola espera. Aquí son tres eventos distintos, y saber cuál está comprobando es lo esencial de escribir un cliente correcto:
- Ejecutada.
eth_sendRawTransactionbloquea hasta que la transacción se ha ejecutado de verdad, así que un retorno correcto ya significa ejecutada - no encolada. Un revert se conoce en este punto. - Preconfirmada. Dentro del objetivo de programación del minibloque, existe un recibo. Sus
blockNumberyblockHashsiguen siendonull. - Confirmada. En el objetivo de programación del bloque EVM, el bloque se sella y esos dos campos se rellenan.
Esperar un número de bloque es esperar lo que no toca
Una biblioteca que sondea hasta que receipt.blockNumber no sea nulo espera al nivel de confirmación cuando la respuesta que quiere - si esto ha funcionado - ya estaba disponible en el nivel de preconfirmación. No es un fallo, pero sí significa que usted espera más de lo necesario. Lea status en cuanto exista el recibo.
La finalidad es una cuarta cosa, distinta: ocurre cuando el lote que la contiene se liquida en Ethereum, y no es observable en absoluto a través de estos campos. Las etiquetas de bloque tampoco ayudan - safe y finalized se resuelven ambas a latest.
Enviar
Firme localmente y envíe en bruto. El nodo no guarda ninguna clave, así que eth_sendTransaction y todos los métodos de firma responden -32601 por diseño.
import { createWalletClient, custom } from "viem";
import { pickleChain } from "./pickle";
const wallet = createWalletClient({ chain: pickleChain, transport: custom(window.ethereum) });
const hash = await wallet.sendTransaction({
to: recipient,
value: 10n ** 16n,
// Explicit, because there is no fee market to estimate from: the base fee is
// zero and the minimum accepted price is one wei.
gasPrice: 1n,
});O contra un proveedor EIP-1193 en bruto, sin biblioteca alguna. Fíjese en qué lado firma: aquí eth_sendTransaction lo gestiona la billetera, que firma y luego envía por usted la transacción en bruto - el nodo, en cambio, rechaza ese método.
const [account] = await window.ethereum.request({ method: "eth_requestAccounts" });
const hash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [{ from: account, to: recipient, value: "0x2386f26fc10000", gasPrice: "0x1" }],
});Leer el recibo
Aquí un recibo que falta es un problema, no falta de paciencia
Un null de eth_getTransactionReceipt significa que el nodo no tiene ningún recibo para ese hash - y en esta cadena normalmente existe uno dentro del objetivo de programación del minibloque, así que un null tras uno o dos segundos es una señal para investigar en lugar de seguir esperando.
También es ambiguo por diseño: un recibo expulsado de la ventana responde null exactamente igual que uno que nunca existió. Por eso espaciar mucho los reintentos es la forma equivocada aquí - espere demasiado entre dos intentos y el recibo puede desaparecer entre ellos.
// Poll early and tightly: the receipt exists almost immediately, and a long
// backoff risks the window evicting it between two attempts.
async function receiptFor(hash, tries = 40) {
for (let i = 0; i < tries; i += 1) {
const receipt = await call("eth_getTransactionReceipt", [hash]);
if (receipt) return receipt;
await new Promise((r) => setTimeout(r, 100));
}
throw new Error("no receipt after 4s - check the node, not the transaction");
}
const receipt = await receiptFor(hash);
// "0x1" succeeded, "0x0" reverted. Both are known before the block seals.
if (receipt.status !== "0x1") throw new Error("transaction reverted");
// Only if you actually need the block: blockNumber is null until the EVM seal.Con viem, waitForTransactionReceipt hace esto por usted - pero ponga pollingInterval bajo y no suba su tiempo de espera suponiendo que esperar más es más seguro. Aquí no lo es.
Dos campos de ese recibo no significan lo que significan en otros sitios. cumulativeGasUsed es el gas propio de esta transacción en lugar de un total acumulado del bloque, y el logIndex de cada log se enumera por transacción en lugar de por bloque - así que (blockNumber, logIndex) no es una clave única. Incluya el hash de la transacción si almacena eventos.
Cuando hay revert
Su biblioteca no puede decodificar la razón
Un revert vuelve como texto de mensaje - execution reverted: 0x… - y error.data nunca se rellena. Todas las bibliotecas decodifican los errores personalizados y las cadenas de revert a partir de error.data, así que en esta cadena ninguna puede: tanto viem como ethers mostrarán el mensaje en bruto en lugar de un error con nombre.
Los datos de retorno están en el mensaje, así que puede decodificarlos por su cuenta:
import { decodeErrorResult } from "viem";
try {
await client.simulateContract({ /* … */ });
} catch (err) {
const hex = String(err.message).match(/0x[0-9a-fA-F]+/)?.[0];
if (hex && hex.length > 2) {
// Decode against your own ABI's error definitions.
const decoded = decodeErrorResult({ abi: myAbi, data: hex as `0x${string}` });
console.error(decoded.errorName, decoded.args);
}
}Una última diferencia de transporte que conviene conocer: el mismo revert es -32000 por HTTP y -32603 por el WebSocket, porque el socket reencapsula todo error que no sea un envío. Un cliente que se ramifica según el código necesita saber en qué transporte está.
El gas
Fije gasPrice explícitamente a 1 wei. No hay un mercado de comisiones contra el que estimar: la base fee es cero, eth_gasPrice responde un wei, y un wei es también el mínimo que la cadena aceptará.
eth_estimateGas funciona y simula correctamente, añadiendo el margen habitual - pero ignora los parámetros gas, gasPrice, nonce y de bloque que usted le envía, y simula siempre con el límite de 30 000 000 por bloque contra el estado más reciente. Y no lea eth_feeHistory: devuelve constantes en lugar de mediciones.
Una transacción en bruto de más de 128 KiB se rechaza por su tamaño antes siquiera de decodificarse, y la cola de admisión retiene 64 transacciones pendientes por emisor. Las dos responden -32005, que significa un límite en lugar de un error - espacie los reintentos en lugar de tratarlo como un fallo.