Class: BlockGiven::Client
- Inherits:
-
Object
- Object
- BlockGiven::Client
- Defined in:
- lib/block_given/client.rb
Overview
Public JSON-RPC client bound to a chain and a connector (viem's PublicClient).
connector = BlockGiven::Connectors::Alchemy.new(api_key: "...")
client = BlockGiven::Client.new(chain: :base, connector: connector)
client.block_number
client.get_balance("0x...")
Instance Attribute Summary collapse
-
#chain ⇒ Object
readonly
Returns the value of attribute chain.
-
#connector ⇒ Object
readonly
Returns the value of attribute connector.
Instance Method Summary collapse
-
#batch(calls) ⇒ Object
Batched JSON-RPC: client.batch([["eth_blockNumber"], ["eth_chainId"]]).
- #block_number ⇒ Object
-
#call(to:, data:, from: nil, value: nil, gas: nil, block: :latest) ⇒ Object
Executes a read-only call.
-
#chain_id ⇒ Object
--- Chain / blocks -----------------------------------------------------.
- #contract?(address) ⇒ Boolean
-
#estimate_fees_per_gas(base_fee_multiplier: nil) ⇒ Object
EIP-1559 fee estimation (viem semantics: baseFee * multiplier + priority fee).
- #estimate_gas(to:, data: nil, from: nil, value: nil) ⇒ Object
- #gas_price ⇒ Object
-
#get_balance(address, block: :latest) ⇒ Object
--- Accounts -----------------------------------------------------------.
- #get_block(block = :latest, include_transactions: false) ⇒ Object
- #get_code(address, block: :latest) ⇒ Object
-
#get_logs(address: nil, topics: nil, from_block: :latest, to_block: :latest, block_hash: nil) ⇒ Object
--- Logs ---------------------------------------------------------------.
-
#get_logs_in_chunks(from_block:, address: nil, topics: nil, to_block: :latest, max_block_range: nil) ⇒ Object
get_logs over a large range, split in chunks of max_block_range blocks so provider limits are respected.
- #get_storage_at(address, slot, block: :latest) ⇒ Object
- #get_transaction(hash) ⇒ Object
- #get_transaction_count(address, block: :pending) ⇒ Object
- #get_transaction_receipt(hash) ⇒ Object
-
#initialize(chain: nil, connector: nil, polling_interval: nil, timeout: nil, logger: nil) ⇒ Client
constructor
A new instance of Client.
- #inspect ⇒ Object
- #logger ⇒ Object
- #max_priority_fee_per_gas ⇒ Object
- #polling_interval ⇒ Object
-
#request(method, *params) ⇒ Object
Raw JSON-RPC call: client.request("eth_blockNumber") / client.request("eth_getBalance", addr, "latest").
-
#send_raw_transaction(raw) ⇒ Object
--- Transactions -------------------------------------------------------.
- #timeout ⇒ Object
- #transaction(hash) ⇒ Object
-
#wait_for_transaction_receipt(hash, confirmations: nil, timeout: nil, polling_interval: nil) ⇒ Object
Polls until the receipt is available (and confirmed by N blocks).
-
#watch_block_number(polling_interval: nil, emit_missed: false, id: nil, &block) ⇒ Object
Yields each new block number.
- #watch_blocks(polling_interval: nil, include_transactions: false, id: nil, &block) ⇒ Object
-
#watch_logs(address: nil, topics: nil, from_block: nil, polling_interval: nil, max_block_range: nil, confirmations: 0, on_progress: nil, id: nil, name: nil, &block) ⇒ Object
Yields new logs matching the filter, one Array per processed block range.
-
#watcher(name, polling_interval = nil, id: nil, &tick) ⇒ Object
Generic background watcher on this client.
Constructor Details
#initialize(chain: nil, connector: nil, polling_interval: nil, timeout: nil, logger: nil) ⇒ Client
Returns a new instance of Client.
13 14 15 16 17 18 19 |
# File 'lib/block_given/client.rb', line 13 def initialize(chain: nil, connector: nil, polling_interval: nil, timeout: nil, logger: nil) @chain = chain ? Chains.resolve(chain) : BlockGiven.config.chain! @connector = connector || BlockGiven.config.connector! @polling_interval = polling_interval @timeout = timeout @logger = logger end |
Instance Attribute Details
#chain ⇒ Object (readonly)
Returns the value of attribute chain.
11 12 13 |
# File 'lib/block_given/client.rb', line 11 def chain @chain end |
#connector ⇒ Object (readonly)
Returns the value of attribute connector.
11 12 13 |
# File 'lib/block_given/client.rb', line 11 def connector @connector end |
Instance Method Details
#batch(calls) ⇒ Object
Batched JSON-RPC: client.batch([["eth_blockNumber"], ["eth_chainId"]])
34 |
# File 'lib/block_given/client.rb', line 34 def batch(calls) = connector.batch(calls, chain: chain) |
#block_number ⇒ Object
39 |
# File 'lib/block_given/client.rb', line 39 def block_number = Utils.hex_to_int(request("eth_blockNumber")) |
#call(to:, data:, from: nil, value: nil, gas: nil, block: :latest) ⇒ Object
Executes a read-only call. Returns the raw hex result.
91 92 93 |
# File 'lib/block_given/client.rb', line 91 def call(to:, data:, from: nil, value: nil, gas: nil, block: :latest) request("eth_call", call_object(to: to, data: data, from: from, value: value, gas: gas), Utils.block_tag(block)) end |
#chain_id ⇒ Object
--- Chain / blocks -----------------------------------------------------
38 |
# File 'lib/block_given/client.rb', line 38 def chain_id = Utils.hex_to_int(request("eth_chainId")) |
#contract?(address) ⇒ Boolean
82 |
# File 'lib/block_given/client.rb', line 82 def contract?(address) = get_code(address) != "0x" |
#estimate_fees_per_gas(base_fee_multiplier: nil) ⇒ Object
EIP-1559 fee estimation (viem semantics: baseFee * multiplier + priority fee).
56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/block_given/client.rb', line 56 def estimate_fees_per_gas(base_fee_multiplier: nil) multiplier = base_fee_multiplier || BlockGiven.config.base_fee_multiplier block = get_block(:latest) base_fee = block[:base_fee_per_gas] || gas_price priority = max_priority_fee_per_gas { base_fee_per_gas: base_fee, max_priority_fee_per_gas: priority, max_fee_per_gas: (base_fee * multiplier).ceil + priority } end |
#estimate_gas(to:, data: nil, from: nil, value: nil) ⇒ Object
95 96 97 |
# File 'lib/block_given/client.rb', line 95 def estimate_gas(to:, data: nil, from: nil, value: nil) Utils.hex_to_int(request("eth_estimateGas", call_object(to: to, data: data, from: from, value: value))) end |
#gas_price ⇒ Object
47 |
# File 'lib/block_given/client.rb', line 47 def gas_price = Utils.hex_to_int(request("eth_gasPrice")) |
#get_balance(address, block: :latest) ⇒ Object
--- Accounts -----------------------------------------------------------
70 71 72 |
# File 'lib/block_given/client.rb', line 70 def get_balance(address, block: :latest) Utils.hex_to_int(request("eth_getBalance", Utils.checksum_address(address), Utils.block_tag(block))) end |
#get_block(block = :latest, include_transactions: false) ⇒ Object
41 42 43 44 45 |
# File 'lib/block_given/client.rb', line 41 def get_block(block = :latest, include_transactions: false) method = Utils.hex?(block.to_s) && block.to_s.length == 66 ? "eth_getBlockByHash" : "eth_getBlockByNumber" raw = request(method, block_param(block), include_transactions) raw && Normalizer.normalize(raw) end |
#get_code(address, block: :latest) ⇒ Object
78 79 80 |
# File 'lib/block_given/client.rb', line 78 def get_code(address, block: :latest) request("eth_getCode", Utils.checksum_address(address), Utils.block_tag(block)) end |
#get_logs(address: nil, topics: nil, from_block: :latest, to_block: :latest, block_hash: nil) ⇒ Object
--- Logs ---------------------------------------------------------------
132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/block_given/client.rb', line 132 def get_logs(address: nil, topics: nil, from_block: :latest, to_block: :latest, block_hash: nil) filter = {} filter[:address] = Array(address).map { |a| Utils.checksum_address(a) } if address filter[:address] = filter[:address].first if filter[:address]&.size == 1 filter[:topics] = topics if topics if block_hash filter[:blockHash] = block_hash else filter[:fromBlock] = Utils.block_tag(from_block) filter[:toBlock] = Utils.block_tag(to_block) end Normalizer.normalize(request("eth_getLogs", filter)) end |
#get_logs_in_chunks(from_block:, address: nil, topics: nil, to_block: :latest, max_block_range: nil) ⇒ Object
get_logs over a large range, split in chunks of max_block_range blocks so provider limits are respected. Yields each chunk's logs when a block is given, else returns them all.
173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/block_given/client.rb', line 173 def get_logs_in_chunks(from_block:, address: nil, topics: nil, to_block: :latest, max_block_range: nil) size = max_block_range || BlockGiven.config.max_block_range to_block = block_number if to_block.nil? || Utils::BLOCK_TAGS.include?(to_block.to_s) collected = [] from = from_block while from <= to_block to = [from + size - 1, to_block].min logs = get_logs(address: address, topics: topics, from_block: from, to_block: to) block_given? ? yield(logs, from, to) : collected.concat(logs) from = to + 1 end collected end |
#get_storage_at(address, slot, block: :latest) ⇒ Object
84 85 86 |
# File 'lib/block_given/client.rb', line 84 def get_storage_at(address, slot, block: :latest) request("eth_getStorageAt", Utils.checksum_address(address), Utils.to_hex(slot), Utils.block_tag(block)) end |
#get_transaction(hash) ⇒ Object
107 108 109 110 |
# File 'lib/block_given/client.rb', line 107 def get_transaction(hash) raw = request("eth_getTransactionByHash", hash) raw && Normalizer.normalize(raw) end |
#get_transaction_count(address, block: :pending) ⇒ Object
74 75 76 |
# File 'lib/block_given/client.rb', line 74 def get_transaction_count(address, block: :pending) Utils.hex_to_int(request("eth_getTransactionCount", Utils.checksum_address(address), Utils.block_tag(block))) end |
#get_transaction_receipt(hash) ⇒ Object
112 113 114 115 |
# File 'lib/block_given/client.rb', line 112 def get_transaction_receipt(hash) raw = request("eth_getTransactionReceipt", hash) raw && Receipt.new(raw) end |
#inspect ⇒ Object
215 |
# File 'lib/block_given/client.rb', line 215 def inspect = "#<BlockGiven::Client chain=#{chain} connector=#{connector.inspect}>" |
#logger ⇒ Object
23 |
# File 'lib/block_given/client.rb', line 23 def logger = @logger || BlockGiven.config.logger |
#max_priority_fee_per_gas ⇒ Object
49 50 51 52 53 |
# File 'lib/block_given/client.rb', line 49 def max_priority_fee_per_gas Utils.hex_to_int(request("eth_maxPriorityFeePerGas")) rescue RpcError Utils.parse_gwei("1") # method not supported by every node end |
#polling_interval ⇒ Object
21 |
# File 'lib/block_given/client.rb', line 21 def polling_interval = @polling_interval || BlockGiven.config.polling_interval |
#request(method, *params) ⇒ Object
Raw JSON-RPC call: client.request("eth_blockNumber") / client.request("eth_getBalance", addr, "latest")
26 27 28 29 30 31 |
# File 'lib/block_given/client.rb', line 26 def request(method, *params) logger.debug { "[block_given] -> #{method} #{params.inspect}" } result = connector.request(method, params, chain: chain) logger.debug { "[block_given] <- #{method} #{result.inspect[0, 200]}" } result end |
#send_raw_transaction(raw) ⇒ Object
--- Transactions -------------------------------------------------------
101 102 103 |
# File 'lib/block_given/client.rb', line 101 def send_raw_transaction(raw) Transaction.new(request("eth_sendRawTransaction", Utils.prefix_hex(raw)), client: self) end |
#timeout ⇒ Object
22 |
# File 'lib/block_given/client.rb', line 22 def timeout = @timeout || BlockGiven.config.timeout |
#transaction(hash) ⇒ Object
105 |
# File 'lib/block_given/client.rb', line 105 def transaction(hash) = Transaction.new(hash, client: self) |
#wait_for_transaction_receipt(hash, confirmations: nil, timeout: nil, polling_interval: nil) ⇒ Object
Polls until the receipt is available (and confirmed by N blocks).
118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/block_given/client.rb', line 118 def wait_for_transaction_receipt(hash, confirmations: nil, timeout: nil, polling_interval: nil) confirmations ||= BlockGiven.config.confirmations interval = polling_interval || self.polling_interval Poller.poll(interval: interval, timeout: timeout || self.timeout, description: "receipt of #{hash}") do receipt = get_transaction_receipt(hash) next nil unless receipt&.block_number next receipt if confirmations <= 1 block_number - receipt.block_number + 1 >= confirmations ? receipt : nil end end |
#watch_block_number(polling_interval: nil, emit_missed: false, id: nil, &block) ⇒ Object
Yields each new block number. With emit_missed: true every block between two polls is yielded, otherwise only the latest one.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/block_given/client.rb', line 150 def watch_block_number(polling_interval: nil, emit_missed: false, id: nil, &block) last = nil watcher("block_number", polling_interval, id: id) do current = block_number next if last && current <= last if emit_missed && last ((last + 1)..current).each { |n| block.call(n) } else block.call(current) end last = current end end |
#watch_blocks(polling_interval: nil, include_transactions: false, id: nil, &block) ⇒ Object
165 166 167 168 169 |
# File 'lib/block_given/client.rb', line 165 def watch_blocks(polling_interval: nil, include_transactions: false, id: nil, &block) watch_block_number(polling_interval: polling_interval, emit_missed: true, id: id) do |number| block.call(get_block(number, include_transactions: include_transactions)) end end |
#watch_logs(address: nil, topics: nil, from_block: nil, polling_interval: nil, max_block_range: nil, confirmations: 0, on_progress: nil, id: nil, name: nil, &block) ⇒ Object
Yields new logs matching the filter, one Array per processed block range.
from_block: resume from this block (catch-up is chunked by max_block_range)
confirmations: stay this many blocks behind the head to dodge reorgs (default 0)
on_progress: ->(from, to) called after each range is processed: persist `to` as your cursor
watcher.cursor: last processed block number
id: stable identifier for BlockGiven::Watcher.find / stop (default: auto-generated)
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/block_given/client.rb', line 194 def watch_logs(address: nil, topics: nil, from_block: nil, polling_interval: nil, max_block_range: nil, confirmations: 0, on_progress: nil, id: nil, name: nil, &block) last = from_block ? from_block - 1 : block_number - confirmations watcher(name || "logs@#{Array(address).first || '*'}", polling_interval, id: id) do |watcher| watcher.cursor ||= last head = block_number - confirmations while last < head && !watcher.stopped? to = [last + (max_block_range || BlockGiven.config.max_block_range), head].min logs = get_logs(address: address, topics: topics, from_block: last + 1, to_block: to) block.call(logs) unless logs.empty? on_progress&.call(last + 1, to) last = watcher.cursor = to end end end |
#watcher(name, polling_interval = nil, id: nil, &tick) ⇒ Object
Generic background watcher on this client.
211 212 213 |
# File 'lib/block_given/client.rb', line 211 def watcher(name, polling_interval = nil, id: nil, &tick) Watcher.new(interval: polling_interval || self.polling_interval, name: name, id: id, logger: logger, &tick).start end |