Für Entwickler / JSON-RPC
Eine Transaktion senden
Das Senden ist gewöhnlich. Das Lesen des Ergebnisses ist es nicht: Eine Quittung existiert, bevor der Block existiert, und ein Revert kommt als Text an, den Ihre Bibliothek nicht dekodieren kann.
Drei Dinge, die wahr sein können
Auf den meisten Chains fallen „gesendet", „gemined" und „final" zu einem einzigen Warten zusammen. Hier sind es drei getrennte Ereignisse, und zu wissen, auf welches davon Sie prüfen, macht das meiste daran aus, einen korrekten Client zu schreiben:
- Ausgeführt.
eth_sendRawTransactionblockiert, bis die Transaktion tatsächlich gelaufen ist, eine erfolgreiche Rückgabe bedeutet also bereits ausgeführt - nicht eingereiht. Ein Revert ist an diesem Punkt bekannt. - Vorbestätigt. Innerhalb des Planungsziels des Mini-Blocks existiert eine Quittung. Ihre
blockNumberund ihrblockHashsind nochnull. - Bestätigt. Beim Planungsziel des EVM-Blocks wird der Block versiegelt, und diese beiden Felder füllen sich.
Auf eine Blocknummer zu warten heißt, auf das Falsche zu warten
Eine Bibliothek, die abfragt, bis receipt.blockNumber nicht null ist, wartet auf die Bestätigungsstufe, während die Antwort, die sie will - ist das gelungen - schon auf der Stufe der Vorbestätigung vorlag. Das ist kein Fehler, aber es heißt, dass Sie länger warten als nötig. Lesen Sie status, sobald die Quittung existiert.
Finalität ist ein viertes, getrenntes Ding: Sie tritt ein, wenn der enthaltende Batch auf Ethereum abwickelt, und ist über diese Felder überhaupt nicht beobachtbar. Die Block-Tags helfen auch nicht - safe und finalized lösen beide auf latest auf.
Senden
Signieren Sie lokal und senden Sie roh. Der Node hält keine Schlüssel, deshalb antworten eth_sendTransaction und jede Signiermethode konstruktionsbedingt mit -32601.
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,
});Oder gegen einen rohen EIP-1193-Provider, ganz ohne Bibliothek. Beachten Sie, welche Seite signiert: eth_sendTransaction wird hier von der Wallet behandelt, die signiert und dann die rohe Transaktion für Sie einreicht - der Node selbst verweigert diese Methode.
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" }],
});Die Quittung lesen
Eine fehlende Quittung ist hier ein Problem, keine Geduldsfrage
null von eth_getTransactionReceipt heißt, dass der Node keine Quittung zu diesem Hash hat - und auf dieser Chain existiert normalerweise eine innerhalb des Planungsziels des Mini-Blocks, ein null nach einer oder zwei Sekunden ist also ein Signal zu untersuchen statt weiterzuwarten.
Es ist außerdem konstruktionsbedingt mehrdeutig: Eine Quittung, die aus dem Fenster verdrängt wurde, antwortet null genau wie eine, die nie existiert hat. Deshalb ist ein langer Abfrage-Backoff hier die falsche Form - warten Sie zwischen zwei Versuchen zu lange, und die Quittung kann zwischen ihnen verschwinden.
// 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.Mit viem erledigt waitForTransactionReceipt das für Sie - setzen Sie aber pollingInterval niedrig und erhöhen Sie sein Timeout nicht in der Annahme, länger zu warten sei sicherer. Hier ist es das nicht.
Zwei Felder auf dieser Quittung bedeuten nicht, was sie anderswo bedeuten. cumulativeGasUsed ist das eigene Gas dieser Transaktion statt einer laufenden Summe für den Block, und logIndex auf jedem Log wird pro Transaktion statt pro Block durchnummeriert - (blockNumber, logIndex) ist also kein eindeutiger Schlüssel. Nehmen Sie den Transaktions-Hash dazu, wenn Sie Ereignisse speichern.
Wenn es revertet
Ihre Bibliothek kann den Grund nicht dekodieren
Ein Revert kommt als Nachrichtentext zurück - execution reverted: 0x… -, und error.data wird nie gefüllt. Jede Bibliothek dekodiert Custom Errors und Revert-Strings aus error.data, auf dieser Chain kann es also keine: viem und ethers zeigen beide die rohe Nachricht statt eines benannten Fehlers.
Die Rückgabedaten stehen in der Nachricht, Sie können sie also selbst dekodieren:
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);
}
}Noch ein Transportunterschied, den man kennen sollte: Derselbe Revert ist -32000 über HTTP und -32603 über den WebSocket, weil der Socket jeden Fehler außer beim Senden neu verpackt. Ein Client, der sich anhand des Codes verzweigt, muss wissen, auf welchem Transport er ist.
Gas
Setzen Sie gasPrice ausdrücklich auf 1 Wei. Es gibt keinen Gebührenmarkt, gegen den man schätzen könnte: Die Basisgebühr ist null, eth_gasPrice antwortet mit einem Wei, und ein Wei ist auch das Minimum, das die Chain annimmt.
eth_estimateGas funktioniert und simuliert ordentlich, mit dem üblichen Aufschlag - aber es ignoriert die Parameter gas, gasPrice, nonce und den Block, die Sie ihm mitgeben, und simuliert immer beim Blocklimit von 30.000.000 gegen den jüngsten Zustand. Und lesen Sie eth_feeHistory nicht: Es gibt Konstanten statt Messungen zurück.
Eine rohe Transaktion über 128 KiB wird wegen ihrer Größe abgelehnt, bevor sie überhaupt dekodiert wird, und die Annahmewarteschlange hält 64 ausstehende Transaktionen pro Sender. Beide antworten mit -32005, was eine Grenze bedeutet und keinen Fehler Ihrerseits - weichen Sie zurück und versuchen Sie es erneut, statt es als Fehlschlag zu behandeln.