Module: EthTool

Defined in:
lib/eth_tool.rb,
lib/eth_tool/version.rb

Constant Summary collapse

VERSION =
"0.1.2"
@@gas_limit =
60000
@@gas_price =
5_000_000_000

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.loggerObject

Returns the value of attribute logger.



32
33
34
# File 'lib/eth_tool.rb', line 32

def logger
  @logger
end

Class Method Details

.confirmations(txhash) ⇒ Object



192
193
194
195
196
197
198
199
200
# File 'lib/eth_tool.rb', line 192

def self.confirmations(txhash)
  tx = @@rpc.eth_get_transaction_by_hash(txhash)["result"]

  return 0 unless (tx and (not tx['blockNumber'].nil?))

  block_number = tx['blockNumber'].to_i(16)
  current_block_number = @@rpc.eth_block_number["result"].to_i(16)
  current_block_number - block_number
end

.confirmed?(txhash, number) ⇒ Boolean

Returns:

  • (Boolean)


180
181
182
183
184
185
186
187
188
189
190
# File 'lib/eth_tool.rb', line 180

def self.confirmed?(txhash, number)
  tx = @@rpc.eth_get_transaction_by_hash(txhash)["result"]

  return false unless (tx and (not tx['blockNumber'].nil?))

  block_number = tx['blockNumber'].to_i(16)
  current_block_number = @@rpc.eth_block_number["result"].to_i(16)
  confirmations = current_block_number - block_number

  confirmations >= number
end

.dec_to_hex(value) ⇒ Object

helper methods #########################################################



214
215
216
# File 'lib/eth_tool.rb', line 214

def self.dec_to_hex(value)
  '0x'+value.to_s(16)
end

.feeObject



27
28
29
# File 'lib/eth_tool.rb', line 27

def self.fee
  BigDecimal(@@gas_limit) * BigDecimal(@@gas_price) / 10**18.to_f
end

.fill_eth(private_key, to) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/eth_tool.rb', line 113

def self.fill_eth(private_key, to)
  # 目标地址上现有eth
  eth_balance = get_eth_balance(to)

  # 目标地址上需要填充满这么多eth
  amount = BigDecimal(@@gas_limit) * BigDecimal(@@gas_price) / 10**18

  return unless eth_balance < amount # 如果有足够多的eth,就直接返回
  
  transfer_eth(private_key, (amount-eth_balance), @@gas_limit, @@gas_price, to)
end

.gas_limit=(gas_limit) ⇒ Object



19
20
21
# File 'lib/eth_tool.rb', line 19

def self.gas_limit=(gas_limit)
  @@gas_limit = gas_limit
end

.gas_price=(gas_price) ⇒ Object



23
24
25
# File 'lib/eth_tool.rb', line 23

def self.gas_price=(gas_price)
  @@gas_price = gas_price
end

.generate_addresses_from_xprv(xprv, amount) ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/eth_tool.rb', line 150

def self.generate_addresses_from_xprv(xprv, amount)
  result = []
  wallet = Bip44::Wallet.from_xprv(xprv)
  amount.times do |i|
    sub_wallet = wallet.sub_wallet("m/#{i}")
    result << sub_wallet.ethereum_address
  end
  result
end

.generate_raw_transaction(private_key, value, data, gas_limit, gas_price = nil, to = nil, nonce = nil) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/eth_tool.rb', line 49

def self.generate_raw_transaction(private_key, value, data, gas_limit, gas_price = nil, to = nil, nonce = nil)
  key = ::Eth::Key.new priv: private_key
  address = key.address

  gas_price_in_dec = gas_price.nil? ? @@rpc.eth_gas_price["result"].to_i(16) : gas_price
  nonce = nonce.nil? ? @@rpc.eth_get_transaction_count(address, 'pending')["result"].to_i(16) : nonce

  args = {
    from: address,
    value: 0,
    data: '0x0',
    nonce: nonce,
    gas_limit: gas_limit,
    gas_price: gas_price_in_dec
  }

  args[:value] = (value * 10**18).to_i if value
  args[:data] = data if data
  args[:to] = to if to
  tx = Eth::Tx.new(args)
  tx.sign key
  tx.hex
end

.get_eth_balance(address) ⇒ Object



44
45
46
47
# File 'lib/eth_tool.rb', line 44

def self.get_eth_balance(address)
  result = @@rpc.eth_get_balance(address)['result'].to_i(16)
  BigDecimal(result) / BigDecimal(10**18)
end

.get_private_key_from_keystore(dir, address, passphrase) ⇒ Object



140
141
142
143
144
145
146
147
148
# File 'lib/eth_tool.rb', line 140

def self.get_private_key_from_keystore(dir, address, passphrase)
  path = File.join(dir, '*')
  Dir[path].each do |file|
    if file.end_with?(address[2..-1])
      key = ::Eth::Key.decrypt(File.read(file), passphrase)
      return key.private_key.to_hex
    end
  end
end

.get_private_key_from_xprv(xprv, index) ⇒ Object



160
161
162
163
164
# File 'lib/eth_tool.rb', line 160

def self.get_private_key_from_xprv(xprv, index)
  wallet = Bip44::Wallet.from_xprv(xprv)
  sub_wallet = wallet.sub_wallet("m/#{index}")
  return sub_wallet.private_key, sub_wallet.ethereum_address
end

.get_token_balance(address, token_contract_address, token_decimals) ⇒ Object



37
38
39
40
41
42
# File 'lib/eth_tool.rb', line 37

