erc20 0.0.19 → 0.0.20
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/lib/erc20/erc20.rb +1 -1
- data/lib/erc20/fake_wallet.rb +15 -17
- data/lib/erc20/wallet.rb +41 -60
- data/test/erc20/test_fake_wallet.rb +9 -5
- data/test/erc20/test_wallet.rb +8 -27
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 3c6a14d29c4065075aa4c50b8101930a95b3ed09eff93ef8e37f145da576a27e
         | 
| 4 | 
            +
              data.tar.gz: cd4ab161e890ba8cd4fdf4d60782c3d1550cadae6b6c74f4d2cec2967e3cda04
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: a73ba1ba539a7d25fe7cf2ecb022ddf5d7fbdcd61cf1653d987b9cf29b5337229a2756853bc538f2ae7385bf141945cea50a5198214e8da8d29088a34dea63c9
         | 
| 7 | 
            +
              data.tar.gz: 318f3270b986d11fb901af11181e457f89a53bc7ae89898a4c3231659edc1537933637285d51c9bac046f5be15b723d9709989def4532d0013ae872e50dadabd
         | 
    
        data/lib/erc20/erc20.rb
    CHANGED
    
    
    
        data/lib/erc20/fake_wallet.rb
    CHANGED
    
    | @@ -67,26 +67,24 @@ class ERC20::FakeWallet | |
| 67 67 | 
             
                b
         | 
| 68 68 | 
             
              end
         | 
| 69 69 |  | 
| 70 | 
            -
              # How much  | 
| 70 | 
            +
              # How much gas units is required in order to send ERC20 transaction.
         | 
| 71 71 | 
             
              #
         | 
| 72 72 | 
             
              # @param [String] from The departing address, in hex
         | 
| 73 73 | 
             
              # @param [String] to Arriving address, in hex
         | 
| 74 | 
            -
              # @ | 
| 75 | 
            -
               | 
| 76 | 
            -
             | 
| 77 | 
            -
                 | 
| 78 | 
            -
                 | 
| 74 | 
            +
              # @param [Integer] amount How many ERC20 tokens to send
         | 
| 75 | 
            +
              # @return [Integer] How many gas units required
         | 
| 76 | 
            +
              def gas_estimate(from, to, amount)
         | 
| 77 | 
            +
                gas = 66_000
         | 
| 78 | 
            +
                @history << { method: :gas_estimate, from:, to:, amount:, result: gas }
         | 
| 79 | 
            +
                gas
         | 
| 79 80 | 
             
              end
         | 
| 80 81 |  | 
| 81 | 
            -
              #  | 
| 82 | 
            -
              #
         | 
| 83 | 
            -
               | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
                g = 55_000
         | 
| 88 | 
            -
                @history << { method: :eth_gas_required, from:, to:, result: g }
         | 
| 89 | 
            -
                g
         | 
| 82 | 
            +
              # What is the price of gas unit in gwei?
         | 
| 83 | 
            +
              # @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
         | 
| 84 | 
            +
              def gas_price
         | 
| 85 | 
            +
                gwei = 55_555
         | 
| 86 | 
            +
                @history << { method: :gas_price, result: gwei }
         | 
| 87 | 
            +
                gwei
         | 
| 90 88 | 
             
              end
         | 
| 91 89 |  | 
| 92 90 | 
             
              # Send a single ERC20 payment from a private address to a public one.
         | 
| @@ -107,9 +105,9 @@ class ERC20::FakeWallet | |
| 107 105 | 
             
              # @param [String] address Public key, in hex
         | 
| 108 106 | 
             
              # @param [Integer] amount The amount of ETHs to send
         | 
| 109 107 | 
             
              # @return [String] Transaction hash
         | 
