erc20 0.0.17 → 0.0.19
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/.rubocop.yml +1 -1
- data/Gemfile.lock +4 -3
- data/README.md +13 -0
- data/lib/erc20/erc20.rb +1 -1
- data/lib/erc20/fake_wallet.rb +40 -23
- data/lib/erc20/wallet.rb +23 -9
- data/test/erc20/test_fake_wallet.rb +15 -12
- data/test/erc20/test_wallet.rb +14 -2
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a15afef1a8d918f6594d8ec52f92899c50559f38b8c5ca5fe1ce70cf5123c7e5
         | 
| 4 | 
            +
              data.tar.gz: 46ce6edcf81eb9a07b16e3b02cd2ca46f9238a07ef3d54566df67ea4260accc6
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9c2aaca43b303101022c8189159b08bc9193ed210675e8ed343e32849883a37ac78c6b7ad7fe0efd63eb2a91f3deda2c73280091e37d25e8f8ca8eada40f74e4
         | 
| 7 | 
            +
              data.tar.gz: 04d81a60b8ab53fd4de2559f52e76e3dbf63e8266ffc739a7b1ac046250a6420554e5beb40219efa9e4617e750827d860f64cf3a3071de76f28310143d0ff885
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -28,6 +28,7 @@ AllCops: | |
| 28 28 | 
             
              SuggestExtensions: false
         | 
| 29 29 | 
             
              NewCops: enable
         | 
| 30 30 | 
             
            plugins:
         | 
| 31 | 
            +
              - rubocop-rspec
         | 
| 31 32 | 
             
              - rubocop-performance
         | 
| 32 33 | 
             
              - rubocop-rake
         | 
| 33 34 | 
             
              - rubocop-minitest
         | 
| @@ -63,4 +64,3 @@ Security/MarshalLoad: | |
| 63 64 | 
             
              Enabled: false
         | 
| 64 65 | 
             
            Layout/MultilineAssignmentLayout:
         | 
| 65 66 | 
             
              Enabled: true
         | 
| 66 | 
            -
            require: []
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -223,11 +223,12 @@ GEM | |
| 223 223 | 
             
                  lint_roller (~> 1.1)
         | 
| 224 224 | 
             
                  rubocop (>= 1.72.1, < 2.0)
         | 
| 225 225 | 
             
                  rubocop-ast (>= 1.38.0, < 2.0)
         | 