def self.get_token_balance(address, token_contract_address, token_decimals)
  data = '0x70a08231' + padding(address) # Ethereum::Function.calc_id('balanceOf(address)') # 70a08231

  result = @@rpc.eth_call({to: token_contract_address, data: data})['result'].to_i(16)
  BigDecimal(result) / BigDecimal(10**token_decimals)
end

.mined?(txhash) ⇒ Boolean

Returns:

  • (Boolean)


175
176
177
178
# File 'lib/eth_tool.rb', line 175

def self.mined?(txhash)
  tx = @@rpc.eth_get_transaction_by_hash(txhash)["result"]
  tx and (not tx['blockNumber'].nil?)
end

.padding(str) ⇒ Object



218
219
220
221
222
223
# File 'lib/eth_tool.rb', line 218

def self.padding(str)
  if str =~ /^0x[a-f0-9]*/
    str = str[2 .. str.length-1]
  end
  str.rjust(64, '0')
end

.rpcObject



15
16
17
# File 'lib/eth_tool.rb', line 15

def self.rpc
  @@rpc
end

.rpc=(url) ⇒ Object



11
12
13
# File 'lib/eth_tool.rb', line 11

def self.rpc=(url)
  @@rpc = Ethereum::HttpClient.new(url)
end

.sweep_eth(private_key, to) ⇒ Object



104
105
106
107
108
109
110
111
# File 'lib/eth_tool.rb', line 104

def self.sweep_eth(private_key, to)
  address = ::Eth::Key.new(priv: private_key).address
  eth_balance = get_eth_balance(address)
  keep = BigDecimal(@@gas_limit) * BigDecimal(@@gas_price) / 10**18
  amount = eth_balance - keep
  return if amount <= 0
  transfer_eth(private_key, amount, @@gas_limit, @@gas_price, to)
end

.sweep_token(private_key, token_contract_address, token_decimals, to) ⇒ Object



97
98
99
100
101
102
# File 'lib/eth_tool.rb', line 97

def self.sweep_token(private_key, token_contract_address, token_decimals, to)
  address = ::Eth::Key.new(priv: private_key).address
  token_balance = get_token_balance(address, token_contract_address, token_decimals)
  return if token_balance == 0
  transfer_token(private_key, token_contract_address, token_decimals, token_balance, @@gas_limit, @@gas_price, to)
end

.token_transfer_success?(txhash) ⇒ Boolean

Returns:

  • (Boolean)


202
203
204
205
206
207
208
209
210
# File 'lib/eth_tool.rb', line 202

def self.token_transfer_success?(txhash)
  tx = @@rpc.eth_get_transaction_by_hash(txhash)["result"]
  return false unless (tx and (not tx['blockNumber'].nil?))

  receipt = @@rpc.eth_get_transaction_receipt(txhash)["result"]
  return false unless receipt

  return false if (receipt['status'] && receipt['status'] == '0x0')
end

.transfer_eth(private_key, amount, gas_limit, gas_price, to) ⇒ Object



87
88
89
90
91
92
93
94
# File 'lib/eth_tool.rb', line 87

def self.transfer_eth(private_key, amount, gas_limit, gas_price, to)
  if amount < (1.0/10**18)
    logger.info "转账金额不能小于以太坊最小单位"
    return 
  end
  rawtx = generate_raw_transaction(private_key, amount, nil, gas_limit, gas_price, to)
  @@rpc.eth_send_raw_transaction(rawtx)
end

.transfer_token(private_key, token_contract_address, token_decimals, amount, gas_limit, gas_price, to) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/eth_tool.rb', line 73

def self.transfer_token(private_key, token_contract_address, token_decimals, amount, gas_limit, gas_price, to)
  if amount < (1.0/10**token_decimals)
    logger.info "转账金额不能小于精度最小单位(#{token_contract_address}, #{token_decimals})"
    return
  end

  # 生成raw transaction
  amount_in_wei = (amount * (10**token_decimals)).to_i
  data = '0xa9059cbb' + padding(to) + padding(dec_to_hex(amount_in_wei)) # Ethereum::Function.calc_id('transfer(address,uint256)') # a9059cbb
  rawtx = generate_raw_transaction(private_key, 0, data, gas_limit, gas_price, token_contract_address)
  
  @@rpc.eth_send_raw_transaction(rawtx)
end

.tx_replace(private_key, txhash, gas_price) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/eth_tool.rb', line 125

def self.tx_replace(private_key, txhash, gas_price)
  tx = @@rpc.eth_get_transaction_by_hash(txhash)["result"]
  return if tx.nil?

  value = tx["value"].to_i(16)
  data = tx["input"]
  gas_limit = tx["gas"].to_i(16)
  gas_price = gas_price
  to = tx["to"]
  nonce = tx["nonce"].to_i(16)

  rawtx = generate_raw_transaction(private_key, value, data, gas_limit, gas_price, to, nonce)
  @@rpc.eth_send_raw_transaction(rawtx)
end

.wait_for_miner(txhash, timeout: 1200, step: 20) ⇒ Object



166
167
168
169
170
171
172
173
# File 'lib/eth_tool.rb', line 166

def self.wait_for_miner(txhash, timeout: 1200, step: 20)
  start_time = Time.now
  loop do
    raise Timeout::Error if ((Time.now - start_time) > timeout)
    return true if mined?(txhash)
    sleep step
  end
end

.wputs(file, text) ⇒ Object



225
226
227
# File 'lib/eth_tool.rb', line 225

def self.wputs(file, text)
  File.open(file, 'a') { |f| f.puts(text) }
end