xrpl-ruby 0.2.4 → 0.5.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/address-codec/address_codec.rb +21 -4
  3. data/lib/address-codec/codec.rb +15 -2
  4. data/lib/address-codec/xrp_codec.rb +29 -2
  5. data/lib/binary-codec/binary_codec.rb +46 -22
  6. data/lib/binary-codec/enums/definitions.json +592 -1
  7. data/lib/binary-codec/enums/definitions.rb +17 -5
  8. data/lib/binary-codec/enums/fields.rb +2 -0
  9. data/lib/binary-codec/serdes/binary_parser.rb +38 -0
  10. data/lib/binary-codec/serdes/binary_serializer.rb +18 -7
  11. data/lib/binary-codec/serdes/bytes_list.rb +11 -0
  12. data/lib/binary-codec/types/account_id.rb +18 -37
  13. data/lib/binary-codec/types/amount.rb +43 -23
  14. data/lib/binary-codec/types/blob.rb +14 -5
  15. data/lib/binary-codec/types/currency.rb +15 -4
  16. data/lib/binary-codec/types/hash.rb +37 -36
  17. data/lib/binary-codec/types/issue.rb +50 -0
  18. data/lib/binary-codec/types/path_set.rb +93 -0
  19. data/lib/binary-codec/types/serialized_type.rb +52 -28
  20. data/lib/binary-codec/types/st_array.rb +71 -0
  21. data/lib/binary-codec/types/st_object.rb +100 -3
  22. data/lib/binary-codec/types/uint.rb +116 -3
  23. data/lib/binary-codec/types/vector256.rb +53 -0
  24. data/lib/binary-codec/types/xchain_bridge.rb +47 -0
  25. data/lib/binary-codec/utilities.rb +18 -0
  26. data/lib/core/base_58_xrp.rb +2 -0
  27. data/lib/core/base_x.rb +10 -0
  28. data/lib/core/core.rb +44 -6
  29. data/lib/core/utilities.rb +38 -0
  30. data/lib/key-pairs/ed25519.rb +64 -0
  31. data/lib/key-pairs/key_pairs.rb +92 -0
  32. data/lib/key-pairs/secp256k1.rb +116 -0
  33. data/lib/wallet/wallet.rb +117 -0
  34. data/lib/xrpl-ruby.rb +25 -1
  35. metadata +26 -2
@@ -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
 
@@ -44,19 +45,30 @@ module BinaryCodec
44
45
 
45
46
  end
46
47
 
48
+ # Returns the singleton instance of the Definitions class.
49
+ # @return [Definitions] The singleton instance.
47
50
  def self.instance
48
51
  @@instance ||= new
49
52
  end
50
53
 
51
- def get_field_header_from_name(field_name)
54
+ # Returns the field header for a given field name.
55
+ # @param field_name [String] The name of the field.
56
+ # @return [FieldHeader] The field header.
57
+ def get_field_header_from_name(field_name)
52
58
  @field_header_map[field_name]
53
59
  end
54
60
 
55
- def get_field_name_from_header(field_header)
61
+ # Returns the field name for a given field header.
62
+ # @param field_header [FieldHeader] The field header.
63
+ # @return [String] The name of the field.
64
+ def get_field_name_from_header(field_header)
56
65
  @field_id_name_map[Digest::MD5.hexdigest(Marshal.dump(field_header))]
57
66
  end
58
67
 
59
- def get_field_instance(field_name)
68
+ # Returns a FieldInstance for a given field name.
69
+ # @param field_name [String] The name of the field.
70
+ # @return [FieldInstance] The field instance.
71
+ def get_field_instance(field_name)
60
72
  field_info = @field_info_map[field_name]
61
73
  field_header = get_field_header_from_name(field_name)
62
74
 
@@ -66,10 +78,10 @@ module BinaryCodec
66
78
  is_serialized: field_info.is_serialized,
67
79
  is_signing_field: field_info.is_signing_field,
68
80
  type: field_info.type,
69
- ordinal: (field_header.type << 16) | field_info.nth, # @type_ordinals[field_info.type],
81
+ ordinal: (@type_ordinals[field_info.type] << 16) | field_info.nth,
70
82
  name: field_name,
71
83
  header: field_header,
72
- associated_type: field_info.type # SerializedType::getTypeByName($this->type)::class;
84
+ associated_type: SerializedType.get_type_by_name(field_info.type)
73
85
  )
74
86
  end
75
87
 
@@ -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
@@ -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,6 +108,8 @@ 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
114
  type = read_uint8
88
115
  nth = type & 15
