⚠️Blast API (blastapi.io) ends Oct 31. Migrate to Dwellir and skip Alchemy's expensive compute units.
Switch Today →
Skip to main content

eth_call

Executes a new message call immediately without creating a transaction on the blockchain. Used for reading smart contract data and simulating transactions.

Save 80% on eth_call Requests

Alchemy = $11.70 per million eth_call requests
Quicknode = $12.40 per million eth_call requests
Dwellir = $2 per million eth_call requests

Quicknode is 6X more expensive for eth_calls!

Switch to Dwellir today →

When to Use This Method

Use eth_call to:

  • Read Contract State - Query data from smart contracts
  • Simulate Transactions - Test transaction outcomes without gas costs
  • View Functions - Call contract view/pure functions
  • Cross-Chain Queries - Read omnichain contract state
  • Gas Estimation - Pre-calculate complex operations

Parameters

  1. Transaction Object (required):

    • from (optional): 20 bytes - Address the transaction is sent from
    • to (required): 20 bytes - Address the transaction is directed to
    • gas (optional): Hexadecimal value of gas provided
    • gasPrice (optional): Hexadecimal value of gas price
    • value (optional): Hexadecimal value sent with transaction
    • data (optional): Hash of method signature and encoded parameters
  2. Block Parameter (optional):

    • latest - Latest block (default)
    • earliest - Genesis block
    • pending - Pending state
    • Block number as hexadecimal
{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb8",
"data": "0x70a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
},
"latest"
],
"id": 1
}

Returns

DATA - The return value of the executed contract function.

  • Type: Hexadecimal string
  • Format: 0x prefixed
  • Decoding: Required based on contract ABI

Implementation Examples

# Query ERC20 balance
curl -X POST https://api-zetachain-mainnet.n.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf",
"data": "0x70a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
},
"latest"
],
"id": 1
}'

# Query omnichain contract state
curl -X POST https://api-zetachain-mainnet.n.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x239e96c8f17C85c30100AC26F635Ea15f23E9c67",
"data": "0x2e64cec1"
},
"latest"
],
"id": 1
}'

Example Response

{
"jsonrpc": "2.0",
"id": 1,
"result": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"
}

Decoding: 0x56bc75e2d63100000 = 100,000,000,000,000,000,000 (100 ZETA with 18 decimals)

Omnichain Contract Calls

ZetaChain enables unique cross-chain contract interactions:

Reading Cross-Chain State

// Query balances across multiple chains from one contract
const omnichainVault = new ethers.Contract(vaultAddress, vaultABI, provider);

async function getMultiChainBalances(userAddress) {
const chains = [1, 56, 137, 42161]; // ETH, BSC, Polygon, Arbitrum
const balances = {};

for (const chainId of chains) {
const balance = await omnichainVault.getBalance(userAddress, chainId);
balances[chainId] = ethers.formatEther(balance);
}

return balances;
}

Cross-Chain Oracle Queries

// Read price feeds from multiple chains
const priceOracle = new ethers.Contract(oracleAddress, oracleABI, provider);

async function getCrossChainPrices(token) {
const prices = await priceOracle.getPricesAcrossChains(token);

return {
ethereum: prices[0] / 1e8,
bsc: prices[1] / 1e8,
polygon: prices[2] / 1e8,
average: prices[3] / 1e8
};
}

Common Use Cases

1. Token Balance Queries

class TokenBalanceReader {
constructor(provider) {
this.provider = provider;
this.cache = new Map();
}

async getBalance(tokenAddress, accountAddress) {
const cacheKey = `${tokenAddress}-${accountAddress}`;

// Check cache (valid for 10 seconds)
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < 10000) {
return cached.balance;
}
}

// ERC20 balanceOf selector: 0x70a08231
const data = '0x70a08231' +
accountAddress.slice(2).padStart(64, '0');

const result = await this.provider.call({
to: tokenAddress,
data: data
});

const balance = ethers.formatEther(result);

// Cache result
this.cache.set(cacheKey, {
balance,
timestamp: Date.now()
});

return balance;
}

async getMultipleBalances(tokens, account) {
const balances = {};

// Batch calls for efficiency
const promises = tokens.map(token =>
this.getBalance(token.address, account)
);

const results = await Promise.all(promises);

tokens.forEach((token, index) => {
balances[token.symbol] = results[index];
});

return balances;
}
}

2. DEX Price Queries