| 110 | 
            -
              def eth_pay(priv, address, amount,  | 
| 108 | 
            +
              def eth_pay(priv, address, amount, gas_price: nil)
         | 
| 111 109 | 
             
                hex = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
         | 
| 112 | 
            -
                @history << { method: :eth_pay, priv:, address:, amount:,  | 
| 110 | 
            +
                @history << { method: :eth_pay, priv:, address:, amount:, gas_price:, result: hex }
         | 
| 113 111 | 
             
                hex
         | 
| 114 112 | 
             
              end
         | 
| 115 113 |  | 
    
        data/lib/erc20/wallet.rb
    CHANGED
    
    | @@ -159,34 +159,33 @@ class ERC20::Wallet | |
| 159 159 | 
             
                b
         | 
| 160 160 | 
             
              end
         | 
| 161 161 |  | 
| 162 | 
            -
              # How much  | 
| 162 | 
            +
              # How much gas units is required in order to send ERC20 transaction.
         | 
| 163 163 | 
             
              #
         | 
| 164 164 | 
             
              # @param [String] from The departing address, in hex
         | 
| 165 | 
            -
              # @param [String] to Arriving address, in hex | 
| 166 | 
            -
              # @ | 
| 167 | 
            -
               | 
| 165 | 
            +
              # @param [String] to Arriving address, in hex
         | 
| 166 | 
            +
              # @param [Integer] amount How many ERC20 tokens to send
         | 
| 167 | 
            +
              # @return [Integer] How many gas units required
         | 
| 168 | 
            +
              def gas_estimate(from, to, amount)
         | 
| 168 169 | 
             
                raise 'Address can\'t be nil' unless from
         | 
| 169 170 | 
             
                raise 'Address must be a String' unless from.is_a?(String)
         | 
| 170 171 | 
             
                raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(from)
         | 
| 171 172 | 
             
                raise 'Address can\'t be nil' unless to
         | 
| 172 173 | 
             
                raise 'Address must be a String' unless to.is_a?(String)
         | 
| 173 174 | 
             
                raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(to)
         | 
| 174 | 
            -
                 | 
| 175 | 
            +
                raise 'Amount can\'t be nil' unless amount
         | 
| 176 | 
            +
                raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
         | 
| 177 | 
            +
                raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
         | 
| 178 | 
            +
                gas = jsonrpc.eth_estimateGas({ from:, to: @contract, data: to_pay_data(to, amount) }, 'latest').to_i(16)
         | 
| 179 | 
            +
                @log.debug("It would take #{gas} gas units to send #{amount} tokens from #{from} to #{to}")
         | 
| 180 | 
            +
                gas
         | 
| 175 181 | 
             
              end
         | 
| 176 182 |  | 
| 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)
         | 
| 183 | 
            +
              # What is the price of gas unit in gwei?
         | 
| 184 | 
            +
              # @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
         | 
| 185 | 
            +
              def gas_price
         | 
| 186 | 
            +
                gwei = jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
         | 
| 187 | 
            +
                @log.debug("The cost of one gas unit is #{gwei} gwei")
         | 
| 188 | 
            +
                gwei
         | 
| 190 189 | 
             
              end
         | 
| 191 190 |  | 
| 192 191 | 
             
              # Send a single ERC20 payment from a private address to a public one.
         | 
| @@ -194,10 +193,10 @@ class ERC20::Wallet | |
| 194 193 | 
             
              # @param [String] priv Private key, in hex
         | 
| 195 194 | 
             
              # @param [String] address Public key, in hex
         | 
| 196 195 | 
             
              # @param [Integer] amount The amount of ERC20 tokens to send
         | 
| 197 | 
            -
              # @param [Integer]  | 
| 198 | 
            -
              # @param [Integer]  | 
| 196 | 
            +
              # @param [Integer] limit How much gas you're ready to spend
         | 
| 197 | 
            +
              # @param [Integer] price How much gas you pay per computation unit
         | 
| 199 198 | 
             
              # @return [String] Transaction hash
         | 
| 200 | 
            -
              def pay(priv, address, amount,  | 
| 199 | 
            +
              def pay(priv, address, amount, limit: nil, price: gas_price)
         | 
| 201 200 | 
             
                raise 'Private key can\'t be nil' unless priv
         | 
| 202 201 | 
             
                raise 'Private key must be a String' unless priv.is_a?(String)
         | 
| 203 202 | 
             
                raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
         | 
| @@ -205,30 +204,29 @@ class ERC20::Wallet | |
| 205 204 | 
             
                raise 'Address must be a String' unless address.is_a?(String)
         | 
| 206 205 | 
             
                raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
         | 
| 207 206 | 
             
                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  | 
| 207 | 
            +
                raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
         | 
| 208 | 
            +
                raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
         | 
| 209 | 
            +
                if limit
         | 
| 210 | 
            +
                  raise 'Gas limit must be an Integer' unless limit.is_a?(Integer)
         | 
| 211 | 
            +
                  raise 'Gas limit must be a positive Integer' unless limit.positive?
         | 
| 213 212 | 
             
                end
         | 
| 214 | 
            -
                if  | 
| 215 | 
            -
                  raise 'Gas price must be an Integer' unless  | 
| 216 | 
            -
                  raise 'Gas price must be a positive Integer' unless  | 
| 213 | 
            +
                if price
         | 
| 214 | 
            +
                  raise 'Gas price must be an Integer' unless price.is_a?(Integer)
         | 
| 215 | 
            +
                  raise 'Gas price must be a positive Integer' unless price.positive?
         | 
| 217 216 | 
             
                end
         | 
| 218 217 | 
             
                key = Eth::Key.new(priv: priv)
         | 
| 219 218 | 
             
                from = key.address.to_s
         | 
| 220 | 
            -
                data = to_pay_data(address, amount)
         | 
| 221 219 | 
             
                tnx =
         | 
| 222 220 | 
             
                  @mutex.synchronize do
         | 
| 223 221 | 
             
                    nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
         | 
| 224 222 | 
             
                    tx = Eth::Tx.new(
         | 
| 225 223 | 
             
                      {
         | 
| 226 224 | 
             
                        nonce:,
         | 
| 227 | 
            -
                        gas_price:  | 
| 228 | 
            -
                        gas_limit:  | 
| 225 | 
            +
                        gas_price: price,
         | 
| 226 | 
            +
                        gas_limit: limit || gas_estimate(from, address, amount),
         | 
| 229 227 | 
             
                        to: @contract,
         | 
| 230 228 | 
             
                        value: 0,
         | 
| 231 | 
            -
                        data | 
| 229 | 
            +
                        data: to_pay_data(address, amount),
         | 
| 232 230 | 
             
                        chain_id: @chain
         | 
| 233 231 | 
             
                      }
         | 
| 234 232 | 
             
                    )
         | 
| @@ -245,10 +243,10 @@ class ERC20::Wallet | |
| 245 243 | 
             
              # @param [String] priv Private key, in hex
         | 
| 246 244 | 
             
              # @param [String] address Public key, in hex
         | 
| 247 245 | 
             
              # @param [Integer] amount The amount of ERC20 tokens to send
         | 
| 248 | 
            -
              # @param [Integer]  | 
| 249 | 
            -
              # @param [Integer]  | 
| 246 | 
            +
              # @param [Integer] limit How much gas you're ready to spend
         | 
| 247 | 
            +
              # @param [Integer] price How much gas you pay per computation unit
         | 
| 250 248 | 
             
              # @return [String] Transaction hash
         | 
| 251 | 
            -
              def eth_pay(priv, address, amount,  | 
| 249 | 
            +
              def eth_pay(priv, address, amount, price: gas_price)
         | 
| 252 250 | 
             
                raise 'Private key can\'t be nil' unless priv
         | 
| 253 251 | 
             
                raise 'Private key must be a String' unless priv.is_a?(String)
         | 
| 254 252 | 
             
                raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
         | 
| @@ -256,15 +254,11 @@ class ERC20::Wallet | |
| 256 254 | 
             
                raise 'Address must be a String' unless address.is_a?(String)
         | 
| 257 255 | 
             
                raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
         | 
| 258 256 | 
             
                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?
         | 
| 257 | 
            +
                raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
         | 
| 258 | 
            +
                raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
         | 
| 259 | 
            +
                if price
         | 
| 260 | 
            +
                  raise 'Gas price must be an Integer' unless price.is_a?(Integer)
         | 
| 261 | 
            +
                  raise 'Gas price must be a positive Integer' unless price.positive?
         | 
| 268 262 | 
             
                end
         | 
| 269 263 | 
             
                key = Eth::Key.new(priv: priv)
         | 
| 270 264 | 
             
                from = key.address.to_s
         | 
| @@ -275,8 +269,8 @@ class ERC20::Wallet | |
| 275 269 | 
             
                      {
         | 
| 276 270 | 
             
                        chain_id: @chain,
         | 
| 277 271 | 
             
                        nonce:,
         | 
| 278 | 
            -
                        gas_price:  | 
| 279 | 
            -
                        gas_limit:  | 
| 272 | 
            +
                        gas_price: price,
         | 
| 273 | 
            +
                        gas_limit: 22_000,
         | 
| 280 274 | 
             
                        to: address,
         | 
| 281 275 | 
             
                        value: amount
         | 
| 282 276 | 
             
                      }
         | 
| @@ -438,15 +432,6 @@ class ERC20::Wallet | |
| 438 432 | 
             
                JSONRPC::Client.new(url, connection:)
         | 
| 439 433 | 
             
              end
         | 
| 440 434 |  | 
| 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 435 | 
             
              def to_pay_data(address, amount)
         | 
| 451 436 | 
             
                func = 'a9059cbb' # transfer(address,uint256)
         | 
| 452 437 | 
             
                to_clean = address.downcase.sub(/^0x/, '')
         | 
| @@ -455,8 +440,4 @@ class ERC20::Wallet | |
| 455 440 | 
             
                amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
         | 
| 456 441 | 
             
                "0x#{func}#{to_padded}#{amt_padded}"
         | 
| 457 442 | 
             
              end
         | 
| 458 | 
            -
             | 
| 459 | 
            -
              def gas_best_price
         | 
| 460 | 
            -
                jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
         | 
| 461 | 
            -
              end
         | 
| 462 443 | 
             
            end
         | 
| @@ -38,14 +38,18 @@ require_relative '../test__helper' | |
| 38 38 | 
             
            # Copyright:: Copyright (c) 2025 Yegor Bugayenko
         | 
| 39 39 | 
             
            # License:: MIT
         | 
| 40 40 | 
             
            class TestFakeWallet < Minitest::Test
         | 
| 41 | 
            -
              def  | 
| 42 | 
            -
                b = ERC20::FakeWallet.new. | 
| 41 | 
            +
              def test_checks_gas_estimate
         | 
| 42 | 
            +
                b = ERC20::FakeWallet.new.gas_estimate(
         | 
| 43 | 
            +
                  '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
         | 
| 44 | 
            +
                  '0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1',
         | 
| 45 | 
            +
                  44_000
         | 
| 46 | 
            +
                )
         | 
| 43 47 | 
             
                refute_nil(b)
         | 
| 44 48 | 
             
              end
         | 
| 45 49 |  | 
| 46 | 
            -
              def  | 
| 47 | 
            -
                 | 
| 48 | 
            -
                refute_nil( | 
| 50 | 
            +
              def test_checks_gas_price
         | 
| 51 | 
            +
                gwei = ERC20::FakeWallet.new.gas_price
         | 
| 52 | 
            +
                refute_nil(gwei)
         | 
| 49 53 | 
             
              end
         | 
| 50 54 |  | 
| 51 55 | 
             
              def test_checks_fake_balance
         | 
    
        data/test/erc20/test_wallet.rb
    CHANGED
    
    | @@ -67,19 +67,13 @@ class TestWallet < Minitest::Test | |
| 67 67 | 
             
                assert_equal(0, b)
         | 
| 68 68 | 
             
              end
         | 
| 69 69 |  | 
| 70 | 
            -
              def  | 
| 71 | 
            -
                b = mainnet. | 
| 70 | 
            +
              def test_checks_gas_estimate_on_mainnet
         | 
| 71 | 
            +
                b = mainnet.gas_estimate(STABLE, Eth::Key.new(priv: JEFF).address.to_s, 44_000)
         | 
| 72 72 | 
             
                refute_nil(b)
         | 
| 73 73 | 
             
                assert_predicate(b, :positive?)
         | 
| 74 74 | 
             
                assert_operator(b, :>, 1000)
         | 
| 75 75 | 
             
              end
         | 
| 76 76 |  | 
| 77 | 
            -
              def test_checks_same_address_gas_required_on_mainnet
         | 
| 78 | 
            -
                b = mainnet.gas_required(STABLE, STABLE)
         | 
| 79 | 
            -
                refute_nil(b)
         | 
| 80 | 
            -
                assert_predicate(b, :positive?)
         | 
| 81 | 
            -
              end
         | 
| 82 | 
            -
             | 
| 83 77 | 
             
              def test_fails_with_invalid_infura_key
         | 
| 84 78 | 
             
                skip('Apparently, even with invalid key, Infura returns balance')
         | 
| 85 79 | 
             
                w = ERC20::Wallet.new(
         | 
| @@ -107,28 +101,15 @@ class TestWallet < Minitest::Test | |
| 107 101 | 
             
                assert_predicate(b, :zero?)
         | 
| 108 102 | 
             
              end
         | 
| 109 103 |  | 
| 110 | 
            -
              def  | 
| 111 | 
            -
                 | 
| 112 | 
            -
                  b1 = wallet.gas_required(
         | 
| 113 | 
            -
                    Eth::Key.new(priv: JEFF).address.to_s,
         | 
| 114 | 
            -
                    Eth::Key.new(priv: WALTER).address.to_s
         | 
| 115 | 
            -
                  )
         | 
| 116 | 
            -
                  assert_equal(21_597, b1)
         | 
| 117 | 
            -
                  b2 = wallet.gas_required(
         | 
| 118 | 
            -
                    Eth::Key.new(priv: JEFF).address.to_s,
         | 
| 119 | 
            -
                    Eth::Key.new(priv: JEFF).address.to_s
         | 
| 120 | 
            -
                  )
         | 
| 121 | 
            -
                  assert_equal(b1, b2)
         | 
| 122 | 
            -
                end
         | 
| 123 | 
            -
              end
         | 
| 124 | 
            -
             | 
| 125 | 
            -
              def test_checks_eth_gas_required_on_hardhat
         | 
| 104 | 
            +
              def test_checks_gas_estimate_on_hardhat
         | 
| 105 | 
            +
                sum = 100_000
         | 
| 126 106 | 
             
                on_hardhat do |wallet|
         | 
| 127 | 
            -
                   | 
| 107 | 
            +
                  b1 = wallet.gas_estimate(
         | 
| 128 108 | 
             
                    Eth::Key.new(priv: JEFF).address.to_s,
         | 
| 129 | 
            -
                    Eth::Key.new(priv: WALTER).address.to_s
         | 
| 109 | 
            +
                    Eth::Key.new(priv: WALTER).address.to_s,
         | 
| 110 | 
            +
                    sum
         | 
| 130 111 | 
             
                  )
         | 
| 131 | 
            -
                   | 
| 112 | 
            +
                  assert_operator(b1, :>, 21_000)
         | 
| 132 113 | 
             
                end
         | 
| 133 114 | 
             
              end
         | 
| 134 115 |  |