| 226 | 
            -
                rubocop-rake (0.7. | 
| 226 | 
            +
                rubocop-rake (0.7.1)
         | 
| 227 227 | 
             
                  lint_roller (~> 1.1)
         | 
| 228 228 | 
             
                  rubocop (>= 1.72.1)
         | 
| 229 | 
            -
                rubocop-rspec (3. | 
| 230 | 
            -
                   | 
| 229 | 
            +
                rubocop-rspec (3.5.0)
         | 
| 230 | 
            +
                  lint_roller (~> 1.1)
         | 
| 231 | 
            +
                  rubocop (~> 1.72, >= 1.72.1)
         | 
| 231 232 | 
             
                ruby-progressbar (1.13.0)
         | 
| 232 233 | 
             
                rubyzip (2.4.1)
         | 
| 233 234 | 
             
                scrypt (3.0.8)
         | 
    
        data/README.md
    CHANGED
    
    | @@ -83,6 +83,19 @@ You can use [squid-proxy] [Docker] image to set up your own [HTTP proxy] server. | |
| 83 83 | 
             
            Of course, this library works with [Polygon], [Optimism],
         | 
| 84 84 | 
             
            and other forks of [Etherium].
         | 
| 85 85 |  | 
| 86 | 
            +
            ## How to use in tests
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            You can use `ERC20::FakeWallet` class that behaves exactly like
         | 
| 89 | 
            +
            `ERC20::Wallet`, but doesn't make any network connections to the provider.
         | 
| 90 | 
            +
            Also, it remembers all requests that were sent to it:
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            ```ruby
         | 
| 93 | 
            +
            require 'erc20'
         | 
| 94 | 
            +
            w = ERC20::FakeWallet.new
         | 
| 95 | 
            +
            w.pay(priv, address, 42_000)
         | 
| 96 | 
            +
            assert w.history.include?({ method: :pay, params: [priv, address, 42_000] })
         | 
| 97 | 
            +
            ```
         | 
| 98 | 
            +
             | 
| 86 99 | 
             
            ## How to contribute
         | 
| 87 100 |  | 
| 88 101 | 
             
            Read
         | 
    
        data/lib/erc20/erc20.rb
    CHANGED
    
    
    
        data/lib/erc20/fake_wallet.rb
    CHANGED
    
    | @@ -32,6 +32,9 @@ class ERC20::FakeWallet | |
| 32 32 | 
             
              # Fakes:
         | 
| 33 33 | 
             
              attr_reader :host, :port, :ssl, :chain, :contract, :ws_path, :http_path
         | 
| 34 34 |  | 
| 35 | 
            +
              # Full history of all method calls:
         | 
| 36 | 
            +
              attr_reader :history
         | 
| 37 | 
            +
             | 
| 35 38 | 
             
              # Ctor.
         | 
| 36 39 | 
             
              def initialize
         | 
| 37 40 | 
             
                @host = 'example.com'
         | 
| @@ -41,40 +44,49 @@ class ERC20::FakeWallet | |
| 41 44 | 
             
                @contract = ERC20::Wallet::USDT
         | 
| 42 45 | 
             
                @ws_path = '/'
         | 
| 43 46 | 
             
                @http_path = '/'
         | 
| 47 | 
            +
                @history = []
         | 
| 44 48 | 
             
              end
         | 
| 45 49 |  | 
| 46 50 | 
             
              # Get ERC20 balance of a public address.
         | 
| 47 51 | 
             
              #
         | 
| 48 | 
            -
              # @param [String]  | 
| 52 | 
            +
              # @param [String] address Public key, in hex, starting from '0x'
         | 
| 49 53 | 
             
              # @return [Integer] Balance, in tokens
         | 
| 50 | 
            -
              def balance( | 
| 51 | 
            -
                42_000_000
         | 
| 54 | 
            +
              def balance(address)
         | 
| 55 | 
            +
                b = 42_000_000
         | 
| 56 | 
            +
                @history << { method: :balance, address:, result: b }
         | 
| 57 | 
            +
                b
         | 
| 52 58 | 
             
              end
         | 
| 53 59 |  | 
| 54 60 | 
             
              # Get ETH balance of a public address.
         | 
| 55 61 | 
             
              #
         | 
| 56 | 
            -
              # @param [String]  | 
| 62 | 
            +
              # @param [String] address Public key, in hex, starting from '0x'
         | 
| 57 63 | 
             
              # @return [Integer] Balance, in tokens
         | 
| 58 | 
            -
              def eth_balance( | 
| 59 | 
            -
                 | 
| 64 | 
            +
              def eth_balance(address)
         | 
| 65 | 
            +
                b = 77_000_000_000_000_000
         | 
| 66 | 
            +
                @history << { method: :eth_balance, address:, result: b }
         | 
| 67 | 
            +
                b
         | 
| 60 68 | 
             
              end
         | 
| 61 69 |  | 
| 62 | 
            -
              # How much ETH gas is required in order to send this  | 
| 70 | 
            +
              # How much ETH gas is required in order to send this ERC20 transaction.
         | 
| 63 71 | 
             
              #
         | 
| 64 | 
            -
              # @param [String]  | 
| 65 | 
            -
              # @param [String]  | 
| 72 | 
            +
              # @param [String] from The departing address, in hex
         | 
| 73 | 
            +
              # @param [String] to Arriving address, in hex
         | 
| 66 74 | 
             
              # @return [Integer] How many ETH required
         | 
| 67 | 
            -
              def  | 
| 68 | 
            -
                 | 
| 75 | 
            +
              def gas_required(from, to = from)
         | 
| 76 | 
            +
                g = 66_000
         | 
| 77 | 
            +
                @history << { method: :gas_required, from:, to:, result: g }
         | 
| 78 | 
            +
                g
         | 
| 69 79 | 
             
              end
         | 
| 70 80 |  | 
| 71 | 
            -
              # How much ETH gas is required in order to send this  | 
| 81 | 
            +
              # How much ETH gas is required in order to send this ETH transaction.
         | 
| 72 82 | 
             
              #
         | 
| 73 | 
            -
              # @param [String]  | 
| 74 | 
            -
              # @param [String]  | 
| 83 | 
            +
              # @param [String] from The departing address, in hex
         | 
| 84 | 
            +
              # @param [String] to Arriving address, in hex (may be skipped)
         | 
| 75 85 | 
             
              # @return [Integer] How many ETH required
         | 
| 76 | 
            -
              def  | 
| 77 | 
            -
                 | 
| 86 | 
            +
              def eth_gas_required(from, to = from)
         | 
| 87 | 
            +
                g = 55_000
         | 
| 88 | 
            +
                @history << { method: :eth_gas_required, from:, to:, result: g }
         | 
| 89 | 
            +
                g
         | 
| 78 90 | 
             
              end
         | 
| 79 91 |  | 
| 80 92 | 
             
              # Send a single ERC20 payment from a private address to a public one.
         | 
| @@ -83,18 +95,22 @@ class ERC20::FakeWallet | |
| 83 95 | 
             
              # @param [String] _address Public key, in hex
         | 
| 84 96 | 
             
              # @param [Integer] _amount The amount of ERC20 tokens to send
         | 
| 85 97 | 
             
              # @return [String] Transaction hash
         | 
| 86 | 
            -
              def pay( | 
| 87 | 
            -
                '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
         | 
| 98 | 
            +
              def pay(priv, address, amount, gas_limit: nil, gas_price: nil)
         | 
| 99 | 
            +
                hex = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
         | 
| 100 | 
            +
                @history << { method: :pay, priv:, address:, amount:, gas_limit:, gas_price:, result: hex }
         | 
| 101 | 
            +
                hex
         | 
| 88 102 | 
             
              end
         | 
| 89 103 |  | 
| 90 104 | 
             
              # Send a single ETH payment from a private address to a public one.
         | 
| 91 105 | 
             
              #
         | 
| 92 | 
            -
              # @param [String]  | 
| 93 | 
            -
              # @param [String]  | 
| 94 | 
            -
              # @param [Integer]  | 
| 106 | 
            +
              # @param [String] priv Private key, in hex
         | 
| 107 | 
            +
              # @param [String] address Public key, in hex
         | 
| 108 | 
            +
              # @param [Integer] amount The amount of ETHs to send
         | 
| 95 109 | 
             
              # @return [String] Transaction hash
         | 
| 96 | 
            -
              def eth_pay( | 
| 97 | 
            -
                '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
         | 
| 110 | 
            +
              def eth_pay(priv, address, amount, gas_limit: nil, gas_price: nil)
         | 
| 111 | 
            +
                hex = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
         | 
| 112 | 
            +
                @history << { method: :eth_pay, priv:, address:, amount:, gas_limit:, gas_price:, result: hex }
         | 
| 113 | 
            +
                hex
         | 
| 98 114 | 
             
              end
         | 
| 99 115 |  | 
| 100 116 | 
             
              # Wait and accept.
         | 
| @@ -104,6 +120,7 @@ class ERC20::FakeWallet | |
| 104 120 | 
             
              # @param [Boolean] raw TRUE if you need to get JSON events as they arrive from Websockets
         | 
| 105 121 | 
             
              # @param [Integer] delay How many seconds to wait between +eth_subscribe+ calls
         | 
| 106 122 | 
             
              def accept(addresses, active = [], raw: false, delay: 1)
         | 
| 123 | 
            +
                @history << { method: :accept, addresses:, active:, raw:, delay: }
         | 
| 107 124 | 
             
                addresses.to_a.each { |a| active.append(a) }
         | 
| 108 125 | 
             
                loop do
         | 
| 109 126 | 
             
                  sleep(delay)
         | 
    
        data/lib/erc20/wallet.rb
    CHANGED
    
    | @@ -159,22 +159,34 @@ class ERC20::Wallet | |
| 159 159 | 
             
                b
         | 
| 160 160 | 
             
              end
         | 
| 161 161 |  | 
| 162 | 
            -
              # How much ETH gas is required in order to send  | 
| 162 | 
            +
              # How much ETH gas 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
         | 
| 165 | 
            +
              # @param [String] to Arriving address, in hex (it's OK to skip it)
         | 
| 166 166 | 
             
              # @return [Integer] How many ETH required
         | 
| 167 | 
            -
              def  | 
| 168 | 
            -
                 | 
| 167 | 
            +
              def gas_required(from, to = from)
         | 
| 168 | 
            +
                raise 'Address can\'t be nil' unless from
         | 
| 169 | 
            +
                raise 'Address must be a String' unless from.is_a?(String)
         | 
| 170 | 
            +
                raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(from)
         | 
| 171 | 
            +
                raise 'Address can\'t be nil' unless to
         | 
| 172 | 
            +
                raise 'Address must be a String' unless to.is_a?(String)
         | 
| 173 | 
            +
                raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(to)
         | 
| 174 | 
            +
                gas_estimate(from, to, to_pay_data(from, 100_000))
         | 
| 169 175 | 
             
              end
         | 
| 170 176 |  | 
| 171 | 
            -
              # How much ETH gas is required in order to send this  | 
| 177 | 
            +
              # How much ETH gas is required in order to send this ETH transaction.
         | 
| 172 178 | 
             
              #
         | 
| 173 179 | 
             
              # @param [String] from The departing address, in hex
         | 
| 174 | 
            -
              # @param [String] to Arriving address, in hex
         | 
| 180 | 
            +
              # @param [String] to Arriving address, in hex (it's OK to skip it)
         | 
| 175 181 | 
             
              # @return [Integer] How many ETH required
         | 
| 176 | 
            -
              def  | 
| 177 | 
            -
                 | 
| 182 | 
            +
              def eth_gas_required(from, to = from)
         | 
| 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)
         | 
| 178 190 | 
             
              end
         | 
| 179 191 |  | 
| 180 192 | 
             
              # Send a single ERC20 payment from a private address to a public one.
         | 
| @@ -430,7 +442,9 @@ class ERC20::Wallet | |
| 430 442 | 
             
              # public address to another public address, possible carrying some data
         | 
| 431 443 | 
             
              # inside the transaction.
         | 
| 432 444 | 
             
              def gas_estimate(from, to, data = '')
         | 
| 433 | 
            -
                jsonrpc.eth_estimateGas({ from:, to:, data: }, 'latest').to_i(16)
         | 
| 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
         | 
| 434 448 | 
             
              end
         | 
| 435 449 |  | 
| 436 450 | 
             
              def to_pay_data(address, amount)
         | 
| @@ -39,29 +39,29 @@ require_relative '../test__helper' | |
| 39 39 | 
             
            # License:: MIT
         | 
| 40 40 | 
             
            class TestFakeWallet < Minitest::Test
         | 
| 41 41 | 
             
              def test_checks_gas_required
         | 
| 42 | 
            -
                b = ERC20::FakeWallet.new.gas_required(
         | 
| 43 | 
            -
                  '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
         | 
| 44 | 
            -
                  '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
         | 
| 45 | 
            -
                )
         | 
| 42 | 
            +
                b = ERC20::FakeWallet.new.gas_required('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
         | 
| 46 43 | 
             
                refute_nil(b)
         | 
| 47 44 | 
             
              end
         | 
| 48 45 |  | 
| 49 46 | 
             
              def test_checks_eth_gas_required
         | 
| 50 | 
            -
                b = ERC20::FakeWallet.new.eth_gas_required(
         | 
| 51 | 
            -
                  '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
         | 
| 52 | 
            -
                  '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
         | 
| 53 | 
            -
                )
         | 
| 47 | 
            +
                b = ERC20::FakeWallet.new.eth_gas_required('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
         | 
| 54 48 | 
             
                refute_nil(b)
         | 
| 55 49 | 
             
              end
         | 
| 56 50 |  | 
| 57 51 | 
             
              def test_checks_fake_balance
         | 
| 58 | 
            -
                 | 
| 52 | 
            +
                w = ERC20::FakeWallet.new
         | 
| 53 | 
            +
                a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
         | 
| 54 | 
            +
                b = w.balance(a)
         | 
| 59 55 | 
             
                refute_nil(b)
         | 
| 56 | 
            +
                assert_includes(w.history, { method: :balance, result: b, address: a })
         | 
| 60 57 | 
             
              end
         | 
| 61 58 |  | 
| 62 59 | 
             
              def test_checks_fake_eth_balance
         | 
| 63 | 
            -
                 | 
| 60 | 
            +
                a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
         | 
| 61 | 
            +
                w = ERC20::FakeWallet.new
         | 
| 62 | 
            +
                b = w.eth_balance(a)
         | 
| 64 63 | 
             
                refute_nil(b)
         | 
| 64 | 
            +
                assert_includes(w.history, { method: :eth_balance, result: b, address: a })
         | 
| 65 65 | 
             
              end
         | 
| 66 66 |  | 
| 67 67 | 
             
              def test_returns_host
         | 
| @@ -70,10 +70,13 @@ class TestFakeWallet < Minitest::Test | |
| 70 70 |  | 
| 71 71 | 
             
              def test_pays_fake_money
         | 
| 72 72 | 
             
                priv = '81a9b2114d53731ecc84b261ef6c0387dde34d5907fe7b441240cc21d61bf80a'
         | 
| 73 | 
            -
                 | 
| 74 | 
            -
                 | 
| 73 | 
            +
                address = '0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1'
         | 
| 74 | 
            +
                w = ERC20::FakeWallet.new
         | 
| 75 | 
            +
                amount = 555_000
         | 
| 76 | 
            +
                txn = w.pay(priv, address, amount)
         | 
| 75 77 | 
             
                assert_equal(66, txn.length)
         | 
| 76 78 | 
             
                assert_match(/^0x[a-f0-9]{64}$/, txn)
         | 
| 79 | 
            +
                assert_includes(w.history, { method: :pay, result: txn, priv:, address:, amount:, gas_limit: nil, gas_price: nil })
         | 
| 77 80 | 
             
              end
         | 
| 78 81 |  | 
| 79 82 | 
             
              def test_pays_fake_eths
         | 
    
        data/test/erc20/test_wallet.rb
    CHANGED
    
    | @@ -71,6 +71,13 @@ class TestWallet < Minitest::Test | |
| 71 71 | 
             
                b = mainnet.gas_required(STABLE, Eth::Key.new(priv: JEFF).address.to_s)
         | 
| 72 72 | 
             
                refute_nil(b)
         | 
| 73 73 | 
             
                assert_predicate(b, :positive?)
         | 
| 74 | 
            +
                assert_operator(b, :>, 1000)
         | 
| 75 | 
            +
              end
         | 
| 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?)
         | 
| 74 81 | 
             
              end
         | 
| 75 82 |  | 
| 76 83 | 
             
              def test_fails_with_invalid_infura_key
         | 
| @@ -102,11 +109,16 @@ class TestWallet < Minitest::Test | |
| 102 109 |  | 
| 103 110 | 
             
              def test_checks_gas_required_on_hardhat
         | 
| 104 111 | 
             
                on_hardhat do |wallet|
         | 
| 105 | 
            -
                   | 
| 112 | 
            +
                  b1 = wallet.gas_required(
         | 
| 106 113 | 
             
                    Eth::Key.new(priv: JEFF).address.to_s,
         | 
| 107 114 | 
             
                    Eth::Key.new(priv: WALTER).address.to_s
         | 
| 108 115 | 
             
                  )
         | 
| 109 | 
            -
                  assert_equal(21_597,  | 
| 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)
         | 
| 110 122 | 
             
                end
         | 
| 111 123 | 
             
              end
         | 
| 112 124 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: erc20
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0. | 
| 4 | 
            +
              version: 0.0.19
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Yegor Bugayenko
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2025-02- | 
| 11 | 
            +
            date: 2025-02-17 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: eth
         |