@@ -105,6 +132,8 @@ module BinaryCodec
105
132
  FieldHeader.new(type: type, nth: nth) # (type << 16) | nth for read_field_ordinal
106
133
  end
107
134
 
135
+ # Reads a field instance from the stream.
136
+ # @return [FieldInstance] The field instance.
108
137
  def read_field
109
138
  field_header = read_field_header
110
139
  field_name = @definitions.get_field_name_from_header(field_header)
@@ -112,14 +141,23 @@ module BinaryCodec
112
141
  @definitions.get_field_instance(field_name)
113
142
  end
114
143
 
144
+ # Reads a value of the specified type from the stream.
145
+ # @param type [Class] The class of the type to read (subclass of SerializedType).
146
+ # @return [SerializedType] The read value.
115
147
  def read_type(type)
116
148
  type.from_parser(self)
117
149
  end
118
150
 
151
+ # Returns the associated type for a given field.
152
+ # @param field [FieldInstance] The field instance.
153
+ # @return [Class] The associated SerializedType subclass.
119
154
  def type_for_field(field)
120
155
  field.associated_type
121
156
  end
122
157
 
158
+ # Reads the value of a specific field from the stream.
159
+ # @param field [FieldInstance] The field to read.
160
+ # @return [SerializedType] The read value.
123
161
  def read_field_value(field)
124
162
  type = SerializedType.get_type_by_name(field.type)
125
163
 
@@ -7,30 +7,38 @@ 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
- field_header = FieldHeader.new(type: field.header.type, nth: field.nth)
28
- type_class = SerializedType.get_type_by_name(field.type)
29
- associated_value = type_class.from(value)
30
-
31
- if !associated_value.respond_to?(:to_byte_sink) || field.name.nil?
32
- raise 'Error'
33
- end
40
+ field_header = field.header
41
+ associated_value = field.associated_type.from(value)
34
42
 
35
43
  @sink.put(field_header.to_bytes)
36
44
 
@@ -41,6 +49,9 @@ module BinaryCodec
41
49
  end
42
50
  end
43
51
 
52
+ # Writes a value with its length encoded prefix.
53
+ # @param value [SerializedType] The value to write.
54
+ # @param is_unl_modify_workaround [Boolean] Whether to apply the UNLModify workaround.
44
55
  def write_length_encoded(value, is_unl_modify_workaround = false)
45
56
  bytes = BytesList.new
46
57
 
@@ -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>] The bytes to add.
20
+ # @return [BytesList] self for chaining.
16
21
  def put(bytes_arg)
17
22
  bytes = 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
- require 'address-codec/address_codec'
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 || Array.new(20, 0))
8
+ super(bytes)
16
9
  end
17
10
 
18
- #def self.address_codec
19
- # @address_codec ||= AddressCodec::AddressCodec.new
20
- #end
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 AccountId.new if value.empty?
18
+ return new if value.empty?
33
19
 
34
20
  if valid_hex?(value)
35
- return AccountId.new(hex_to_bytes(value))
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
- # Defines how to build an AccountID from a base58 r-Address
45
- #
46
- # @param value [String] A base58 r-Address
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
- if @address_codec.valid_x_address?(value)
50
- classic = @address_codec.x_address_to_classic_address(value)
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
- AccountId.new(@address_codec.decode_account_id(value))
45
+ new(address_codec.decode_account_id(value))
60
46
  end
61
47
 
62
- # Overload of to_json
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
- # Defines how to encode AccountID into a base58 address
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
@@ -3,8 +3,6 @@
3
3
  require 'bigdecimal'
4
4
  require 'bigdecimal/util'
5
5
 
6
- require_relative '../utilities'
7
-
8
6
  module BinaryCodec
9
7
  class Amount < SerializedType
10
8
 
@@ -31,6 +29,9 @@ module BinaryCodec
31
29
  #
32
30
  # @param value [Amount, Hash, String] representing the amount
33
31
  # @return [Amount] an Amount object
32
+ # Creates a new Amount instance from a value.
33
+ # @param value [Amount, String, Hash, Integer] The value to convert.
34
+ # @return [Amount] The created instance.
34
35
  def self.from(value)
35
36
  return value if value.is_a?(Amount)
36
37
 
@@ -93,8 +94,12 @@ module BinaryCodec
93
94
  # Read an amount from a BinaryParser
94
95
  #
95
96
  # @param parser [BinaryParser] The BinaryParser to read the Amount from
