block_given 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -1
- data/README.md +92 -55
- data/lib/block_given/abi/codec/decoder.rb +111 -0
- data/lib/block_given/abi/codec.rb +170 -0
- data/lib/block_given/abi/coder.rb +122 -11
- data/lib/block_given/abi/custom_error.rb +33 -1
- data/lib/block_given/abi/event.rb +85 -3
- data/lib/block_given/abi/function.rb +94 -2
- data/lib/block_given/abi/interface.rb +99 -4
- data/lib/block_given/abi/parameter.rb +60 -3
- data/lib/block_given/abi/standards/erc1155.rb +41 -0
- data/lib/block_given/abi/standards/erc20.rb +33 -0
- data/lib/block_given/abi/standards/erc4626.rb +41 -0
- data/lib/block_given/abi/standards/erc721.rb +47 -0
- data/lib/block_given/abi/standards.rb +124 -0
- data/lib/block_given/abi/type.rb +138 -0
- data/lib/block_given/chain.rb +114 -2
- data/lib/block_given/client.rb +322 -20
- data/lib/block_given/configuration.rb +85 -3
- data/lib/block_given/connectors/alchemy.rb +38 -4
- data/lib/block_given/connectors/base.rb +32 -5
- data/lib/block_given/connectors/http.rb +91 -5
- data/lib/block_given/connectors/stub.rb +70 -4
- data/lib/block_given/contract.rb +422 -26
- data/lib/block_given/crypto/keccak.rb +152 -0
- data/lib/block_given/crypto/secp256k1.rb +168 -0
- data/lib/block_given/crypto.rb +22 -0
- data/lib/block_given/eip712.rb +199 -0
- data/lib/block_given/errors.rb +134 -14
- data/lib/block_given/event.rb +51 -1
- data/lib/block_given/normalizer.rb +27 -2
- data/lib/block_given/poller.rb +177 -15
- data/lib/block_given/receipt.rb +63 -3
- data/lib/block_given/rlp.rb +146 -0
- data/lib/block_given/signed_transaction.rb +148 -47
- data/lib/block_given/transaction.rb +103 -12
- data/lib/block_given/transaction_envelope/fields.rb +104 -0
- data/lib/block_given/transaction_envelope.rb +183 -0
- data/lib/block_given/utils.rb +143 -9
- data/lib/block_given/version.rb +2 -1
- data/lib/block_given/wallet.rb +216 -35
- data/lib/block_given.rb +67 -4
- metadata +19 -23
data/lib/block_given/wallet.rb
CHANGED
|
@@ -1,66 +1,240 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module BlockGiven
|
|
4
|
-
# A private key
|
|
5
|
-
# (viem's WalletClient + Account).
|
|
4
|
+
# A private key plus an optional {Client}, able to sign messages and to prepare, sign and send
|
|
5
|
+
# transactions (the equivalent of viem's WalletClient + Account).
|
|
6
6
|
#
|
|
7
|
+
# Every value and fee is an Integer amount of wei. Transactions are EIP-1559 (type 2) by default;
|
|
8
|
+
# passing `gas_price:` to any transaction method builds a legacy (type 0) transaction instead.
|
|
9
|
+
# Missing fields (nonce, gas, fees, chain id) are resolved from {#client} at signing time.
|
|
10
|
+
#
|
|
11
|
+
# @example Create a wallet and send ether
|
|
7
12
|
# wallet = BlockGiven::Wallet.new(private_key: ENV["PRIVATE_KEY"])
|
|
8
|
-
# wallet.address
|
|
13
|
+
# wallet.address # => "0xAbC..." (EIP-55 checksummed)
|
|
9
14
|
# wallet.send_transaction(to: "0x...", value: BlockGiven::Utils.parse_ether("0.01")).wait
|
|
10
15
|
class Wallet
|
|
16
|
+
# @return [String] the EIP-55 checksummed address derived from the private key
|
|
11
17
|
attr_reader :address
|
|
12
18
|
|
|
19
|
+
# Creates a wallet with a freshly generated random private key.
|
|
20
|
+
#
|
|
21
|
+
# @param client [Client, nil] client used for RPC calls; when nil, {BlockGiven.client} is used
|
|
22
|
+
# @return [Wallet] a new wallet holding the generated key
|
|
13
23
|
def self.generate(client: nil)
|
|
14
|
-
new(private_key:
|
|
24
|
+
new(private_key: Crypto::Secp256k1.int_to_bytes(Crypto::Secp256k1.generate_private_key).unpack1("H*"),
|
|
25
|
+
client: client)
|
|
15
26
|
end
|
|
16
27
|
|
|
28
|
+
# Builds a wallet from an existing private key.
|
|
29
|
+
#
|
|
30
|
+
# @param private_key [String] 32-byte private key as 64 hex characters, with or without the `0x` prefix
|
|
31
|
+
# (surrounding whitespace is ignored)
|
|
32
|
+
# @param client [Client, nil] client used for nonce, gas and fee lookups and for broadcasting; when nil,
|
|
33
|
+
# every call falls back to {BlockGiven.client}
|
|
34
|
+
# @raise [BlockGiven::InvalidArgumentError] when the key is not exactly 32 bytes of hex, or is not a valid
|
|
35
|
+
# secp256k1 key (zero, or not below the curve order)
|
|
36
|
+
# @example
|
|
37
|
+
# wallet = BlockGiven::Wallet.new(private_key: "0x4c0883a6...d1e6") # 64 hex chars
|
|
38
|
+
# wallet.address # => "0x..."
|
|
17
39
|
def initialize(private_key:, client: nil)
|
|
18
40
|
hex = Utils.strip_hex(private_key.to_s.strip)
|
|
19
41
|
raise InvalidArgumentError, "private key must be 32 bytes hex" unless hex.match?(/\A[0-9a-fA-F]{64}\z/)
|
|
20
42
|
|
|
21
|
-
@
|
|
22
|
-
|
|
43
|
+
@private_key = hex.to_i(16)
|
|
44
|
+
unless Crypto::Secp256k1.valid_private_key?(@private_key)
|
|
45
|
+
raise InvalidArgumentError, "private key is out of the secp256k1 range"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@public_key = Crypto::Secp256k1.public_key(@private_key)
|
|
49
|
+
@address = Crypto.address(@public_key)
|
|
23
50
|
@client = client
|
|
24
51
|
end
|
|
25
52
|
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
# The private key, for persistence or export. It is never included in {#inspect} or {#to_s}.
|
|
54
|
+
#
|
|
55
|
+
# @return [String] the 32-byte private key as `0x`-prefixed hex
|
|
56
|
+
def private_key = Utils.bin_to_hex(Crypto::Secp256k1.int_to_bytes(@private_key))
|
|
28
57
|
|
|
58
|
+
# The uncompressed public key matching {#private_key}.
|
|
59
|
+
#
|
|
60
|
+
# @return [String] the 65-byte public key as `0x`-prefixed hex
|
|
61
|
+
def public_key = Utils.bin_to_hex(@public_key)
|
|
62
|
+
|
|
63
|
+
# The client used for every RPC call made by this wallet.
|
|
64
|
+
#
|
|
65
|
+
# @return [Client] the client given to {#initialize}, or {BlockGiven.client} when none was given
|
|
29
66
|
def client = @client || BlockGiven.client
|
|
67
|
+
|
|
68
|
+
# Returns a copy of this wallet (same key) bound to another client, e.g. to sign on another chain.
|
|
69
|
+
#
|
|
70
|
+
# @param client [Client] the client the copy will use
|
|
71
|
+
# @return [Wallet] a new wallet with the same private key and the given client
|
|
30
72
|
def with_client(client) = self.class.new(private_key: private_key, client: client)
|
|
73
|
+
|
|
74
|
+
# The chain of {#client}.
|
|
75
|
+
#
|
|
76
|
+
# @return [Chain] the chain the wallet's transactions are built for
|
|
31
77
|
def chain = client.chain
|
|
32
78
|
|
|
79
|
+
# Balance of {#address} in wei.
|
|
80
|
+
#
|
|
81
|
+
# @param block [Symbol, Integer, String] block tag (`:latest`, `:pending`, `:safe`, `:finalized`, `:earliest`)
|
|
82
|
+
# or block number
|
|
83
|
+
# @return [Integer] the balance in wei at that block
|
|
33
84
|
def balance(block: :latest) = client.get_balance(address, block: block)
|
|
85
|
+
|
|
86
|
+
# Transaction count of {#address}, i.e. the nonce the next transaction should use.
|
|
87
|
+
#
|
|
88
|
+
# @param block [Symbol, Integer, String] block tag or number; defaults to `:pending` so that transactions
|
|
89
|
+
# still waiting in the mempool are counted
|
|
90
|
+
# @return [Integer] the transaction count at that block
|
|
34
91
|
def nonce(block: :pending) = client.get_transaction_count(address, block: block)
|
|
35
92
|
|
|
36
|
-
#
|
|
93
|
+
# Signs a message with the EIP-191 `personal_sign` scheme.
|
|
94
|
+
#
|
|
95
|
+
# The message is prefixed with `"\x19Ethereum Signed Message:\n" + length` before hashing, so the signature
|
|
96
|
+
# can be verified with `ecrecover` or viem's `verifyMessage`.
|
|
97
|
+
#
|
|
98
|
+
# @param message [String] the message to sign, as a plain string
|
|
99
|
+
# @return [String] the 65-byte signature (r, s, v) as `0x`-prefixed hex
|
|
100
|
+
# @example
|
|
101
|
+
# signature = wallet.sign_message("Login to Bolero at 2024-01-01")
|
|
102
|
+
# signature # => "0x..." (132 hex characters)
|
|
37
103
|
def sign_message(message)
|
|
38
|
-
|
|
104
|
+
bytes = message.to_s.b
|
|
105
|
+
sign_digest(Crypto::Keccak.digest("\x19Ethereum Signed Message:\n#{bytes.bytesize}".b + bytes))
|
|
39
106
|
end
|
|
40
107
|
|
|
41
|
-
# EIP-712 typed data
|
|
108
|
+
# Signs EIP-712 typed structured data.
|
|
109
|
+
#
|
|
110
|
+
# @param typed_data [Hash] the EIP-712 payload with `:types`, `:primaryType`, `:domain` and `:message` keys
|
|
111
|
+
# (String keys are accepted too). `EIP712Domain` may be omitted from `:types`: it is then derived from the
|
|
112
|
+
# domain keys present. Arrays are hashed as the specification (and viem) define them
|
|
113
|
+
# @return [String] the 65-byte signature (r, s, v) as `0x`-prefixed hex
|
|
114
|
+
# @raise [BlockGiven::InvalidArgumentError] when a section is missing, a type is unknown or a value does not
|
|
115
|
+
# fit its type
|
|
116
|
+
# @example Sign an ERC-2612 permit
|
|
117
|
+
# wallet.sign_typed_data(
|
|
118
|
+
# types: { EIP712Domain: [...], Permit: [{ name: "owner", type: "address" }, ...] },
|
|
119
|
+
# primaryType: "Permit",
|
|
120
|
+
# domain: { name: "USD Coin", version: "2", chainId: 8453, verifyingContract: "0x..." },
|
|
121
|
+
# message: { owner: wallet.address, spender: "0x...", value: 1_000_000, nonce: 0, deadline: 1_700_000_000 }
|
|
122
|
+
# )
|
|
42
123
|
def sign_typed_data(typed_data)
|
|
43
|
-
|
|
124
|
+
sign_digest(Eip712.hash(typed_data))
|
|
44
125
|
end
|
|
45
126
|
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
127
|
+
# Resolves the missing fields, signs, and returns the transaction without broadcasting it.
|
|
128
|
+
#
|
|
129
|
+
# The returned {SignedTransaction} knows its {SignedTransaction#hash} (keccak of the signed bytes) and
|
|
130
|
+
# {SignedTransaction#nonce} before any network call: persist them, then call {SignedTransaction#broadcast}.
|
|
131
|
+
# See {#prepare_transaction} for how each missing field is resolved.
|
|
132
|
+
#
|
|
133
|
+
# @overload signed_transaction(to: nil, value: 0, data: nil, gas: nil, nonce: nil, max_fee_per_gas: nil,
|
|
134
|
+
# max_priority_fee_per_gas: nil, gas_price: nil, chain_id: nil, access_list: nil)
|
|
135
|
+
# @param to [String, nil] recipient address (any case, checksummed before signing); nil for contract creation
|
|
136
|
+
# @param value [Integer, String, Float, BigDecimal, Rational] amount of wei to send; Strings may be decimal
|
|
137
|
+
# or `0x` hex, non-Integer numerics must have no fractional part. Defaults to 0
|
|
138
|
+
# @param data [String, nil] calldata as hex (with or without `0x`); nil or empty means no calldata
|
|
139
|
+
# @param gas [Integer, nil] gas limit; defaults to `eth_estimateGas` multiplied by
|
|
140
|
+
# `BlockGiven.config.gas_multiplier` (rounded up)
|
|
141
|
+
# @param nonce [Integer, nil] nonce to use; defaults to the wallet's pending transaction count ({#nonce})
|
|
142
|
+
# @param max_fee_per_gas [Integer, nil] EIP-1559 max fee per gas in wei; defaults to
|
|
143
|
+
# {Client#estimate_fees_per_gas} when omitted (ignored when `gas_price:` is given)
|
|
144
|
+
# @param max_priority_fee_per_gas [Integer, nil] EIP-1559 priority fee per gas in wei; defaults to
|
|
145
|
+
# {Client#estimate_fees_per_gas} when omitted (ignored when `gas_price:` is given)
|
|
146
|
+
# @param gas_price [Integer, nil] gas price in wei; when given, a legacy (type 0) transaction is built and
|
|
147
|
+
# the EIP-1559 fee fields are not used. Never estimated: legacy transactions need an explicit value
|
|
148
|
+
# @param chain_id [Integer, nil] chain id for EIP-155 replay protection; defaults to `client.chain.id`
|
|
149
|
+
# @param access_list [Array<Hash>, nil] EIP-2930 access list; nil means an empty list (EIP-1559 only)
|
|
150
|
+
# @return [SignedTransaction] the signed transaction, ready to be broadcast or persisted
|
|
151
|
+
# @raise [BlockGiven::InvalidArgumentError] when `value:` is malformed or fractional, or when `to:` is nil
|
|
152
|
+
# without an explicit `gas:` (contract creation cannot be estimated here)
|
|
153
|
+
# @example Persist the hash before broadcasting, replace the transaction if it gets stuck
|
|
154
|
+
# signed = wallet.signed_transaction(to: usdc_address, data: calldata)
|
|
155
|
+
# record.update!(tx_hash: signed.hash, nonce: signed.nonce, raw_tx: signed.raw)
|
|
156
|
+
# tx = signed.broadcast # => BlockGiven::Transaction with the same hash
|
|
157
|
+
# signed.replacement.broadcast if tx.pending? # same nonce, fees bumped by 12.5%
|
|
50
158
|
def signed_transaction(**params)
|
|
51
159
|
prepared = prepare_transaction(**params)
|
|
52
|
-
|
|
53
|
-
tx.sign(@key)
|
|
54
|
-
SignedTransaction.new(raw: Utils.prefix_hex(tx.hex), params: prepared, wallet: self)
|
|
160
|
+
SignedTransaction.new(raw: TransactionEnvelope.sign(prepared, @private_key), params: prepared, wallet: self)
|
|
55
161
|
end
|
|
56
162
|
|
|
57
|
-
# Signs and returns the raw
|
|
163
|
+
# Signs a transaction and returns only the raw signed bytes, without broadcasting.
|
|
164
|
+
#
|
|
165
|
+
# Shorthand for `signed_transaction(**params).raw`; see {#signed_transaction} for the keyword semantics.
|
|
166
|
+
#
|
|
167
|
+
# @overload sign_transaction(to: nil, value: 0, data: nil, gas: nil, nonce: nil, max_fee_per_gas: nil,
|
|
168
|
+
# max_priority_fee_per_gas: nil, gas_price: nil, chain_id: nil, access_list: nil)
|
|
169
|
+
# @param to [String, nil] recipient address; nil for contract creation (then `gas:` is required)
|
|
170
|
+
# @param value [Integer, String, Float, BigDecimal, Rational] amount of wei to send (default 0)
|
|
171
|
+
# @param data [String, nil] calldata as hex
|
|
172
|
+
# @param gas [Integer, nil] gas limit; default `eth_estimateGas` times `BlockGiven.config.gas_multiplier`
|
|
173
|
+
# @param nonce [Integer, nil] nonce; default the wallet's pending transaction count
|
|
174
|
+
# @param max_fee_per_gas [Integer, nil] EIP-1559 max fee in wei; default from {Client#estimate_fees_per_gas}
|
|
175
|
+
# @param max_priority_fee_per_gas [Integer, nil] EIP-1559 priority fee in wei; default from
|
|
176
|
+
# {Client#estimate_fees_per_gas}
|
|
177
|
+
# @param gas_price [Integer, nil] gas price in wei; switches to a legacy (type 0) transaction
|
|
178
|
+
# @param chain_id [Integer, nil] chain id; default `client.chain.id`
|
|
179
|
+
# @param access_list [Array<Hash>, nil] EIP-2930 access list (EIP-1559 only)
|
|
180
|
+
# @return [String] the RLP-encoded signed transaction as `0x`-prefixed hex, as accepted by
|
|
181
|
+
# `eth_sendRawTransaction`
|
|
182
|
+
# @raise [BlockGiven::InvalidArgumentError] same conditions as {#signed_transaction}
|
|
58
183
|
def sign_transaction(**params) = signed_transaction(**params).raw
|
|
59
184
|
|
|
60
|
-
# Signs and broadcasts
|
|
185
|
+
# Signs a transaction and broadcasts it right away.
|
|
186
|
+
#
|
|
187
|
+
# Shorthand for `signed_transaction(**params).broadcast`; see {#signed_transaction} for the keyword semantics.
|
|
188
|
+
#
|
|
189
|
+
# @overload send_transaction(to: nil, value: 0, data: nil, gas: nil, nonce: nil, max_fee_per_gas: nil,
|
|
190
|
+
# max_priority_fee_per_gas: nil, gas_price: nil, chain_id: nil, access_list: nil)
|
|
191
|
+
# @param to [String, nil] recipient address; nil for contract creation (then `gas:` is required)
|
|
192
|
+
# @param value [Integer, String, Float, BigDecimal, Rational] amount of wei to send (default 0)
|
|
193
|
+
# @param data [String, nil] calldata as hex
|
|
194
|
+
# @param gas [Integer, nil] gas limit; default `eth_estimateGas` times `BlockGiven.config.gas_multiplier`
|
|
195
|
+
# @param nonce [Integer, nil] nonce; default the wallet's pending transaction count
|
|
196
|
+
# @param max_fee_per_gas [Integer, nil] EIP-1559 max fee in wei; default from {Client#estimate_fees_per_gas}
|
|
197
|
+
# @param max_priority_fee_per_gas [Integer, nil] EIP-1559 priority fee in wei; default from
|
|
198
|
+
# {Client#estimate_fees_per_gas}
|
|
199
|
+
# @param gas_price [Integer, nil] gas price in wei; switches to a legacy (type 0) transaction
|
|
200
|
+
# @param chain_id [Integer, nil] chain id; default `client.chain.id`
|
|
201
|
+
# @param access_list [Array<Hash>, nil] EIP-2930 access list (EIP-1559 only)
|
|
202
|
+
# @return [Transaction] a handle on the broadcast transaction, carrying the locally computed hash
|
|
203
|
+
# @raise [BlockGiven::InvalidArgumentError] same conditions as {#signed_transaction}
|
|
204
|
+
# @raise [BlockGiven::RpcError] when the node rejects the transaction (a revert during the node's
|
|
205
|
+
# pre-check is raised as {ContractRevertError})
|
|
206
|
+
# @example
|
|
207
|
+
# tx = wallet.send_transaction(to: "0x...", value: BlockGiven::Utils.parse_ether("0.5"))
|
|
208
|
+
# tx.hash # => "0x..."
|
|
209
|
+
# receipt = tx.wait!
|
|
61
210
|
def send_transaction(**params) = signed_transaction(**params).broadcast
|
|
62
211
|
|
|
63
|
-
# Resolves every missing field
|
|
212
|
+
# Resolves every missing transaction field from the client, without signing anything.
|
|
213
|
+
#
|
|
214
|
+
# This is the shared first step of {#signed_transaction}, {#sign_transaction} and {#send_transaction}.
|
|
215
|
+
# It normalises the inputs (checksummed `to`, Integer `value`, `0x`-prefixed `data`) and fills in the
|
|
216
|
+
# chain id, nonce, gas limit and fees from the node. When `gas_price:` is given the result describes a
|
|
217
|
+
# legacy (type 0) transaction and contains `:gas_price`; otherwise it contains `:max_fee_per_gas` and
|
|
218
|
+
# `:max_priority_fee_per_gas`.
|
|
219
|
+
#
|
|
220
|
+
# @param to [String, nil] recipient address (any case, checksummed in the result); nil for contract creation
|
|
221
|
+
# @param value [Integer, String, Float, BigDecimal, Rational, nil] amount of wei; Strings may be decimal or
|
|
222
|
+
# `0x` hex, non-Integer numerics must have no fractional part; nil is treated as 0
|
|
223
|
+
# @param data [String, nil] calldata as hex (with or without `0x`); nil or empty becomes `""`
|
|
224
|
+
# @param gas [Integer, nil] gas limit; defaults to `eth_estimateGas` (from {#address}, with `to`, `data` and
|
|
225
|
+
# `value`) multiplied by `BlockGiven.config.gas_multiplier` and rounded up
|
|
226
|
+
# @param nonce [Integer, nil] nonce; defaults to the wallet's pending transaction count ({#nonce})
|
|
227
|
+
# @param max_fee_per_gas [Integer, nil] EIP-1559 max fee per gas in wei; when either EIP-1559 field is
|
|
228
|
+
# omitted the missing one comes from {Client#estimate_fees_per_gas}. Ignored with `gas_price:`
|
|
229
|
+
# @param max_priority_fee_per_gas [Integer, nil] EIP-1559 priority fee per gas in wei; see `max_fee_per_gas`
|
|
230
|
+
# @param gas_price [Integer, nil] gas price in wei for a legacy (type 0) transaction; never estimated
|
|
231
|
+
# @param chain_id [Integer, nil] chain id; defaults to `client.chain.id`
|
|
232
|
+
# @param access_list [Array<Hash>, nil] EIP-2930 access list, kept as given (nil when omitted)
|
|
233
|
+
# @return [Hash{Symbol => Object}] the resolved parameters: `:from`, `:to`, `:value`, `:data`, `:chain_id`,
|
|
234
|
+
# `:nonce`, `:gas`, `:access_list`, plus either `:gas_price` or `:max_fee_per_gas` and
|
|
235
|
+
# `:max_priority_fee_per_gas`
|
|
236
|
+
# @raise [BlockGiven::InvalidArgumentError] when `value` cannot be coerced to an Integer amount of wei (for
|
|
237
|
+
# example a fractional Float), or when `to` is nil without an explicit `gas`
|
|
64
238
|
def prepare_transaction(to: nil, value: 0, data: nil, gas: nil, nonce: nil, max_fee_per_gas: nil,
|
|
65
239
|
max_priority_fee_per_gas: nil, gas_price: nil, chain_id: nil, access_list: nil)
|
|
66
240
|
to = Utils.checksum_address(to) if to
|
|
@@ -90,14 +264,34 @@ module BlockGiven
|
|
|
90
264
|
params
|
|
91
265
|
end
|
|
92
266
|
|
|
267
|
+
# Two wallets are equal when they control the same address, whatever their client.
|
|
268
|
+
#
|
|
269
|
+
# @param other [Object] the object to compare with
|
|
270
|
+
# @return [Boolean] true when `other` is a {Wallet} with the same {#address}
|
|
93
271
|
def ==(other) = other.is_a?(Wallet) && other.address == address
|
|
94
272
|
alias eql? ==
|
|
273
|
+
|
|
274
|
+
# Hash code consistent with {#==}, so wallets can be used as Hash keys and in Sets.
|
|
275
|
+
#
|
|
276
|
+
# @return [Integer] the hash of {#address}
|
|
95
277
|
def hash = address.hash
|
|
278
|
+
|
|
279
|
+
# @return [String] the checksummed {#address}
|
|
96
280
|
def to_s = address
|
|
281
|
+
|
|
282
|
+
# Debug representation showing only the address. The private key is never printed.
|
|
283
|
+
#
|
|
284
|
+
# @return [String] e.g. `#<BlockGiven::Wallet 0xAbC...>`
|
|
97
285
|
def inspect = "#<BlockGiven::Wallet #{address}>"
|
|
98
286
|
|
|
99
287
|
private
|
|
100
288
|
|
|
289
|
+
# r || s || v with v = 27 + recovery id, as `personal_sign` and `eth_signTypedData` return.
|
|
290
|
+
def sign_digest(digest)
|
|
291
|
+
r, s, recovery_id = Crypto::Secp256k1.sign(digest, @private_key)
|
|
292
|
+
Utils.bin_to_hex(Crypto::Secp256k1.int_to_bytes(r) + Crypto::Secp256k1.int_to_bytes(s) + (27 + recovery_id).chr)
|
|
293
|
+
end
|
|
294
|
+
|
|
101
295
|
def estimate_gas(to:, data:, value:)
|
|
102
296
|
raise InvalidArgumentError, "contract creation requires an explicit gas: value" if to.nil?
|
|
103
297
|
|
|
@@ -120,18 +314,5 @@ module BlockGiven
|
|
|
120
314
|
else raise InvalidArgumentError, "invalid value: #{value.inspect}"
|
|
121
315
|
end
|
|
122
316
|
end
|
|
123
|
-
|
|
124
|
-
def to_eth_tx_params(params)
|
|
125
|
-
base = {
|
|
126
|
-
chain_id: params[:chain_id], nonce: params[:nonce], gas_limit: params[:gas],
|
|
127
|
-
to: params[:to], value: params[:value], data: params[:data]
|
|
128
|
-
}
|
|
129
|
-
if params[:gas_price]
|
|
130
|
-
base.merge(gas_price: params[:gas_price])
|
|
131
|
-
else
|
|
132
|
-
base[:access_list] = params[:access_list] || []
|
|
133
|
-
base.merge(priority_fee: params[:max_priority_fee_per_gas], max_gas_fee: params[:max_fee_per_gas])
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
317
|
end
|
|
137
318
|
end
|
data/lib/block_given.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "eth"
|
|
4
|
-
|
|
5
3
|
require_relative "block_given/version"
|
|
6
4
|
require_relative "block_given/errors"
|
|
7
5
|
require_relative "block_given/utils"
|
|
6
|
+
require_relative "block_given/crypto"
|
|
7
|
+
require_relative "block_given/rlp"
|
|
8
8
|
require_relative "block_given/chain"
|
|
9
9
|
require_relative "block_given/configuration"
|
|
10
10
|
require_relative "block_given/connectors/base"
|
|
@@ -15,31 +15,74 @@ require_relative "block_given/normalizer"
|
|
|
15
15
|
require_relative "block_given/poller"
|
|
16
16
|
require_relative "block_given/receipt"
|
|
17
17
|
require_relative "block_given/transaction"
|
|
18
|
+
require_relative "block_given/transaction_envelope"
|
|
19
|
+
require_relative "block_given/transaction_envelope/fields"
|
|
18
20
|
require_relative "block_given/signed_transaction"
|
|
19
21
|
require_relative "block_given/client"
|
|
22
|
+
require_relative "block_given/eip712"
|
|
20
23
|
require_relative "block_given/wallet"
|
|
24
|
+
require_relative "block_given/abi/type"
|
|
25
|
+
require_relative "block_given/abi/codec"
|
|
26
|
+
require_relative "block_given/abi/codec/decoder"
|
|
21
27
|
require_relative "block_given/abi/parameter"
|
|
22
28
|
require_relative "block_given/abi/coder"
|
|
23
29
|
require_relative "block_given/abi/function"
|
|
24
30
|
require_relative "block_given/abi/event"
|
|
25
31
|
require_relative "block_given/abi/custom_error"
|
|
26
32
|
require_relative "block_given/abi/interface"
|
|
33
|
+
require_relative "block_given/abi/standards"
|
|
27
34
|
require_relative "block_given/event"
|
|
28
35
|
require_relative "block_given/contract"
|
|
29
36
|
require_relative "block_given/railtie" if defined?(Rails::Railtie)
|
|
30
37
|
|
|
31
38
|
# viem-inspired toolkit to read from and write to EVM smart contracts.
|
|
32
39
|
#
|
|
40
|
+
# The gem is organised around a handful of entry points:
|
|
41
|
+
#
|
|
42
|
+
# - {BlockGiven.configure} sets the global {Configuration}: JSON-RPC connector, chain, polling and fee
|
|
43
|
+
# defaults, and the directory application ABIs live in.
|
|
44
|
+
# - {BlockGiven.client} returns the default {Client}: thin wrappers over JSON-RPC (`eth_*`) that return Ruby
|
|
45
|
+
# values (Integer quantities in wei, checksummed addresses, snake_case symbol keys), plus fee estimation,
|
|
46
|
+
# receipt polling, log fetching and stoppable `watch_*` pollers.
|
|
47
|
+
# - {Contract} is a class-level DSL (`abi_file`, `address`, `chain`) that generates typed read, write and
|
|
48
|
+
# simulate methods and decoded events from an ABI.
|
|
49
|
+
# - {Wallet} holds a private key and signs transactions (EIP-1559 and legacy), personal messages (EIP-191)
|
|
50
|
+
# and typed data (EIP-712).
|
|
51
|
+
# - {Connectors} carry the transport: {Connectors::Alchemy} and {Connectors::Http} for real nodes,
|
|
52
|
+
# {Connectors::Stub} for tests.
|
|
53
|
+
# - {Utils}, {Normalizer} and {Chains} are stateless helpers (unit parsing, hex handling, keccak256,
|
|
54
|
+
# RPC payload normalisation and the catalogue of known networks).
|
|
55
|
+
#
|
|
56
|
+
# Every error raised by the gem inherits from {BlockGiven::Error}.
|
|
57
|
+
#
|
|
58
|
+
# @example Configure once, then use the default client
|
|
33
59
|
# BlockGiven.configure do |c|
|
|
34
60
|
# c.connector = BlockGiven::Connectors::Alchemy.new(api_key: ENV["ALCHEMY_API_KEY"])
|
|
35
61
|
# c.chain = :base
|
|
36
62
|
# end
|
|
63
|
+
#
|
|
64
|
+
# BlockGiven.client.block_number # => 12_345_678
|
|
37
65
|
module BlockGiven
|
|
38
66
|
class << self
|
|
67
|
+
# Global configuration shared by the default client and by contracts without an explicit chain or connector.
|
|
68
|
+
#
|
|
69
|
+
# @return [BlockGiven::Configuration] the memoised configuration, created with defaults on first access
|
|
39
70
|
def config
|
|
40
71
|
@config ||= Configuration.new
|
|
41
72
|
end
|
|
42
73
|
|
|
74
|
+
# Configure the gem and discard the memoised default client so the next {.client} call reflects the changes.
|
|
75
|
+
#
|
|
76
|
+
# @yield [config] the global configuration to mutate
|
|
77
|
+
# @yieldparam config [BlockGiven::Configuration]
|
|
78
|
+
# @return [BlockGiven::Configuration] the updated configuration
|
|
79
|
+
# @example
|
|
80
|
+
# BlockGiven.configure do |c|
|
|
81
|
+
# c.connector = BlockGiven::Connectors::Alchemy.new(api_key: ENV["ALCHEMY_API_KEY"])
|
|
82
|
+
# c.chain = :base_sepolia
|
|
83
|
+
# c.polling_interval = 1.0
|
|
84
|
+
# c.abi_path = Rails.root.join("abis")
|
|
85
|
+
# end
|
|
43
86
|
def configure
|
|
44
87
|
yield config
|
|
45
88
|
@client = nil
|
|
@@ -47,22 +90,42 @@ module BlockGiven
|
|
|
47
90
|
end
|
|
48
91
|
|
|
49
92
|
# Default client built from the global configuration.
|
|
93
|
+
#
|
|
94
|
+
# Memoised until {.configure} or {.reset!} runs, or until a client is assigned through {.client=}.
|
|
95
|
+
#
|
|
96
|
+
# @return [BlockGiven::Client]
|
|
97
|
+
# @raise [BlockGiven::ConfigurationError] when no connector or no chain is configured
|
|
98
|
+
# @example
|
|
99
|
+
# BlockGiven.client.get_balance("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") # => 1_000_000_000_000_000_000
|
|
50
100
|
def client
|
|
51
101
|
@client ||= Client.new
|
|
52
102
|
end
|
|
53
103
|
|
|
104
|
+
# Replace the default client, for instance with one built on a {Connectors::Stub} in tests.
|
|
105
|
+
#
|
|
106
|
+
# @param value [BlockGiven::Client, nil] nil makes the next {.client} call rebuild one from the configuration
|
|
107
|
+
# @return [BlockGiven::Client, nil]
|
|
54
108
|
attr_writer :client
|
|
55
109
|
|
|
56
|
-
# Running background watchers (see
|
|
110
|
+
# Running background watchers, oldest first (see {Watcher.find}, {Watcher.stop} and {Watcher.stop_all}).
|
|
111
|
+
#
|
|
112
|
+
# @return [Array<BlockGiven::Watcher>]
|
|
57
113
|
def watchers = Watcher.all
|
|
58
114
|
|
|
59
|
-
# Forget configuration and default client, stop every watcher (useful in tests).
|
|
115
|
+
# Forget configuration and default client, and stop every watcher (useful in tests).
|
|
116
|
+
#
|
|
117
|
+
# Watchers are asked to stop gracefully and joined for up to one second each before the state is cleared.
|
|
118
|
+
#
|
|
119
|
+
# @return [void]
|
|
60
120
|
def reset!
|
|
61
121
|
Watcher.stop_all(join: 1)
|
|
62
122
|
@config = nil
|
|
63
123
|
@client = nil
|
|
64
124
|
end
|
|
65
125
|
|
|
126
|
+
# Logger from the global configuration.
|
|
127
|
+
#
|
|
128
|
+
# @return [Logger] a `$stderr` logger at WARN level unless the application set one (see {Configuration#logger})
|
|
66
129
|
def logger = config.logger
|
|
67
130
|
end
|
|
68
131
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: block_given
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Remi Wallaere
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09
|
|
11
|
+
date: 2026-10-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -24,26 +24,6 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '3.1'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: eth
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0.5'
|
|
34
|
-
- - ">="
|
|
35
|
-
- !ruby/object:Gem::Version
|
|
36
|
-
version: 0.5.17
|
|
37
|
-
type: :runtime
|
|
38
|
-
prerelease: false
|
|
39
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
-
requirements:
|
|
41
|
-
- - "~>"
|
|
42
|
-
- !ruby/object:Gem::Version
|
|
43
|
-
version: '0.5'
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.5.17
|
|
47
27
|
- !ruby/object:Gem::Dependency
|
|
48
28
|
name: logger
|
|
49
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -63,6 +43,7 @@ description: |
|
|
|
63
43
|
inspired by viem: typed contract classes generated from an ABI, wallets that sign
|
|
64
44
|
EIP-1559 transactions, pluggable JSON-RPC connectors (Alchemy first) and
|
|
65
45
|
polling helpers for receipts, blocks and events.
|
|
46
|
+
Documentation: https://rubydoc.info/gems/block_given
|
|
66
47
|
email:
|
|
67
48
|
- remi@boleromusic.com
|
|
68
49
|
executables: []
|
|
@@ -73,12 +54,20 @@ files:
|
|
|
73
54
|
- LICENSE.txt
|
|
74
55
|
- README.md
|
|
75
56
|
- lib/block_given.rb
|
|
57
|
+
- lib/block_given/abi/codec.rb
|
|
58
|
+
- lib/block_given/abi/codec/decoder.rb
|
|
76
59
|
- lib/block_given/abi/coder.rb
|
|
77
60
|
- lib/block_given/abi/custom_error.rb
|
|
78
61
|
- lib/block_given/abi/event.rb
|
|
79
62
|
- lib/block_given/abi/function.rb
|
|
80
63
|
- lib/block_given/abi/interface.rb
|
|
81
64
|
- lib/block_given/abi/parameter.rb
|
|
65
|
+
- lib/block_given/abi/standards.rb
|
|
66
|
+
- lib/block_given/abi/standards/erc1155.rb
|
|
67
|
+
- lib/block_given/abi/standards/erc20.rb
|
|
68
|
+
- lib/block_given/abi/standards/erc4626.rb
|
|
69
|
+
- lib/block_given/abi/standards/erc721.rb
|
|
70
|
+
- lib/block_given/abi/type.rb
|
|
82
71
|
- lib/block_given/chain.rb
|
|
83
72
|
- lib/block_given/client.rb
|
|
84
73
|
- lib/block_given/configuration.rb
|
|
@@ -87,14 +76,21 @@ files:
|
|
|
87
76
|
- lib/block_given/connectors/http.rb
|
|
88
77
|
- lib/block_given/connectors/stub.rb
|
|
89
78
|
- lib/block_given/contract.rb
|
|
79
|
+
- lib/block_given/crypto.rb
|
|
80
|
+
- lib/block_given/crypto/keccak.rb
|
|
81
|
+
- lib/block_given/crypto/secp256k1.rb
|
|
82
|
+
- lib/block_given/eip712.rb
|
|
90
83
|
- lib/block_given/errors.rb
|
|
91
84
|
- lib/block_given/event.rb
|
|
92
85
|
- lib/block_given/normalizer.rb
|
|
93
86
|
- lib/block_given/poller.rb
|
|
94
87
|
- lib/block_given/railtie.rb
|
|
95
88
|
- lib/block_given/receipt.rb
|
|
89
|
+
- lib/block_given/rlp.rb
|
|
96
90
|
- lib/block_given/signed_transaction.rb
|
|
97
91
|
- lib/block_given/transaction.rb
|
|
92
|
+
- lib/block_given/transaction_envelope.rb
|
|
93
|
+
- lib/block_given/transaction_envelope/fields.rb
|
|
98
94
|
- lib/block_given/utils.rb
|
|
99
95
|
- lib/block_given/version.rb
|
|
100
96
|
- lib/block_given/wallet.rb
|
|
@@ -123,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
123
119
|
- !ruby/object:Gem::Version
|
|
124
120
|
version: '0'
|
|
125
121
|
requirements: []
|
|
126
|
-
rubygems_version: 3.
|
|
122
|
+
rubygems_version: 3.5.22
|
|
127
123
|
signing_key:
|
|
128
124
|
specification_version: 4
|
|
129
125
|
summary: viem-inspired Ruby client for EVM smart contracts.
|