Hedera has two kinds of reads: free queries (the node answers for nothing) and paid queries (the request must carry a signed payment covering the query fee). hedera_ex handles the payment for you.

Free — account balance

{:ok, %{balance: tinybars, token_balances: tokens}} =
  Client.account_balance(client, AccountId.parse("0.0.8260469"))

token_balances is a list of %{token_id:, balance:, decimals:}.

The QueryHeader.payment is a signed CryptoTransfer paying the node; the fee is :query_payment tinybars (default 100_000):

{:ok, info} = Client.account_info(client, account, query_payment: 100_000)

info.account_id             #=> %AccountId{}
info.balance                #=> tinybars
info.memo                   #=> string
info.deleted                #=> boolean
info.owned_nfts             #=> integer
info.receiver_sig_required  #=> boolean
info.key_present?           #=> boolean

Execute a contract call on a node and read its return value without a consensus transaction (no state change, no consensus fee — just the query fee):

{:ok, %{result: bytes, gas_used: gas, error_message: err}} =
  Client.call_contract_local(client, contract,
    gas: 50_000,
    function_parameters: abi_encoded_call
  )

result is the raw ABI-encoded return value; decode it with your ABI helper. error_message is nil on success.

Consensus results

For a transaction's outcome, use Client.transaction_receipt/3 (a free query that polls until final) — see Transactions. For a contract call's return value after a consensus call_contract/3, read the transaction record via the mirror node:

{:ok, record} = Hedera.MirrorNode.contract_result(tx_id)
record["call_result"]   # hex-encoded return bytes (subject to mirror-node lag)

Note: the free/paid query functions currently target the first node in the address book (no cross-node retry, unlike the write path). Retry at the call site if a specific node is unavailable.