xrpl-ruby 0.2.4 → 0.6.0
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/CHANGELOG.md +31 -0
- data/LICENSE +21 -0
- data/README.md +94 -0
- data/lib/address-codec/address_codec.rb +21 -4
- data/lib/address-codec/codec.rb +15 -2
- data/lib/address-codec/xrp_codec.rb +29 -2
- data/lib/binary-codec/binary_codec.rb +47 -21
- data/lib/binary-codec/enums/definitions.json +592 -1
- data/lib/binary-codec/enums/definitions.rb +23 -9
- data/lib/binary-codec/enums/fields.rb +3 -1
- data/lib/binary-codec/serdes/binary_parser.rb +44 -10
- data/lib/binary-codec/serdes/binary_serializer.rb +29 -6
- data/lib/binary-codec/serdes/bytes_list.rb +12 -1
- data/lib/binary-codec/types/account_id.rb +18 -37
- data/lib/binary-codec/types/amount.rb +123 -77
- data/lib/binary-codec/types/blob.rb +14 -5
- data/lib/binary-codec/types/currency.rb +15 -4
- data/lib/binary-codec/types/hash.rb +37 -36
- data/lib/binary-codec/types/issue.rb +47 -0
- data/lib/binary-codec/types/path_set.rb +93 -0
- data/lib/binary-codec/types/serialized_type.rb +52 -28
- data/lib/binary-codec/types/st_array.rb +106 -0
- data/lib/binary-codec/types/st_object.rb +150 -14
- data/lib/binary-codec/types/uint.rb +166 -3
- data/lib/binary-codec/types/vector256.rb +53 -0
- data/lib/binary-codec/types/xchain_bridge.rb +47 -0
- data/lib/binary-codec/utilities.rb +18 -0
- data/lib/core/base_58_xrp.rb +2 -0
- data/lib/core/base_x.rb +10 -0
- data/lib/core/core.rb +44 -6
- data/lib/core/utilities.rb +38 -0
- data/lib/key-pairs/ed25519.rb +69 -0
- data/lib/key-pairs/key_pairs.rb +92 -0
- data/lib/key-pairs/secp256k1.rb +169 -0
- data/lib/wallet/wallet.rb +179 -0
- data/lib/xrpl/client.rb +498 -0
- data/lib/xrpl/faucet.rb +138 -0
- data/lib/xrpl/version.rb +5 -0
- data/lib/xrpl-ruby.rb +30 -1
- metadata +80 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'json'
|
|
3
|
+
require 'digest'
|
|
3
4
|
|
|
4
5
|
module BinaryCodec
|
|
5
6
|
|
|
@@ -30,10 +31,12 @@ module BinaryCodec
|
|
|
30
31
|
is_signing_field: field[1]['isSigningField'],
|
|
31
32
|
type: field[1]['type']
|
|
32
33
|
)
|
|
33
|
-
|
|
34
|
+
type_ordinal = @type_ordinals[field_info.type]
|
|
35
|
+
field_header = FieldHeader.new(type: type_ordinal, nth: field_info.nth)
|
|
34
36
|
|
|
35
37
|
@field_info_map[field_name] = field_info
|
|
36
|
-
|
|
38
|
+
key = (type_ordinal << 16) | field_info.nth
|
|
39
|
+
@field_id_name_map[key] = field_name
|
|
37
40
|
@field_header_map[field_name] = field_header
|
|
38
41
|
end
|
|
39
42
|
|
|
@@ -44,19 +47,30 @@ module BinaryCodec
|
|
|
44
47
|
|
|
45
48
|
end
|
|
46
49
|
|
|
50
|
+
# Returns the singleton instance of the Definitions class.
|
|
51
|
+
# @return [Definitions] The singleton instance.
|
|
47
52
|
def self.instance
|
|
48
53
|
@@instance ||= new
|
|
49
54
|
end
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
# Returns the field header for a given field name.
|
|
57
|
+
# @param field_name [String] The name of the field.
|
|
58
|
+
# @return [FieldHeader] The field header.
|
|
59
|
+
def get_field_header_from_name(field_name)
|
|
52
60
|
@field_header_map[field_name]
|
|
53
61
|
end
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
# Returns the field name for a given field header.
|
|
64
|
+
# @param field_header [FieldHeader] The field header.
|
|
65
|
+
# @return [String] The name of the field.
|
|
66
|
+
def get_field_name_from_header(field_header)
|
|
67
|
+
@field_id_name_map[(field_header.type << 16) | field_header.nth]
|
|
68
|
+
end
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
# Returns a FieldInstance for a given field name.
|
|
71
|
+
# @param field_name [String] The name of the field.
|
|
72
|
+
# @return [FieldInstance] The field instance.
|
|
73
|
+
def get_field_instance(field_name)
|
|
60
74
|
field_info = @field_info_map[field_name]
|
|
61
75
|
field_header = get_field_header_from_name(field_name)
|
|
62
76
|
|
|
@@ -66,10 +80,10 @@ module BinaryCodec
|
|
|
66
80
|
is_serialized: field_info.is_serialized,
|
|
67
81
|
is_signing_field: field_info.is_signing_field,
|
|
68
82
|
type: field_info.type,
|
|
69
|
-
ordinal: (
|
|
83
|
+
ordinal: (@type_ordinals[field_info.type] << 16) | field_info.nth,
|
|
70
84
|
name: field_name,
|
|
71
85
|
header: field_header,
|
|
72
|
-
associated_type: field_info.type
|
|
86
|
+
associated_type: SerializedType.get_type_by_name(field_info.type)
|
|
73
87
|
)
|
|
74
88
|
end
|
|
75
89
|
|
|
@@ -11,6 +11,8 @@ module BinaryCodec
|
|
|
11
11
|
@nth = nth
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
# Converts the field header to a byte array.
|
|
15
|
+
# @return [Array<Integer>] The byte array.
|
|
14
16
|
def to_bytes
|
|
15
17
|
header = []
|
|
16
18
|
if type < 16
|
|
@@ -20,7 +22,7 @@ module BinaryCodec
|
|
|
20
22
|
header.push(type << 4, nth)
|
|
21
23
|
end
|
|
22
24
|
elsif nth < 16
|
|
23
|
-
header.push(nth,type)
|
|
25
|
+
header.push(nth, type)
|
|
24
26
|
else
|
|
25
27
|
header.push(0, type, nth)
|
|
26
28
|
end
|
|
@@ -11,6 +11,8 @@ module BinaryCodec
|
|
|
11
11
|
@definitions = Definitions.instance
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
# Returns the first byte in the stream without consuming it.
|
|
15
|
+
# @return [Integer] The first byte.
|
|
14
16
|
def peek
|
|
15
17
|
if @bytes.empty?
|
|
16
18
|
raise StandardError.new
|
|
@@ -18,6 +20,8 @@ module BinaryCodec
|
|
|
18
20
|
@bytes[0]
|
|
19
21
|
end
|
|
20
22
|
|
|
23
|
+
# Consumes n bytes from the stream.
|
|
24
|
+
# @param n [Integer] The number of bytes to skip.
|
|
21
25
|
def skip(n)
|
|
22
26
|
if n > @bytes.length
|
|
23
27
|
raise StandardError.new
|
|
@@ -25,6 +29,9 @@ module BinaryCodec
|
|
|
25
29
|
@bytes = @bytes[n..-1]
|
|
26
30
|
end
|
|
27
31
|
|
|
32
|
+
# Reads n bytes from the stream.
|
|
33
|
+
# @param n [Integer] The number of bytes to read.
|
|
34
|
+
# @return [Array<Integer>] The read bytes.
|
|
28
35
|
def read(n)
|
|
29
36
|
if n > @bytes.length
|
|
30
37
|
raise StandardError.new('End of byte stream reached')
|
|
@@ -35,6 +42,9 @@ module BinaryCodec
|
|
|
35
42
|
slice
|
|
36
43
|
end
|
|
37
44
|
|
|
45
|
+
# Reads n bytes and converts them to an unsigned integer.
|
|
46
|
+
# @param n [Integer] The number of bytes to read (1-4).
|
|
47
|
+
# @return [Integer] The resulting integer.
|
|
38
48
|
def read_uint_n(n)
|
|
39
49
|
if n <= 0 || n > 4
|
|
40
50
|
raise StandardError.new('invalid n')
|
|
@@ -42,31 +52,46 @@ module BinaryCodec
|
|
|
42
52
|
read(n).reduce(0) { |a, b| (a << 8) | b }
|
|
43
53
|
end
|
|
44
54
|
|
|
55
|
+
# Reads a 1-byte unsigned integer.
|
|
56
|
+
# @return [Integer] The 8-bit integer.
|
|
45
57
|
def read_uint8
|
|
46
58
|
read_uint_n(1)
|
|
47
59
|
end
|
|
48
60
|
|
|
61
|
+
# Reads a 2-byte unsigned integer.
|
|
62
|
+
# @return [Integer] The 16-bit integer.
|
|
49
63
|
def read_uint16
|
|
50
64
|
read_uint_n(2)
|
|
51
65
|
end
|
|
52
66
|
|
|
67
|
+
# Reads a 4-byte unsigned integer.
|
|
68
|
+
# @return [Integer] The 32-bit integer.
|
|
53
69
|
def read_uint32
|
|
54
70
|
read_uint_n(4)
|
|
55
71
|
end
|
|
56
72
|
|
|
73
|
+
# Returns the number of bytes remaining in the stream.
|
|
74
|
+
# @return [Integer] The remaining size.
|
|
57
75
|
def size
|
|
58
76
|
@bytes.length
|
|
59
77
|
end
|
|
60
78
|
|
|
79
|
+
# Checks if the end of the stream has been reached.
|
|
80
|
+
# @param custom_end [Integer, nil] Optional offset to check against.
|
|
81
|
+
# @return [Boolean] True if at the end, false otherwise.
|
|
61
82
|
def end?(custom_end = nil)
|
|
62
83
|
length = @bytes.length
|
|
63
84
|
length == 0 || (!custom_end.nil? && length <= custom_end)
|
|
64
85
|
end
|
|
65
86
|
|
|
87
|
+
# Reads variable length data from the stream.
|
|
88
|
+
# @return [Array<Integer>] The read bytes.
|
|
66
89
|
def read_variable_length
|
|
67
90
|
read(read_variable_length_length)
|
|
68
91
|
end
|
|
69
92
|
|
|
93
|
+
# Reads the length of a variable length data segment.
|
|
94
|
+
# @return [Integer] The length.
|
|
70
95
|
def read_variable_length_length
|
|
71
96
|
b1 = read_uint8
|
|
72
97
|
if b1 <= 192
|
|
@@ -83,43 +108,52 @@ module BinaryCodec
|
|
|
83
108
|
end
|
|
84
109
|
end
|
|
85
110
|
|
|
111
|
+
# Reads a field header from the stream.
|
|
112
|
+
# @return [FieldHeader] The field header.
|
|
86
113
|
def read_field_header
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
114
|
+
first_byte = read_uint8
|
|
115
|
+
type = first_byte >> 4
|
|
116
|
+
nth = first_byte & 15
|
|
90
117
|
|
|
91
118
|
if type == 0
|
|
92
119
|
type = read_uint8
|
|
93
|
-
if type == 0 || type < 16
|
|
94
|
-
raise StandardError.new("Cannot read FieldOrdinal, type_code #{type} out of range")
|
|
95
|
-
end
|
|
96
120
|
end
|
|
97
121
|
|
|
98
122
|
if nth == 0
|
|
99
123
|
nth = read_uint8
|
|
100
|
-
if nth == 0 || nth < 16
|
|
101
|
-
raise StandardError.new("Cannot read FieldOrdinal, field_code #{nth} out of range")
|
|
102
|
-
end
|
|
103
124
|
end
|
|
104
125
|
|
|
105
|
-
FieldHeader.new(type: type, nth: nth)
|
|
126
|
+
FieldHeader.new(type: type, nth: nth)
|
|
106
127
|
end
|
|
107
128
|
|
|
129
|
+
# Reads a field instance from the stream.
|
|
130
|
+
# @return [FieldInstance] The field instance.
|
|
108
131
|
def read_field
|
|
109
132
|
field_header = read_field_header
|
|
110
133
|
field_name = @definitions.get_field_name_from_header(field_header)
|
|
134
|
+
|
|
135
|
+
raise "Unknown field for header: type=#{field_header.type}, nth=#{field_header.nth}" if field_name.nil?
|
|
111
136
|
|
|
112
137
|
@definitions.get_field_instance(field_name)
|
|
113
138
|
end
|
|
114
139
|
|
|
140
|
+
# Reads a value of the specified type from the stream.
|
|
141
|
+
# @param type [Class] The class of the type to read (subclass of SerializedType).
|
|
142
|
+
# @return [SerializedType] The read value.
|
|
115
143
|
def read_type(type)
|
|
116
144
|
type.from_parser(self)
|
|
117
145
|
end
|
|
118
146
|
|
|
147
|
+
# Returns the associated type for a given field.
|
|
148
|
+
# @param field [FieldInstance] The field instance.
|
|
149
|
+
# @return [Class] The associated SerializedType subclass.
|
|
119
150
|
def type_for_field(field)
|
|
120
151
|
field.associated_type
|
|
121
152
|
end
|
|
122
153
|
|
|
154
|
+
# Reads the value of a specific field from the stream.
|
|
155
|
+
# @param field [FieldInstance] The field to read.
|
|
156
|
+
# @return [SerializedType] The read value.
|
|
123
157
|
def read_field_value(field)
|
|
124
158
|
type = SerializedType.get_type_by_name(field.type)
|
|
125
159
|
|
|
@@ -7,40 +7,63 @@ module BinaryCodec
|
|
|
7
7
|
@sink = sink || BytesList.new
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# Serializes a value into the sink.
|
|
11
|
+
# @param value [SerializedType] The value to write.
|
|
10
12
|
def write(value)
|
|
11
13
|
value.to_byte_sink(@sink)
|
|
12
14
|
end
|
|
13
15
|
|
|
16
|
+
# Adds raw bytes to the sink.
|
|
17
|
+
# @param bytes [Array<Integer>] The bytes to add.
|
|
14
18
|
def put(bytes)
|
|
15
19
|
@sink.put(bytes)
|
|
16
20
|
end
|
|
17
21
|
|
|
22
|
+
# Serializes a value of a given type.
|
|
23
|
+
# @param type [Class] The class of the type (subclass of SerializedType).
|
|
24
|
+
# @param value [Object] The value to serialize.
|
|
18
25
|
def write_type(type, value)
|
|
19
26
|
write(type.from(value))
|
|
20
27
|
end
|
|
21
28
|
|
|
29
|
+
# Writes a BytesList into the sink.
|
|
30
|
+
# @param bytes_list [BytesList] The bytes list to write.
|
|
22
31
|
def write_bytes_list(bytes_list)
|
|
23
32
|
bytes_list.to_byte_sink(@sink)
|
|
24
33
|
end
|
|
25
34
|
|
|
35
|
+
# Writes a field and its value into the sink.
|
|
36
|
+
# @param field [FieldInstance] The field to write.
|
|
37
|
+
# @param value [Object] The value of the field.
|
|
38
|
+
# @param is_unl_modify_workaround [Boolean] Whether to apply the UNLModify workaround.
|
|
26
39
|
def write_field_and_value(field, value, is_unl_modify_workaround = false)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
# Special case for Blob fields that are empty (e.g., SigningPubKey = "")
|
|
41
|
+
# In Ruby, Blob.from("") returns an empty Blob.
|
|
42
|
+
# If we want to force 0x00 length prefix, we handle it here.
|
|
43
|
+
if field.type == 'Blob' && (value == "" || (value.is_a?(Array) && value.empty?))
|
|
44
|
+
@sink.put(field.header.to_bytes)
|
|
45
|
+
@sink.put([0]) # length 0
|
|
46
|
+
return
|
|
33
47
|
end
|
|
34
48
|
|
|
49
|
+
field_header = field.header
|
|
50
|
+
associated_value = field.associated_type.from(value)
|
|
51
|
+
|
|
35
52
|
@sink.put(field_header.to_bytes)
|
|
36
53
|
|
|
37
54
|
if field.is_variable_length_encoded
|
|
38
55
|
write_length_encoded(associated_value, is_unl_modify_workaround)
|
|
39
56
|
else
|
|
40
57
|
associated_value.to_byte_sink(@sink)
|
|
58
|
+
if field.type == 'STObject'
|
|
59
|
+
@sink.put([0xE1]) # ObjectEndMarker
|
|
60
|
+
end
|
|
41
61
|
end
|
|
42
62
|
end
|
|
43
63
|
|
|
64
|
+
# Writes a value with its length encoded prefix.
|
|
65
|
+
# @param value [SerializedType] The value to write.
|
|
66
|
+
# @param is_unl_modify_workaround [Boolean] Whether to apply the UNLModify workaround.
|
|
44
67
|
def write_length_encoded(value, is_unl_modify_workaround = false)
|
|
45
68
|
bytes = BytesList.new
|
|
46
69
|
|
|
@@ -9,24 +9,35 @@ module BinaryCodec
|
|
|
9
9
|
@bytes_array = []
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Returns the total length of all bytes in the list.
|
|
13
|
+
# @return [Integer] The total length.
|
|
12
14
|
def get_length
|
|
13
15
|
@bytes_array.inject(0) { |sum, arr| sum + arr.length }
|
|
14
16
|
end
|
|
15
17
|
|
|
18
|
+
# Adds bytes to the list.
|
|
19
|
+
# @param bytes_arg [Array<Integer>, Integer] The bytes to add.
|
|
20
|
+
# @return [BytesList] self for chaining.
|
|
16
21
|
def put(bytes_arg)
|
|
17
|
-
bytes = bytes_arg.dup
|
|
22
|
+
bytes = bytes_arg.is_a?(Integer) ? [bytes_arg] : bytes_arg.dup
|
|
18
23
|
@bytes_array << bytes
|
|
19
24
|
self # Allow chaining
|
|
20
25
|
end
|
|
21
26
|
|
|
27
|
+
# Puts the bytes into another byte sink.
|
|
28
|
+
# @param list [Object] The sink to put bytes into.
|
|
22
29
|
def to_byte_sink(list)
|
|
23
30
|
list.put(to_bytes)
|
|
24
31
|
end
|
|
25
32
|
|
|
33
|
+
# Returns all bytes as a single flat array.
|
|
34
|
+
# @return [Array<Integer>] The flattened byte array.
|
|
26
35
|
def to_bytes
|
|
27
36
|
@bytes_array.flatten # TODO: Uses concat in xrpl.js, maybe implement that instead
|
|
28
37
|
end
|
|
29
38
|
|
|
39
|
+
# Returns the hex representation of all bytes in the list.
|
|
40
|
+
# @return [String] The hex string.
|
|
30
41
|
def to_hex
|
|
31
42
|
bytes_to_hex(to_bytes)
|
|
32
43
|
end
|
|
@@ -1,38 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
require 'address-codec/xrp_codec'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module BinaryCodec
|
|
5
4
|
class AccountId < Hash160
|
|
6
|
-
|
|
7
|
-
# Create a single instance of AddressCodec for use in static functions
|
|
8
|
-
@address_codec = AddressCodec::AddressCodec.new
|
|
9
|
-
|
|
10
|
-
attr_reader :bytes
|
|
11
|
-
|
|
12
5
|
@width = 20
|
|
13
6
|
|
|
14
7
|
def initialize(bytes = nil)
|
|
15
|
-
super(bytes
|
|
8
|
+
super(bytes)
|
|
16
9
|
end
|
|
17
10
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
# Defines how to construct an AccountID
|
|
23
|
-
#
|
|
24
|
-
# @param value [AccountID, String] Either an existing AccountID, a hex string, or a base58 r-Address
|
|
25
|
-
# @return [AccountID] An AccountID object
|
|
11
|
+
# Creates a new AccountId instance from a value.
|
|
12
|
+
# @param value [AccountId, String] The value to convert (hex or base58 address).
|
|
13
|
+
# @return [AccountId] The created instance.
|
|
26
14
|
def self.from(value)
|
|
27
|
-
if value.is_a?(AccountId)
|
|
28
|
-
return value
|
|
29
|
-
end
|
|
15
|
+
return value if value.is_a?(AccountId)
|
|
30
16
|
|
|
31
17
|
if value.is_a?(String)
|
|
32
|
-
return
|
|
18
|
+
return new if value.empty?
|
|
33
19
|
|
|
34
20
|
if valid_hex?(value)
|
|
35
|
-
return
|
|
21
|
+
return new(hex_to_bytes(value))
|
|
36
22
|
else
|
|
37
23
|
return from_base58(value)
|
|
38
24
|
end
|
|
@@ -41,13 +27,13 @@ module BinaryCodec
|
|
|
41
27
|
raise 'Cannot construct AccountID from the value provided'
|
|
42
28
|
end
|
|
43
29
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# @
|
|
47
|
-
# @return [AccountID] An AccountID object
|
|
30
|
+
# Creates an AccountId instance from a base58 address.
|
|
31
|
+
# @param value [String] The classic or X-address.
|
|
32
|
+
# @return [AccountId] The created instance.
|
|
48
33
|
def self.from_base58(value)
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
address_codec = AddressCodec::AddressCodec.new
|
|
35
|
+
if address_codec.valid_x_address?(value)
|
|
36
|
+
classic = address_codec.x_address_to_classic_address(value)
|
|
51
37
|
|
|
52
38
|
if classic[:tag] != false
|
|
53
39
|
raise 'Only allowed to have tag on Account or Destination'
|
|
@@ -56,24 +42,19 @@ module BinaryCodec
|
|
|
56
42
|
value = classic[:classic_address]
|
|
57
43
|
end
|
|
58
44
|
|
|
59
|
-
|
|
45
|
+
new(address_codec.decode_account_id(value))
|
|
60
46
|
end
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
#
|
|
64
|
-
# @return [String] The base58 string for this AccountID
|
|
65
|
-
def to_json
|
|
48
|
+
def to_json(_definitions = nil, _field_name = nil)
|
|
66
49
|
to_base58
|
|
67
50
|
end
|
|
68
51
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# @return [String] The base58 string defined by this.bytes
|
|
52
|
+
# Returns the base58 representation of the account ID.
|
|
53
|
+
# @return [String] The base58 address.
|
|
72
54
|
def to_base58
|
|
73
55
|
address_codec = AddressCodec::AddressCodec.new
|
|
74
56
|
address_codec.encode_account_id(@bytes)
|
|
75
57
|
end
|
|
76
|
-
|
|
77
58
|
end
|
|
78
59
|
|
|
79
60
|
end
|