moac 0.4.8
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 +7 -0
- data/.gitignore +11 -0
- data/.gitmodules +3 -0
- data/.rspec +4 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +60 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/lib/moac.rb +75 -0
- data/lib/moac/address.rb +62 -0
- data/lib/moac/gas.rb +9 -0
- data/lib/moac/key.rb +71 -0
- data/lib/moac/key/decrypter.rb +113 -0
- data/lib/moac/key/encrypter.rb +128 -0
- data/lib/moac/open_ssl.rb +197 -0
- data/lib/moac/secp256k1.rb +7 -0
- data/lib/moac/sedes.rb +40 -0
- data/lib/moac/tx.rb +158 -0
- data/lib/moac/utils.rb +126 -0
- data/lib/moac/version.rb +3 -0
- data/moac.gemspec +32 -0
- metadata +194 -0
data/lib/moac/sedes.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Moac
|
2
|
+
module Sedes
|
3
|
+
include RLP::Sedes
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def address
|
8
|
+
Binary.fixed_length(20, allow_empty: true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def int20
|
12
|
+
BigEndianInt.new(20)
|
13
|
+
end
|
14
|
+
|
15
|
+
def int32
|
16
|
+
BigEndianInt.new(32)
|
17
|
+
end
|
18
|
+
|
19
|
+
def int256
|
20
|
+
BigEndianInt.new(256)
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash32
|
24
|
+
Binary.fixed_length(32)
|
25
|
+
end
|
26
|
+
|
27
|
+
def trie_root
|
28
|
+
Binary.fixed_length(32, allow_empty: true)
|
29
|
+
end
|
30
|
+
|
31
|
+
def big_endian_int
|
32
|
+
RLP::Sedes.big_endian_int
|
33
|
+
end
|
34
|
+
|
35
|
+
def binary
|
36
|
+
RLP::Sedes.binary
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/moac/tx.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
module Moac
|
2
|
+
class Tx
|
3
|
+
|
4
|
+
include RLP::Sedes::Serializable
|
5
|
+
extend Sedes
|
6
|
+
|
7
|
+
set_serializable_fields({
|
8
|
+
nonce: big_endian_int,
|
9
|
+
systemContract: big_endian_int,
|
10
|
+
gas_price: big_endian_int,
|
11
|
+
gas_limit: big_endian_int,
|
12
|
+
to: address,
|
13
|
+
value: big_endian_int,
|
14
|
+
data_bin: binary,
|
15
|
+
shardingFlag: big_endian_int,
|
16
|
+
via: binary,
|
17
|
+
v: big_endian_int,
|
18
|
+
r: big_endian_int,
|
19
|
+
s: big_endian_int
|
20
|
+
})
|
21
|
+
|
22
|
+
attr_writer :signature
|
23
|
+
|
24
|
+
def self.decode(data)
|
25
|
+
data = Utils.hex_to_bin(data) if data.match(/\A(?:0x)?\h+\Z/)
|
26
|
+
deserialize(RLP.decode data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(params)
|
30
|
+
|
31
|
+
fields = params.merge({v: Moac.chain_id, r: 0, s: 0})
|
32
|
+
fields[:to] = Utils.normalize_address(fields[:to])
|
33
|
+
|
34
|
+
if params[:data]
|
35
|
+
self.data = params.delete(:data)
|
36
|
+
fields[:data_bin] = data_bin
|
37
|
+
end
|
38
|
+
serializable_initialize fields
|
39
|
+
|
40
|
+
check_transaction_validity
|
41
|
+
end
|
42
|
+
|
43
|
+
def unsigned_encoded
|
44
|
+
RLP.encode(unsigned, sedes: sedes)
|
45
|
+
end
|
46
|
+
|
47
|
+
def signing_data
|
48
|
+
Utils.bin_to_prefixed_hex unsigned_encoded
|
49
|
+
end
|
50
|
+
|
51
|
+
def encoded
|
52
|
+
RLP.encode self
|
53
|
+
end
|
54
|
+
|
55
|
+
def hex
|
56
|
+
Utils.bin_to_prefixed_hex encoded
|
57
|
+
end
|
58
|
+
|
59
|
+
def sign(key)
|
60
|
+
self.signature = key.sign(unsigned_encoded)
|
61
|
+
vrs = Utils.v_r_s_for signature
|
62
|
+
self.v = vrs[0]
|
63
|
+
self.r = vrs[1]
|
64
|
+
self.s = vrs[2]
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_h
|
70
|
+
hash_keys.inject({}) do |hash, field|
|
71
|
+
hash[field] = send field
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def from
|
77
|
+
if signature
|
78
|
+
public_key = OpenSsl.recover_compact(signature_hash, signature)
|
79
|
+
Utils.public_key_to_address(public_key) if public_key
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def signature
|
84
|
+
return @signature if @signature
|
85
|
+
self.signature = [
|
86
|
+
Utils.int_to_base256(v),
|
87
|
+
Utils.zpad_int(r),
|
88
|
+
Utils.zpad_int(s),
|
89
|
+
].join if [v, r, s].all?
|
90
|
+
end
|
91
|
+
|
92
|
+
def hash
|
93
|
+
"0x#{Utils.bin_to_hex Utils.keccak256_rlp(self)}"
|
94
|
+
end
|
95
|
+
alias_method :id, :hash
|
96
|
+
|
97
|
+
def data_hex
|
98
|
+
Utils.bin_to_prefixed_hex data_bin
|
99
|
+
end
|
100
|
+
|
101
|
+
def data_hex=(hex)
|
102
|
+
self.data_bin = Utils.hex_to_bin(hex)
|
103
|
+
end
|
104
|
+
|
105
|
+
def data
|
106
|
+
Moac.tx_data_hex? ? data_hex : data_bin
|
107
|
+
end
|
108
|
+
|
109
|
+
def data=(string)
|
110
|
+
Moac.tx_data_hex? ? self.data_hex=(string) : self.data_bin=(string)
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def hash_keys
|
117
|
+
keys = self.class.serializable_fields.keys
|
118
|
+
keys.delete(:data_bin)
|
119
|
+
keys + [:data]
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_transaction_validity
|
123
|
+
if [gas_price, gas_limit, value, nonce].max > UINT_MAX
|
124
|
+
raise InvalidTransaction, "Values way too high!"
|
125
|
+
elsif gas_limit < intrinsic_gas_used
|
126
|
+
raise InvalidTransaction, "Gas limit too low"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def intrinsic_gas_used
|
131
|
+
num_zero_bytes = data_bin.count(BYTE_ZERO)
|
132
|
+
num_non_zero_bytes = data_bin.size - num_zero_bytes
|
133
|
+
|
134
|
+
Gas::GTXCOST +
|
135
|
+
Gas::GTXDATAZERO * num_zero_bytes +
|
136
|
+
Gas::GTXDATANONZERO * num_non_zero_bytes
|
137
|
+
end
|
138
|
+
|
139
|
+
def signature_hash
|
140
|
+
Utils.keccak256 unsigned_encoded
|
141
|
+
end
|
142
|
+
|
143
|
+
def unsigned
|
144
|
+
Tx.new to_h.merge(v: Moac.chain_id, r: 0, s: 0)
|
145
|
+
end
|
146
|
+
|
147
|
+
def sedes
|
148
|
+
if Moac.prevent_replays? && !(Moac.replayable_v? v)
|
149
|
+
self.class
|
150
|
+
else
|
151
|
+
UnsignedTx
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
UnsignedTx = Tx.exclude([:v, :r, :s])
|
158
|
+
end
|
data/lib/moac/utils.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
module Moac
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def normalize_address(address)
|
7
|
+
if address.nil? || address == ''
|
8
|
+
''
|
9
|
+
elsif address.size == 40
|
10
|
+
hex_to_bin address
|
11
|
+
elsif address.size == 42 && address[0..1] == '0x'
|
12
|
+
hex_to_bin address[2..-1]
|
13
|
+
else
|
14
|
+
address
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def bin_to_hex(string)
|
19
|
+
RLP::Utils.encode_hex string
|
20
|
+
end
|
21
|
+
|
22
|
+
def hex_to_bin(string)
|
23
|
+
RLP::Utils.decode_hex remove_hex_prefix(string)
|
24
|
+
end
|
25
|
+
|
26
|
+
def base256_to_int(str)
|
27
|
+
RLP::Sedes.big_endian_int.deserialize str.sub(/\A(\x00)+/, '')
|
28
|
+
end
|
29
|
+
|
30
|
+
def int_to_base256(int)
|
31
|
+
RLP::Sedes.big_endian_int.serialize int
|
32
|
+
end
|
33
|
+
|
34
|
+
def v_r_s_for(signature)
|
35
|
+
[
|
36
|
+
signature[0].bytes[0],
|
37
|
+
Utils.base256_to_int(signature[1..32]),
|
38
|
+
Utils.base256_to_int(signature[33..65]),
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
def prefix_hex(hex)
|
43
|
+
hex.match(/\A0x/) ? hex : "0x#{hex}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def remove_hex_prefix(s)
|
47
|
+
s[0,2] == '0x' ? s[2..-1] : s
|
48
|
+
end
|
49
|
+
|
50
|
+
def bin_to_prefixed_hex(binary)
|
51
|
+
prefix_hex bin_to_hex(binary)
|
52
|
+
end
|
53
|
+
|
54
|
+
def public_key_to_address(hex)
|
55
|
+
bytes = hex_to_bin(hex)
|
56
|
+
address_bytes = Utils.keccak256(bytes[1..-1])[-20..-1]
|
57
|
+
format_address bin_to_prefixed_hex(address_bytes)
|
58
|
+
end
|
59
|
+
|
60
|
+
def sha256(x)
|
61
|
+
Digest::SHA256.digest x
|
62
|
+
end
|
63
|
+
|
64
|
+
def keccak256(x)
|
65
|
+
Digest::SHA3.new(256).digest(x)
|
66
|
+
end
|
67
|
+
|
68
|
+
def keccak512(x)
|
69
|
+
Digest::SHA3.new(512).digest(x)
|
70
|
+
end
|
71
|
+
|
72
|
+
def keccak256_rlp(x)
|
73
|
+
keccak256 RLP.encode(x)
|
74
|
+
end
|
75
|
+
|
76
|
+
def ripemd160(x)
|
77
|
+
Digest::RMD160.digest x
|
78
|
+
end
|
79
|
+
|
80
|
+
def hash160(x)
|
81
|
+
ripemd160 sha256(x)
|
82
|
+
end
|
83
|
+
|
84
|
+
def zpad(x, l)
|
85
|
+
lpad x, BYTE_ZERO, l
|
86
|
+
end
|
87
|
+
|
88
|
+
def zunpad(x)
|
89
|
+
x.sub /\A\x00+/, ''
|
90
|
+
end
|
91
|
+
|
92
|
+
def zpad_int(n, l=32)
|
93
|
+
zpad encode_int(n), l
|
94
|
+
end
|
95
|
+
|
96
|
+
def zpad_hex(s, l=32)
|
97
|
+
zpad decode_hex(s), l
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_address?(address)
|
101
|
+
Address.new(address).valid?
|
102
|
+
end
|
103
|
+
|
104
|
+
def format_address(address)
|
105
|
+
Address.new(address).checksummed
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def lpad(x, symbol, l)
|
113
|
+
return x if x.size >= l
|
114
|
+
symbol * (l - x.size) + x
|
115
|
+
end
|
116
|
+
|
117
|
+
def encode_int(n)
|
118
|
+
unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
|
119
|
+
raise ArgumentError, "Integer invalid or out of range: #{n}"
|
120
|
+
end
|
121
|
+
|
122
|
+
int_to_base256 n
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
data/lib/moac/version.rb
ADDED
data/moac.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'moac/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "moac"
|
8
|
+
spec.version = Moac::VERSION
|
9
|
+
spec.authors = ["Kwin Song"]
|
10
|
+
spec.email = ["songkuoyin@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Simple API to sign Ethereum transactions.}
|
13
|
+
spec.description = %q{Library to build, parse, and sign Ethereum transactions.}
|
14
|
+
spec.homepage = "https://github.com/songky/ruby-moac"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'digest-sha3', '~> 1.1'
|
23
|
+
spec.add_dependency 'ffi', '~> 1.0'
|
24
|
+
spec.add_dependency 'money-tree', '~> 0.9'
|
25
|
+
spec.add_dependency 'rlp', '~> 0.7.3'
|
26
|
+
spec.add_dependency 'scrypt', '~> 3.0.5'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
29
|
+
spec.add_development_dependency 'pry', '~> 0.1'
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: moac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.8
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kwin Song
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: digest-sha3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ffi
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: money-tree
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.9'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rlp
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.7.3
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.7.3
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: scrypt
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.0.5
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.0.5
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.12'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.12'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.1'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.1'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '10.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '10.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.0'
|
139
|
+
description: Library to build, parse, and sign Ethereum transactions.
|
140
|
+
email:
|
141
|
+
- songkuoyin@gmail.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".gitignore"
|
147
|
+
- ".gitmodules"
|
148
|
+
- ".rspec"
|
149
|
+
- ".travis.yml"
|
150
|
+
- CHANGELOG.md
|
151
|
+
- Gemfile
|
152
|
+
- LICENSE.txt
|
153
|
+
- README.md
|
154
|
+
- Rakefile
|
155
|
+
- bin/console
|
156
|
+
- bin/setup
|
157
|
+
- lib/moac.rb
|
158
|
+
- lib/moac/address.rb
|
159
|
+
- lib/moac/gas.rb
|
160
|
+
- lib/moac/key.rb
|
161
|
+
- lib/moac/key/decrypter.rb
|
162
|
+
- lib/moac/key/encrypter.rb
|
163
|
+
- lib/moac/open_ssl.rb
|
164
|
+
- lib/moac/secp256k1.rb
|
165
|
+
- lib/moac/sedes.rb
|
166
|
+
- lib/moac/tx.rb
|
167
|
+
- lib/moac/utils.rb
|
168
|
+
- lib/moac/version.rb
|
169
|
+
- moac.gemspec
|
170
|
+
homepage: https://github.com/songky/ruby-moac
|
171
|
+
licenses:
|
172
|
+
- MIT
|
173
|
+
metadata: {}
|
174
|
+
post_install_message:
|
175
|
+
rdoc_options: []
|
176
|
+
require_paths:
|
177
|
+
- lib
|
178
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '0'
|
183
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
requirements: []
|
189
|
+
rubyforge_project:
|
190
|
+
rubygems_version: 2.6.14
|
191
|
+
signing_key:
|
192
|
+
specification_version: 4
|
193
|
+
summary: Simple API to sign Ethereum transactions.
|
194
|
+
test_files: []
|