async function getDEXPrices(pairAddress) {
const pairABI = [
'function getReserves() view returns (uint112 reserve0, uint112 reserve1, uint32 timestamp)',
'function token0() view returns (address)',
'function token1() view returns (address)'
];

const pair = new ethers.Contract(pairAddress, pairABI, provider);

// Get reserves and tokens
const [reserves, token0, token1] = await Promise.all([
pair.getReserves(),
pair.token0(),
pair.token1()
]);

// Calculate price
const price0To1 = Number(reserves.reserve1) / Number(reserves.reserve0);
const price1To0 = Number(reserves.reserve0) / Number(reserves.reserve1);

return {
token0Address: token0,
token1Address: token1,
price0To1,
price1To0,
liquidity: {
token0: ethers.formatEther(reserves.reserve0),
token1: ethers.formatEther(reserves.reserve1)
}
};
}

3. NFT Metadata Reading

async function getNFTMetadata(nftAddress, tokenId) {
const nftABI = [
'function tokenURI(uint256 tokenId) view returns (string)',
'function ownerOf(uint256 tokenId) view returns (address)',
'function balanceOf(address owner) view returns (uint256)'
];

const nft = new ethers.Contract(nftAddress, nftABI, provider);

try {
const [tokenURI, owner] = await Promise.all([
nft.tokenURI(tokenId),
nft.ownerOf(tokenId)
]);

// Fetch metadata from URI
const metadata = await fetch(tokenURI).then(r => r.json());

return {
tokenId,
owner,
tokenURI,
metadata
};
} catch (error) {
console.error('Error reading NFT:', error);
throw error;
}
}

4. Governance Voting Power

async function getVotingPower(governanceAddress, voterAddress, proposalId) {
const govABI = [
'function getVotes(address account) view returns (uint256)',
'function proposals(uint256) view returns (tuple(uint256 id, address proposer, uint256 forVotes, uint256 againstVotes, uint256 startBlock, uint256 endBlock, bool executed))',
'function hasVoted(uint256 proposalId, address account) view returns (bool)'
];

const governance = new ethers.Contract(governanceAddress, govABI, provider);

const [votes, proposal, hasVoted] = await Promise.all([
governance.getVotes(voterAddress),
governance.proposals(proposalId),
governance.hasVoted(proposalId, voterAddress)
]);

return {
votingPower: ethers.formatEther(votes),
proposal: {
forVotes: ethers.formatEther(proposal.forVotes),
againstVotes: ethers.formatEther(proposal.againstVotes),
active: proposal.endBlock > await provider.getBlockNumber()
},
hasVoted
};
}

5. Cross-Chain Bridge Status

async function checkBridgeTransfer(bridgeAddress, transferId) {
const bridgeABI = [
'function getTransferStatus(bytes32 transferId) view returns (uint8 status)',
'function getTransferDetails(bytes32 transferId) view returns (tuple(address sender, address recipient, uint256 amount, uint256 sourceChain, uint256 destChain, uint256 timestamp))'
];

const bridge = new ethers.Contract(bridgeAddress, bridgeABI, provider);

const [status, details] = await Promise.all([
bridge.getTransferStatus(transferId),
bridge.getTransferDetails(transferId)
]);

const statusMap = {
0: 'Pending',
1: 'InProgress',
2: 'Completed',
3: 'Failed'
};

return {
status: statusMap[status],
amount: ethers.formatEther(details.amount),
sourceChain: details.sourceChain,
destChain: details.destChain,
timestamp: new Date(Number(details.timestamp) * 1000)
};
}

Gas Optimization

Batch Reading

// Efficient multi-call pattern
async function batchRead(calls) {
const multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11';
const multicallABI = ['function aggregate(tuple(address target, bytes callData)[] calls) view returns (uint256 blockNumber, bytes[] returnData)'];

const multicall = new ethers.Contract(multicallAddress, multicallABI, provider);

const [, results] = await multicall.aggregate(calls);
return results;
}

Error Handling

Common Error Patterns

async function safeContractCall(contract, method, params) {
try {
const result = await contract[method](...params);
return { success: true, data: result };
} catch (error) {
if (error.code === 'CALL_EXCEPTION') {
return { success: false, error: 'Contract reverted' };
}
if (error.code === -32000) {
return { success: false, error: 'Execution reverted' };
}
throw error;
}
}

Best Practices

  1. Always specify gas limit for complex calls
  2. Cache results to minimize RPC calls
  3. Use multicall for batch operations
  4. Handle reverts gracefully
  5. Validate return data before decoding

Error Codes

CodeMessageDescription
-32600Invalid RequestThe JSON sent is not valid
-32602Invalid paramsInvalid method parameters
-32603Internal errorInternal JSON-RPC error
-32000Execution revertedContract execution failed
3Execution revertedEVM revert with reason