개발자를 위한 문서 / JSON-RPC
트랜잭션 보내기
보내는 것은 평범합니다. 결과를 읽는 것은 그렇지 않습니다. 블록보다 먼저 영수증이 존재하고, revert는 라이브러리가 디코딩할 수 없는 텍스트로 도착합니다.
참일 수 있는 세 가지
대부분의 체인에서 "보냈다", "채굴됐다", "최종이다"는 한 번의 기다림으로 뭉쳐집니다. 여기서 그것은 세 개의 별개 사건이며, 지금 무엇을 검사하고 있는지 아는 것이 올바른 클라이언트를 쓰는 일의 대부분입니다.
- 실행됨.
eth_sendRawTransaction은 트랜잭션이 실제로 돌 때까지 블로킹하므로, 성공적으로 돌아왔다는 것은 이미 대기열에 들어갔다는 뜻이 아니라 실행되었다는 뜻입니다. revert는 이 시점에 알 수 있습니다. - 사전 확인됨. 미니블록 스케줄링 목표 안에 영수증이 존재합니다. 그
blockNumber와blockHash는 아직null입니다. - 확인됨. EVM 블록 스케줄링 목표에서 블록이 봉인되고 그 두 필드가 채워집니다.
블록 번호를 기다리는 것은 엉뚱한 것을 기다리는 것입니다
receipt.blockNumber가 null이 아닐 때까지 폴링하는 라이브러리는, 원하는 답 - 이것이 성공했는가 - 이 사전 확인 단계에서 이미 나와 있는데도 확인 단계를 기다리고 있는 것입니다. 버그는 아니지만 필요보다 오래 기다린다는 뜻입니다. 영수증이 존재하는 즉시 status를 읽으십시오.
최종성은 네 번째이자 별개의 것입니다. 해당 배치가 Ethereum에서 정산될 때 일어나며, 이 필드들로는 전혀 관측되지 않습니다. 블록 태그도 도움이 되지 않습니다 - safe와 finalized는 둘 다 latest로 해석됩니다.
보내기
로컬에서 서명하고 raw로 보내십시오. 노드는 키를 하나도 갖고 있지 않으므로 eth_sendTransaction과 모든 서명 메서드는 설계상 -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,
});또는 라이브러리 없이 순수한 EIP-1193 프로바이더를 상대로도 됩니다. 어느 쪽이 서명하는지에 주의하십시오. 여기서 eth_sendTransaction은 지갑이 처리하며, 지갑이 서명한 뒤 raw 트랜잭션을 대신 제출합니다 - 노드 자신은 그 메서드를 거절합니다.
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" }],
});영수증 읽기
여기서 영수증이 없다는 것은 인내의 문제가 아니라 문제 그 자체입니다
eth_getTransactionReceipt가 null을 준다는 것은 노드에 그 해시에 대한 영수증이 없다는 뜻입니다. 이 체인에서는 보통 미니블록 스케줄링 목표 안에 영수증이 존재하므로, 1초나 2초 뒤에도 null이라면 계속 기다릴 것이 아니라 조사해 볼 신호입니다.
이것은 설계상 모호하기도 합니다. 보관 창에서 축출된 영수증은 존재한 적이 없는 것과 똑같이 null로 답합니다. 긴 폴링 백오프가 여기서 잘못된 모양인 이유가 그것입니다 - 시도 사이를 너무 길게 두면 영수증이 그 둘 사이에서 사라질 수 있습니다.
// 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.viem에서는 waitForTransactionReceipt가 이 일을 대신해 줍니다 - 다만 pollingInterval을 낮게 두고, 오래 기다리는 편이 안전하다는 가정으로 타임아웃을 올리지 마십시오. 여기서는 그렇지 않습니다.
그 영수증의 두 필드는 다른 곳에서와 뜻이 다릅니다. cumulativeGasUsed는 블록의 누계가 아니라 이 트랜잭션 자신의 가스이고, 각 로그의 logIndex는 블록이 아니라 트랜잭션마다 매겨집니다 - 그래서 (blockNumber, logIndex)는 유일한 키가 아닙니다. 이벤트를 저장한다면 트랜잭션 해시를 포함하십시오.
revert할 때
라이브러리는 사유를 디코딩하지 못합니다
revert는 메시지 텍스트로 돌아오고 - execution reverted: 0x… - error.data는 결코 채워지지 않습니다. 모든 라이브러리는 커스텀 오류와 revert 문자열을 error.data에서 디코딩하므로, 이 체인에서는 어느 것도 하지 못합니다. viem과 ethers 모두 이름 붙은 오류 대신 원본 메시지를 내놓습니다.
반환 데이터는 메시지 안에 있으므로 직접 디코딩할 수 있습니다.
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);
}
}알아 둘 만한 전송 계층 차이가 하나 더 있습니다. 같은 revert가 HTTP에서는 -32000, WebSocket에서는 -32603입니다. 소켓이 전송이 아닌 모든 오류를 다시 감싸기 때문입니다. 코드로 분기하는 클라이언트는 자신이 어느 전송 계층 위에 있는지 알아야 합니다.
가스
gasPrice를 1 wei로 명시하십시오. 추정할 수수료 시장 자체가 없습니다. 기본 수수료는 0이고, eth_gasPrice는 1 wei로 답하며, 1 wei는 체인이 받아들이는 최솟값이기도 합니다.
eth_estimateGas는 동작하고 제대로 시뮬레이션하며 통상의 여유도 붙여 줍니다 - 다만 여러분이 보낸 gas, gasPrice, nonce와 블록 파라미터를 무시하고 항상 최신 상태를 상대로 30,000,000의 블록 한도에서 시뮬레이션합니다. 그리고 eth_feeHistory는 읽지 마십시오. 측정값이 아니라 상수를 돌려줍니다.
128 KiB를 넘는 raw 트랜잭션은 디코딩되기도 전에 크기에서 거부되고, 진입 큐는 보내는 이당 64개의 대기 트랜잭션을 담습니다. 둘 다 -32005로 답하는데, 이는 실수가 아니라 한도라는 뜻입니다 - 실패로 취급하는 대신 백오프하고 재시도하십시오.