96
- # @return [Amount] An Amount object
97
- def self.from_parser(parser)
97
+ # @return [Amount] An Amount bundle exec rspec spec/binary-codec/types/st_object_spec.rb object
98
+ # Creates an Amount instance from a parser.
99
+ # @param parser [BinaryParser] The parser to read from.
100
+ # @param _size_hint [Integer, nil] Optional size hint (unused).
101
+ # @return [Amount] The created instance.
102
+ def self.from_parser(parser, _size_hint = nil)
98
103
  is_iou = parser.peek & 0x80 != 0
99
104
  return Amount.new(parser.read(48)) if is_iou
100
105
 
@@ -107,9 +112,13 @@ module BinaryCodec
107
112
  # The JSON representation of this Amount
108
113
  #
109
114
  # @return [Hash, String] The JSON interpretation of this.bytes
110
- def to_json
115
+ # Returns the JSON representation of the Amount.
116
+ # @param _definitions [Definitions, nil] Unused.
117
+ # @param _field_name [String, nil] Optional field name.
118
+ # @return [String, Hash] The JSON representation.
119
+ def to_json(_definitions = nil, _field_name = nil)
111
120
  if is_native?
112
- bytes = @bytes.dup # Duplicate the bytes to avoid mutation
121
+ bytes = @bytes.dup
113
122
  is_positive = (bytes[0] & 0x40) != 0
114
123
  sign = is_positive ? '' : '-'
115
124
  bytes[0] &= 0x3f
@@ -122,47 +131,52 @@ module BinaryCodec
122
131
  end
123
132
 
124
133
  if is_iou?
125
- parser = BinaryParser.new(to_s)
126
- mantissa = parser.read(8)
134
+ parser = BinaryParser.new(to_hex)
135
+ mantissa_bytes = parser.read(8)
127
136
  currency = Currency.from_parser(parser)
128
137
  issuer = AccountId.from_parser(parser)
129
138
 
130
- b1 = mantissa[0]
131
- b2 = mantissa[1]
139
+ b1 = mantissa_bytes[0]
140
+ b2 = mantissa_bytes[1]
132
141
 
133
142
  is_positive = (b1 & 0x40) != 0
134
143
  sign = is_positive ? '' : '-'
135
144
  exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97
136
145
 
137
- mantissa[0] = 0
138
- mantissa[1] &= 0x3f
139
- value = BigDecimal("#{sign}#{bytes_to_hex(mantissa).to_i(16)}") * BigDecimal("1e#{exponent}")
146
+ mantissa_bytes[0] = 0
147
+ mantissa_bytes[1] &= 0x3f
148
+
149
+ # Convert mantissa bytes to integer
150
+ mantissa_int = mantissa_bytes.reduce(0) { |acc, b| (acc << 8) + b }
151
+
152
+ value = BigDecimal(mantissa_int) * (BigDecimal(10)**exponent)
153
+ value = -value unless is_positive
140
154
  self.class.assert_iou_is_valid(value)
141
155
 
142
156
  return {
143
- "value" => value.to_s('F'),
157
+ "value" => value.to_s('F').sub(/\.0$/, ''),
144
158
  "currency" => currency.to_json,
145
159
  "issuer" => issuer.to_json
146
- }.to_s
160
+ }
147
161
  end
148
162
 
149
163
  if is_mpt?
150
- parser = BinaryParser.new(to_s)
164
+ parser = BinaryParser.new(to_hex)
151
165
  leading_byte = parser.read(1)
152
- amount = parser.read(8)
166
+ amount_bytes = parser.read(8)
153
167
  mpt_id = Hash192.from_parser(parser)
154
168
 
155
169
  is_positive = (leading_byte[0] & 0x40) != 0
156
170
  sign = is_positive ? '' : '-'
157
171
 
158
- msb = read_uint32be(amount[0, 4])
159
- lsb = read_uint32be(amount[4, 4])
172
+ msb = BinaryCodec.read_uint32be(amount_bytes[0, 4])
173
+ lsb = BinaryCodec.read_uint32be(amount_bytes[4, 4])
160
174
  num = (msb << 32) | lsb
161
175
 
162
176
  return {
163
- value: "#{sign}#{num}",
164
- mpt_issuance_id: mpt_id.to_s
165
- }.to_s
177
+ "value" => "#{sign}#{num}",
178
+ "mpt_issuance_id" => mpt_id.to_hex
179
+ }
166
180
  end
167
181
 
168
182
  raise 'Invalid amount to construct JSON'
@@ -247,7 +261,7 @@ module BinaryCodec
247
261
  # Ensure that the value, after being multiplied by the exponent, does not
248
262
  # contain a decimal. This function is typically used to validate numbers
249
263
  # that need to be represented as precise integers after scaling, such as
250
- # amounts in financial transactions.
264
+ # amounts in financial transactions. Example failure:1.1234567891234567
251
265
  #
