erc20 0.0.19 → 0.0.21
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/.0pdd.yml +2 -19
- data/.github/workflows/actionlint.yml +3 -20
- data/.github/workflows/codecov.yml +3 -19
- data/.github/workflows/copyrights.yml +3 -19
- data/.github/workflows/markdown-lint.yml +3 -19
- data/.github/workflows/pdd.yml +3 -19
- data/.github/workflows/rake.yml +3 -19
- data/.github/workflows/reuse.yml +19 -0
- data/.github/workflows/shellcheck.yml +3 -19
- data/.github/workflows/xcop.yml +3 -19
- data/.github/workflows/yamllint.yml +3 -19
- data/.rubocop.yml +3 -19
- data/.rultor.yml +3 -19
- data/.simplecov +2 -19
- data/.yamllint.yml +2 -19
- data/Gemfile +3 -20
- data/Gemfile.lock +7 -7
- data/LICENSES/MIT.txt +21 -0
- data/README.md +11 -1
- data/REUSE.toml +19 -0
- data/Rakefile +2 -19
- data/erc20.gemspec +2 -19
- data/hardhat/Dockerfile +2 -19
- data/hardhat/contracts/Foo.sol +2 -0
- data/hardhat/hardhat.config.js +2 -19
- data/hardhat/ignition/modules/Foo.ts +2 -19
- data/lib/erc20/erc20.rb +3 -20
- data/lib/erc20/fake_wallet.rb +41 -41
- data/lib/erc20/wallet.rb +43 -79
- data/lib/erc20.rb +2 -19
- data/test/erc20/test_fake_wallet.rb +31 -25
- data/test/erc20/test_wallet.rb +10 -46
- data/test/test__helper.rb +2 -19
- metadata +5 -2
data/hardhat/Dockerfile
CHANGED
@@ -1,22 +1,5 @@
|
|
1
|
-
# Copyright (c) 2025 Yegor Bugayenko
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in all
|
11
|
-
# copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
-
# SOFTWARE.
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
2
|
+
# SPDX-License-Identifier: MIT
|
20
3
|
|
21
4
|
FROM node:22
|
22
5
|
|
data/hardhat/contracts/Foo.sol
CHANGED
data/hardhat/hardhat.config.js
CHANGED
@@ -1,22 +1,5 @@
|
|
1
|
-
// Copyright (c) 2025 Yegor Bugayenko
|
2
|
-
//
|
3
|
-
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
// of this software and associated documentation files (the "Software"), to deal
|
5
|
-
// in the Software without restriction, including without limitation the rights
|
6
|
-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
// copies of the Software, and to permit persons to whom the Software is
|
8
|
-
// furnished to do so, subject to the following conditions:
|
9
|
-
//
|
10
|
-
// The above copyright notice and this permission notice shall be included in all
|
11
|
-
// copies or substantial portions of the Software.
|
12
|
-
//
|
13
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
-
// SOFTWARE.
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
2
|
+
// SPDX-License-Identifier: MIT
|
20
3
|
|
21
4
|
require("@nomicfoundation/hardhat-toolbox");
|
22
5
|
|
@@ -1,22 +1,5 @@
|
|
1
|
-
// Copyright (c) 2025 Yegor Bugayenko
|
2
|
-
//
|
3
|
-
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
// of this software and associated documentation files (the 'Software'), to deal
|
5
|
-
// in the Software without restriction, including without limitation the rights
|
6
|
-
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
// copies of the Software, and to permit persons to whom the Software is
|
8
|
-
// furnished to do so, subject to the following conditions:
|
9
|
-
//
|
10
|
-
// The above copyright notice and this permission notice shall be included in all
|
11
|
-
// copies or substantial portions of the Software.
|
12
|
-
//
|
13
|
-
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
-
// SOFTWARE.
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
2
|
+
// SPDX-License-Identifier: MIT
|
20
3
|
|
21
4
|
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
|
22
5
|
|
data/lib/erc20/erc20.rb
CHANGED
@@ -1,24 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2025 Yegor Bugayenko
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
# This module makes manipulations with Etherium ERC20 tokens
|
24
7
|
# as simple as they can be, if you have a provider of
|
@@ -42,5 +25,5 @@
|
|
42
25
|
# License:: MIT
|
43
26
|
module ERC20
|
44
27
|
# Current version of the gem (changed by the +.rultor.yml+ on every release)
|
45
|
-
VERSION = '0.0.
|
28
|
+
VERSION = '0.0.21'
|
46
29
|
end
|
data/lib/erc20/fake_wallet.rb
CHANGED
@@ -1,24 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2025 Yegor Bugayenko
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
require_relative 'erc20'
|
24
7
|
require_relative 'wallet'
|
@@ -29,6 +12,9 @@ require_relative 'wallet'
|
|
29
12
|
# Copyright:: Copyright (c) 2025 Yegor Bugayenko
|
30
13
|
# License:: MIT
|
31
14
|
class ERC20::FakeWallet
|
15
|
+
# Transaction hash always returned:
|
16
|
+
TXN_HASH = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
|
17
|
+
|
32
18
|
# Fakes:
|
33
19
|
attr_reader :host, :port, :ssl, :chain, :contract, :ws_path, :http_path
|
34
20
|
|
@@ -45,6 +31,22 @@ class ERC20::FakeWallet
|
|
45
31
|
@ws_path = '/'
|
46
32
|
@http_path = '/'
|
47
33
|
@history = []
|
34
|
+
@balances = {}
|
35
|
+
@eth_balances = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set balance, to be returned by the +balance()+.
|
39
|
+
# @param [String] address Public key, in hex, starting from '0x'
|
40
|
+
# @param [Integer] tokens How many tokens to put there
|
41
|
+
def set_balance(address, tokens)
|
42
|
+
@balances[address] = tokens
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set balance, to be returned by the +balance()+.
|
46
|
+
# @param [String] address Public key, in hex, starting from '0x'
|
47
|
+
# @param [Integer] wei How many wei to put there
|
48
|
+
def set_eth_balance(address, wei)
|
49
|
+
@eth_balances[address] = wei
|
48
50
|
end
|
49
51
|
|
50
52
|
# Get ERC20 balance of a public address.
|
@@ -52,7 +54,7 @@ class ERC20::FakeWallet
|
|
52
54
|
# @param [String] address Public key, in hex, starting from '0x'
|
53
55
|
# @return [Integer] Balance, in tokens
|
54
56
|
def balance(address)
|
55
|
-
b = 42_000_000
|
57
|
+
b = @balances[address] || 42_000_000
|
56
58
|
@history << { method: :balance, address:, result: b }
|
57
59
|
b
|
58
60
|
end
|
@@ -62,31 +64,29 @@ class ERC20::FakeWallet
|
|
62
64
|
# @param [String] address Public key, in hex, starting from '0x'
|
63
65
|
# @return [Integer] Balance, in tokens
|
64
66
|
def eth_balance(address)
|
65
|
-
b = 77_000_000_000_000_000
|
67
|
+
b = @eth_balances[address] || 77_000_000_000_000_000
|
66
68
|
@history << { method: :eth_balance, address:, result: b }
|
67
69
|
b
|
68
70
|
end
|
69
71
|
|
70
|
-
# How much
|
72
|
+
# How much gas units is required in order to send ERC20 transaction.
|
71
73
|
#
|
72
74
|
# @param [String] from The departing address, in hex
|
73
75
|
# @param [String] to Arriving address, in hex
|
74
|
-
# @
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
# @param [Integer] amount How many ERC20 tokens to send
|
77
|
+
# @return [Integer] How many gas units required
|
78
|
+
def gas_estimate(from, to, amount)
|
79
|
+
gas = 66_000
|
80
|
+
@history << { method: :gas_estimate, from:, to:, amount:, result: gas }
|
81
|
+
gas
|
79
82
|
end
|
80
83
|
|
81
|
-
#
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
g = 55_000
|
88
|
-
@history << { method: :eth_gas_required, from:, to:, result: g }
|
89
|
-
g
|
84
|
+
# What is the price of gas unit in gwei?
|
85
|
+
# @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
|
86
|
+
def gas_price
|
87
|
+
gwei = 55_555
|
88
|
+
@history << { method: :gas_price, result: gwei }
|
89
|
+
gwei
|
90
90
|
end
|
91
91
|
|
92
92
|
# Send a single ERC20 payment from a private address to a public one.
|
@@ -96,7 +96,7 @@ class ERC20::FakeWallet
|
|
96
96
|
# @param [Integer] _amount The amount of ERC20 tokens to send
|
97
97
|
# @return [String] Transaction hash
|
98
98
|
def pay(priv, address, amount, gas_limit: nil, gas_price: nil)
|
99
|
-
hex =
|
99
|
+
hex = TXN_HASH
|
100
100
|
@history << { method: :pay, priv:, address:, amount:, gas_limit:, gas_price:, result: hex }
|
101
101
|
hex
|
102
102
|
end
|
@@ -107,9 +107,9 @@ class ERC20::FakeWallet
|
|
107
107
|
# @param [String] address Public key, in hex
|
108
108
|
# @param [Integer] amount The amount of ETHs to send
|
109
109
|
# @return [String] Transaction hash
|
110
|
-
def eth_pay(priv, address, amount,
|
111
|
-
hex =
|
112
|
-
@history << { method: :eth_pay, priv:, address:, amount:,
|
110
|
+
def eth_pay(priv, address, amount, gas_price: nil)
|
111
|
+
hex = TXN_HASH
|
112
|
+
@history << { method: :eth_pay, priv:, address:, amount:, gas_price:, result: hex }
|
113
113
|
hex
|
114
114
|
end
|
115
115
|
|
@@ -134,7 +134,7 @@ class ERC20::FakeWallet
|
|
134
134
|
amount: 424_242,
|
135
135
|
from: '0xd5ff1bfcde7a03da61ad229d962c74f1ea2f16a5',
|
136
136
|
to: a,
|
137
|
-
txn:
|
137
|
+
txn: TXN_HASH
|
138
138
|
}
|
139
139
|
end
|
140
140
|
yield event
|
data/lib/erc20/wallet.rb
CHANGED
@@ -1,24 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2025 Yegor Bugayenko
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
require 'eth'
|
24
7
|
require 'eventmachine'
|
@@ -159,34 +142,33 @@ class ERC20::Wallet
|
|
159
142
|
b
|
160
143
|
end
|
161
144
|
|
162
|
-
# How much
|
145
|
+
# How much gas units is required in order to send ERC20 transaction.
|
163
146
|
#
|
164
147
|
# @param [String] from The departing address, in hex
|
165
|
-
# @param [String] to Arriving address, in hex
|
166
|
-
# @
|
167
|
-
|
148
|
+
# @param [String] to Arriving address, in hex
|
149
|
+
# @param [Integer] amount How many ERC20 tokens to send
|
150
|
+
# @return [Integer] How many gas units required
|
151
|
+
def gas_estimate(from, to, amount)
|
168
152
|
raise 'Address can\'t be nil' unless from
|
169
153
|
raise 'Address must be a String' unless from.is_a?(String)
|
170
154
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(from)
|
171
155
|
raise 'Address can\'t be nil' unless to
|
172
156
|
raise 'Address must be a String' unless to.is_a?(String)
|
173
157
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(to)
|
174
|
-
|
158
|
+
raise 'Amount can\'t be nil' unless amount
|
159
|
+
raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
|
160
|
+
raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
|
161
|
+
gas = jsonrpc.eth_estimateGas({ from:, to: @contract, data: to_pay_data(to, amount) }, 'latest').to_i(16)
|
162
|
+
@log.debug("It would take #{gas} gas units to send #{amount} tokens from #{from} to #{to}")
|
163
|
+
gas
|
175
164
|
end
|
176
165
|
|
177
|
-
#
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
raise 'Address can\'t be nil' unless from
|
184
|
-
raise 'Address must be a String' unless from.is_a?(String)
|
185
|
-
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(from)
|
186
|
-
raise 'Address can\'t be nil' unless to
|
187
|
-
raise 'Address must be a String' unless to.is_a?(String)
|
188
|
-
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(to)
|
189
|
-
gas_estimate(from, to)
|
166
|
+
# What is the price of gas unit in gwei?
|
167
|
+
# @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
|
168
|
+
def gas_price
|
169
|
+
gwei = jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
|
170
|
+
@log.debug("The cost of one gas unit is #{gwei} gwei")
|
171
|
+
gwei
|
190
172
|
end
|
191
173
|
|
192
174
|
# Send a single ERC20 payment from a private address to a public one.
|
@@ -194,10 +176,10 @@ class ERC20::Wallet
|
|
194
176
|
# @param [String] priv Private key, in hex
|
195
177
|
# @param [String] address Public key, in hex
|
196
178
|
# @param [Integer] amount The amount of ERC20 tokens to send
|
197
|
-
# @param [Integer]
|
198
|
-
# @param [Integer]
|
179
|
+
# @param [Integer] limit How much gas you're ready to spend
|
180
|
+
# @param [Integer] price How much gas you pay per computation unit
|
199
181
|
# @return [String] Transaction hash
|
200
|
-
def pay(priv, address, amount,
|
182
|
+
def pay(priv, address, amount, limit: nil, price: gas_price)
|
201
183
|
raise 'Private key can\'t be nil' unless priv
|
202
184
|
raise 'Private key must be a String' unless priv.is_a?(String)
|
203
185
|
raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
|
@@ -205,30 +187,29 @@ class ERC20::Wallet
|
|
205
187
|
raise 'Address must be a String' unless address.is_a?(String)
|
206
188
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
|
207
189
|
raise 'Amount can\'t be nil' unless amount
|
208
|
-
raise
|
209
|
-
raise
|
210
|
-
if
|
211
|
-
raise 'Gas limit must be an Integer' unless
|
212
|
-
raise 'Gas limit must be a positive Integer' unless
|
190
|
+
raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
|
191
|
+
raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
|
192
|
+
if limit
|
193
|
+
raise 'Gas limit must be an Integer' unless limit.is_a?(Integer)
|
194
|
+
raise 'Gas limit must be a positive Integer' unless limit.positive?
|
213
195
|
end
|
214
|
-
if
|
215
|
-
raise 'Gas price must be an Integer' unless
|
216
|
-
raise 'Gas price must be a positive Integer' unless
|
196
|
+
if price
|
197
|
+
raise 'Gas price must be an Integer' unless price.is_a?(Integer)
|
198
|
+
raise 'Gas price must be a positive Integer' unless price.positive?
|
217
199
|
end
|
218
200
|
key = Eth::Key.new(priv: priv)
|
219
201
|
from = key.address.to_s
|
220
|
-
data = to_pay_data(address, amount)
|
221
202
|
tnx =
|
222
203
|
@mutex.synchronize do
|
223
204
|
nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
|
224
205
|
tx = Eth::Tx.new(
|
225
206
|
{
|
226
207
|
nonce:,
|
227
|
-
gas_price:
|
228
|
-
gas_limit:
|
208
|
+
gas_price: price,
|
209
|
+
gas_limit: limit || gas_estimate(from, address, amount),
|
229
210
|
to: @contract,
|
230
211
|
value: 0,
|
231
|
-
data
|
212
|
+
data: to_pay_data(address, amount),
|
232
213
|
chain_id: @chain
|
233
214
|
}
|
234
215
|
)
|
@@ -245,10 +226,10 @@ class ERC20::Wallet
|
|
245
226
|
# @param [String] priv Private key, in hex
|
246
227
|
# @param [String] address Public key, in hex
|
247
228
|
# @param [Integer] amount The amount of ERC20 tokens to send
|
248
|
-
# @param [Integer]
|
249
|
-
# @param [Integer]
|
229
|
+
# @param [Integer] limit How much gas you're ready to spend
|
230
|
+
# @param [Integer] price How much gas you pay per computation unit
|
250
231
|
# @return [String] Transaction hash
|
251
|
-
def eth_pay(priv, address, amount,
|
232
|
+
def eth_pay(priv, address, amount, price: gas_price)
|
252
233
|
raise 'Private key can\'t be nil' unless priv
|
253
234
|
raise 'Private key must be a String' unless priv.is_a?(String)
|
254
235
|
raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
|
@@ -256,15 +237,11 @@ class ERC20::Wallet
|
|
256
237
|
raise 'Address must be a String' unless address.is_a?(String)
|
257
238
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
|
258
239
|
raise 'Amount can\'t be nil' unless amount
|
259
|
-
raise
|
260
|
-
raise
|
261
|
-
if
|
262
|
-
raise 'Gas
|
263
|
-
raise 'Gas
|
264
|
-
end
|
265
|
-
if gas_price
|
266
|
-
raise 'Gas price must be an Integer' unless gas_price.is_a?(Integer)
|
267
|
-
raise 'Gas price must be a positive Integer' unless gas_price.positive?
|
240
|
+
raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
|
241
|
+
raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
|
242
|
+
if price
|
243
|
+
raise 'Gas price must be an Integer' unless price.is_a?(Integer)
|
244
|
+
raise 'Gas price must be a positive Integer' unless price.positive?
|
268
245
|
end
|
269
246
|
key = Eth::Key.new(priv: priv)
|
270
247
|
from = key.address.to_s
|
@@ -275,8 +252,8 @@ class ERC20::Wallet
|
|
275
252
|
{
|
276
253
|
chain_id: @chain,
|
277
254
|
nonce:,
|
278
|
-
gas_price:
|
279
|
-
gas_limit:
|
255
|
+
gas_price: price,
|
256
|
+
gas_limit: 22_000,
|
280
257
|
to: address,
|
281
258
|
value: amount
|
282
259
|
}
|
@@ -438,15 +415,6 @@ class ERC20::Wallet
|
|
438
415
|
JSONRPC::Client.new(url, connection:)
|
439
416
|
end
|
440
417
|
|
441
|
-
# How much gas should be spent in order to send a transaction from one
|
442
|
-
# public address to another public address, possible carrying some data
|
443
|
-
# inside the transaction.
|
444
|
-
def gas_estimate(from, to, data = '')
|
445
|
-
gas = jsonrpc.eth_estimateGas({ from:, to:, data: }, 'latest').to_i(16)
|
446
|
-
@log.debug("Estimated gas is #{gas} ETH#{data.empty? ? '' : ', for ERC20 transfer'}")
|
447
|
-
gas
|
448
|
-
end
|
449
|
-
|
450
418
|
def to_pay_data(address, amount)
|
451
419
|
func = 'a9059cbb' # transfer(address,uint256)
|
452
420
|
to_clean = address.downcase.sub(/^0x/, '')
|
@@ -455,8 +423,4 @@ class ERC20::Wallet
|
|
455
423
|
amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
|
456
424
|
"0x#{func}#{to_padded}#{amt_padded}"
|
457
425
|
end
|
458
|
-
|
459
|
-
def gas_best_price
|
460
|
-
jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
|
461
|
-
end
|
462
426
|
end
|
data/lib/erc20.rb
CHANGED
@@ -1,24 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2025 Yegor Bugayenko
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
require_relative 'erc20/erc20'
|
24
7
|
require_relative 'erc20/wallet'
|
@@ -1,24 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2025 Yegor Bugayenko
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
require 'backtrace'
|
24
7
|
require 'donce'
|
@@ -38,14 +21,18 @@ require_relative '../test__helper'
|
|
38
21
|
# Copyright:: Copyright (c) 2025 Yegor Bugayenko
|
39
22
|
# License:: MIT
|
40
23
|
class TestFakeWallet < Minitest::Test
|
41
|
-
def
|
42
|
-
b = ERC20::FakeWallet.new.
|
24
|
+
def test_checks_gas_estimate
|
25
|
+
b = ERC20::FakeWallet.new.gas_estimate(
|
26
|
+
'0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
|
27
|
+
'0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1',
|
28
|
+
44_000
|
29
|
+
)
|
43
30
|
refute_nil(b)
|
44
31
|
end
|
45
32
|
|
46
|
-
def
|
47
|
-
|
48
|
-
refute_nil(
|
33
|
+
def test_checks_gas_price
|
34
|
+
gwei = ERC20::FakeWallet.new.gas_price
|
35
|
+
refute_nil(gwei)
|
49
36
|
end
|
50
37
|
|
51
38
|
def test_checks_fake_balance
|
@@ -53,17 +40,35 @@ class TestFakeWallet < Minitest::Test
|
|
53
40
|
a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
|
54
41
|
b = w.balance(a)
|
55
42
|
refute_nil(b)
|
43
|
+
assert_equal(42_000_000, b)
|
56
44
|
assert_includes(w.history, { method: :balance, result: b, address: a })
|
57
45
|
end
|
58
46
|
|
47
|
+
def test_checks_preset_fake_balance
|
48
|
+
w = ERC20::FakeWallet.new
|
49
|
+
a = '0xEB2fE8872A6f1eDb70a2632Effffffffeefbbfaa'
|
50
|
+
b = 55_555
|
51
|
+
w.set_balance(a, b)
|
52
|
+
assert_equal(b, w.balance(a))
|
53
|
+
end
|
54
|
+
|
59
55
|
def test_checks_fake_eth_balance
|
60
|
-
a = '
|
56
|
+
a = '0xEB2fE8872A6f1eDb70a2632Ebbffffff66fff77f'
|
61
57
|
w = ERC20::FakeWallet.new
|
62
58
|
b = w.eth_balance(a)
|
63
59
|
refute_nil(b)
|
60
|
+
assert_equal(77_000_000_000_000_000, b)
|
64
61
|
assert_includes(w.history, { method: :eth_balance, result: b, address: a })
|
65
62
|
end
|
66
63
|
|
64
|
+
def test_checks_preset_fake_eth_balance
|
65
|
+
a = '0xEB2fE8872A6f1eDb70a2632Eff88fff99fff33ff'
|
66
|
+
w = ERC20::FakeWallet.new
|
67
|
+
b = 33_333
|
68
|
+
w.set_eth_balance(a, b)
|
69
|
+
assert_equal(b, w.eth_balance(a))
|
70
|
+
end
|
71
|
+
|
67
72
|
def test_returns_host
|
68
73
|
assert_equal('example.com', ERC20::FakeWallet.new.host)
|
69
74
|
end
|
@@ -76,6 +81,7 @@ class TestFakeWallet < Minitest::Test
|
|
76
81
|
txn = w.pay(priv, address, amount)
|
77
82
|
assert_equal(66, txn.length)
|
78
83
|
assert_match(/^0x[a-f0-9]{64}$/, txn)
|
84
|
+
assert_equal(ERC20::FakeWallet::TXN_HASH, txn)
|
79
85
|
assert_includes(w.history, { method: :pay, result: txn, priv:, address:, amount:, gas_limit: nil, gas_price: nil })
|
80
86
|
end
|
81
87
|
|