chaingate
Version:
Multi-chain cryptocurrency SDK for TypeScript — unified API for Bitcoin, Ethereum, Litecoin, Dogecoin, Bitcoin Cash, Polygon, Arbitrum, and any EVM-compatible chain. Create wallets, query balances, send transactions, and manage tokens and NFTs across UTXO
167 lines (166 loc) • 6.04 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EvmRpcExplorer = void 0;
const errors_1 = require("../../errors");
const EvmNonceCache_1 = require("../../utils/EvmNonceCache");
/**
* Lightweight EVM explorer that communicates directly with a JSON-RPC 2.0
* endpoint. No dependency on the ChainGate API.
*
* Only exposes the subset of RPC methods needed by {@link EvmRpcConnector} and
* {@link EvmRpcTransaction}.
*/
class EvmRpcExplorer {
constructor(rpcUrl, chainId, nonceCache) {
this.nextId = 1;
this.rpcUrl = rpcUrl;
this.chainId = chainId;
this.nonceCache = nonceCache ?? new EvmNonceCache_1.EvmNonceCache();
}
// ---------------------------------------------------------------------------
// Public RPC helpers
// ---------------------------------------------------------------------------
/**
* Returns the wei balance for an address (`eth_getBalance` at `"latest"`).
*/
async getBalance(address) {
const hex = await this.call('eth_getBalance', [address, 'latest']);
return BigInt(hex);
}
/**
* Returns the nonce / transaction count for an address
* (`eth_getTransactionCount` at `"latest"`).
*/
async getTransactionCount(address) {
const hex = await this.call('eth_getTransactionCount', [address, 'latest']);
return BigInt(hex);
}
/**
* Returns the next nonce to use when sending a transaction from an address
* (`eth_getTransactionCount` at `"pending"`).
*/
async getNonce(address) {
const hex = await this.call('eth_getTransactionCount', [address, 'pending']);
return BigInt(hex);
}
/**
* Estimates gas for a transaction (`eth_estimateGas`).
*/
async estimateGas(params) {
const txObj = {
from: params.from,
to: params.to,
value: '0x' + params.value.toString(16),
};
if (params.data && params.data !== '0x') {
txObj.data = params.data;
}
if (params.nonce !== undefined) {
txObj.nonce = '0x' + params.nonce.toString(16);
}
const hex = await this.call('eth_estimateGas', [txObj]);
return BigInt(hex);
}
/**
* Returns fee data for the current block.
*
* Tries EIP-1559 (`eth_maxPriorityFeePerGas` + `baseFeePerGas` from the
* latest block). If the chain does not support EIP-1559, falls back to
* `eth_gasPrice`.
*/
async getFeeData() {
const gasPrice = await this.getGasPrice();
try {
const [priorityFeeHex, block] = await Promise.all([
this.call('eth_maxPriorityFeePerGas', []),
this.call('eth_getBlockByNumber', ['latest', false]),
]);
if (block.baseFeePerGas) {
const baseFee = BigInt(block.baseFeePerGas);
const maxPriorityFeePerGas = BigInt(priorityFeeHex);
// maxFeePerGas = 2 * baseFee + maxPriorityFeePerGas (same heuristic as ethers.js)
const maxFeePerGas = baseFee * 2n + maxPriorityFeePerGas;
return {
supportsEip1559: true,
maxFeePerGas,
maxPriorityFeePerGas,
gasPrice,
};
}
}
catch {
// Chain does not support EIP-1559 — fall through to legacy.
}
return { supportsEip1559: false, gasPrice };
}
/**
* Broadcasts a signed raw transaction (`eth_sendRawTransaction`).
*
* @returns The transaction hash.
*/
async sendRawTransaction(signedTxHex) {
return this.call('eth_sendRawTransaction', [signedTxHex]);
}
/**
* Returns the transaction receipt, or `null` if the transaction is still
* pending (`eth_getTransactionReceipt`).
*/
async getTransactionReceipt(txHash) {
const result = await this.call('eth_getTransactionReceipt', [txHash]);
if (!result)
return null;
return {
blockNumber: Number(BigInt(result.blockNumber)),
status: Number(BigInt(result.status)),
};
}
/**
* Returns the number of decimals for an ERC-20 token contract.
*/
async getTokenDecimals(contractAddress) {
// decimals() selector: 0x313ce567
const result = await this.call('eth_call', [
{ to: contractAddress, data: '0x313ce567' },
'latest',
]);
return Number(BigInt(result));
}
// ---------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------
/** Returns the current gas price via `eth_gasPrice`. */
async getGasPrice() {
const hex = await this.call('eth_gasPrice', []);
return BigInt(hex);
}
/**
* Sends a JSON-RPC 2.0 request to the configured endpoint.
*
* @throws {RpcError} if the response contains an `error` field or the HTTP
* request fails.
*/
async call(method, params) {
const id = this.nextId++;
const body = JSON.stringify({ jsonrpc: '2.0', method, params, id });
let res;
try {
res = await fetch(this.rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
});
}
catch (err) {
throw new errors_1.RpcError(`RPC request to ${this.rpcUrl} failed: ${err instanceof Error ? err.message : String(err)}`);
}
if (!res.ok) {
throw new errors_1.RpcError(`RPC HTTP error ${res.status}: ${res.statusText}`);
}
const json = (await res.json());
if (json.error) {
throw new errors_1.RpcError(json.error.message, json.error.code);
}
return json.result;
}
}
exports.EvmRpcExplorer = EvmRpcExplorer;