252
266
  # @param decimal [BigDecimal] A BigDecimal object
253
267
  # @raise [ArgumentError] if the value contains a decimal
@@ -262,6 +276,8 @@ module BinaryCodec
262
276
  # Check if this amount is in units of Native Currency (XRP)
263
277
  #
264
278
  # @return [Boolean] true if Native (XRP)
279
+ # Checks if the amount is a native XRP amount.
280
+ # @return [Boolean] True if native, false otherwise.
265
281
  def is_native?
266
282
  (self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20).zero?
267
283
  end
@@ -269,6 +285,8 @@ module BinaryCodec
269
285
  # Check if this amount is in units of MPT
270
286
  #
271
287
  # @return [Boolean] true if MPT
288
+ # Checks if the amount is a multi-purpose token (MPT).
289
+ # @return [Boolean] True if MPT, false otherwise.
272
290
  def is_mpt?
273
291
  (self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20) != 0
274
292
  end
@@ -276,6 +294,8 @@ module BinaryCodec
276
294
  # Check if this amount is in units of IOU
277
295
  #
278
296
  # @return [Boolean] true if IOU
297
+ # Checks if the amount is an IOU amount.
298
+ # @return [Boolean] True if IOU, false otherwise.
279
299
  def is_iou?
280
300
  (self.bytes[0] & 0x80) != 0
281
301
  end
@@ -1,29 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../core/core'
4
-
5
3
  module BinaryCodec
6
4
  class Blob < SerializedType
7
5
 
8
6
  def initialize(byte_buf = nil)
9
- @bytes = byte_buf || Array.new(0)
7
+ super(byte_buf || [])
10
8
  end
11
9
 
10
+ # Creates a new Blob instance from a value.
11
+ # @param value [Blob, String, Array<Integer>] The value to convert.
12
+ # @return [Blob] The created instance.
12
13
  def self.from(value)
13
14
  return value if value.is_a?(Blob)
14
15
 
15
16
  if value.is_a?(String)
16
- if value !~ /^[A-F0-9]*$/i
17
+ unless valid_hex?(value)
17
18
  raise StandardError, 'Cannot construct Blob from a non-hex string'
18
19
  end
19
20
  return Blob.new(hex_to_bytes(value))
20
21
  end
21
22
 
23
+ if value.is_a?(Array)
24
+ return Blob.new(value)
25
+ end
26
+
22
27
  raise StandardError, 'Cannot construct Blob from value given'
23
28
  end
24
29
 
30
+ # Creates a Blob instance from a parser.
31
+ # @param parser [BinaryParser] The parser to read from.
32
+ # @param hint [Integer, nil] Optional width hint.
33
+ # @return [Blob] The created instance.
25
34
  def self.from_parser(parser, hint = nil)
26
- Blob.new(parser.read(hint))
35
+ new(parser.read(hint))
27
36
  end
28
37
 
29
38
  end
@@ -15,8 +15,8 @@ module BinaryCodec
15
15
  @width = 20
16
16
 
17
17
  def initialize(byte_buf = nil)
18
- super(byte_buf || Array.new(20, 0)) # Defaults to XRP bytes if no buffer is given
19
- hex = bytes_to_hex(@bytes)
18
+ super(byte_buf)
19
+ hex = to_hex
20
20
 
21
21
  if XRP_HEX_REGEX.match?(hex)
22
22
  @_iso = 'XRP'
@@ -27,20 +27,31 @@ module BinaryCodec
27
27
  end
28
28
  end
29
29
 
30
+ # Returns the ISO code of the currency, if applicable.
31
+ # @return [String, nil] The ISO code.
30
32
  def iso
31
33
  @_iso
32
34
  end
33
35
 
36
+ # Creates a new Currency instance from a value.
37
+ # @param value [Currency, String, Array<Integer>] The value to convert.
38
+ # @return [Currency] The created instance.
34
39
  def self.from(value)
35
40
  return value if value.is_a?(Currency)
36
41
 
37
42
  if value.is_a?(String)
38
- return Currency.new(bytes_from_representation(value))
43
+ return new(bytes_from_representation(value))
39
44
  end
40
45
 
41
- raise StandardError, 'Cannot construct Currency from value given'
46
+ if value.is_a?(Array)
47
+ return new(value)
48
+ end
49
+
50
+ raise StandardError, "Cannot construct Currency from #{value.class}"
42
51
  end
43
52
 
53
+ # Returns the JSON representation of the currency.
54
+ # @return [String] The ISO code or hex string.
44
55
  def to_json
45
56
  iso = self.iso
46
57
  return iso unless iso.nil?