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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/LICENSE +21 -0
  4. data/README.md +94 -0
  5. data/lib/address-codec/address_codec.rb +21 -4
  6. data/lib/address-codec/codec.rb +15 -2
  7. data/lib/address-codec/xrp_codec.rb +29 -2
  8. data/lib/binary-codec/binary_codec.rb +47 -21
  9. data/lib/binary-codec/enums/definitions.json +592 -1
  10. data/lib/binary-codec/enums/definitions.rb +23 -9
  11. data/lib/binary-codec/enums/fields.rb +3 -1
  12. data/lib/binary-codec/serdes/binary_parser.rb +44 -10
  13. data/lib/binary-codec/serdes/binary_serializer.rb +29 -6
  14. data/lib/binary-codec/serdes/bytes_list.rb +12 -1
  15. data/lib/binary-codec/types/account_id.rb +18 -37
  16. data/lib/binary-codec/types/amount.rb +123 -77
  17. data/lib/binary-codec/types/blob.rb +14 -5
  18. data/lib/binary-codec/types/currency.rb +15 -4
  19. data/lib/binary-codec/types/hash.rb +37 -36
  20. data/lib/binary-codec/types/issue.rb +47 -0
  21. data/lib/binary-codec/types/path_set.rb +93 -0
  22. data/lib/binary-codec/types/serialized_type.rb +52 -28
  23. data/lib/binary-codec/types/st_array.rb +106 -0
  24. data/lib/binary-codec/types/st_object.rb +150 -14
  25. data/lib/binary-codec/types/uint.rb +166 -3
  26. data/lib/binary-codec/types/vector256.rb +53 -0
  27. data/lib/binary-codec/types/xchain_bridge.rb +47 -0
  28. data/lib/binary-codec/utilities.rb +18 -0
  29. data/lib/core/base_58_xrp.rb +2 -0
  30. data/lib/core/base_x.rb +10 -0
  31. data/lib/core/core.rb +44 -6
  32. data/lib/core/utilities.rb +38 -0
  33. data/lib/key-pairs/ed25519.rb +69 -0
  34. data/lib/key-pairs/key_pairs.rb +92 -0
  35. data/lib/key-pairs/secp256k1.rb +169 -0
  36. data/lib/wallet/wallet.rb +179 -0
  37. data/lib/xrpl/client.rb +498 -0
  38. data/lib/xrpl/faucet.rb +138 -0
  39. data/lib/xrpl/version.rb +5 -0
  40. data/lib/xrpl-ruby.rb +30 -1
  41. metadata +80 -4
@@ -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
 
@@ -18,6 +16,8 @@ module BinaryCodec
18
16
 
19
17
  MAX_DROPS = BigDecimal("1e17")
20
18
  MIN_XRP = BigDecimal("1e-6")
19
+ MIN_XRP_DROPS = 1
20
+ MAX_XRP_DROPS = 10**17
21
21
 
22
22
  def initialize(bytes = nil)
23
23
  if bytes.nil?
@@ -31,70 +31,91 @@ module BinaryCodec
31
31
  #
32
32
  # @param value [Amount, Hash, String] representing the amount
33
33
  # @return [Amount] an Amount object
34
+ # Creates a new Amount instance from a value.
35
+ # @param value [Amount, String, Hash, Integer] The value to convert.
36
+ # @return [Amount] The created instance.
34
37
  def self.from(value)
35
38
  return value if value.is_a?(Amount)
36
39
 
37
- amount = Array.new(8, 0) # Equivalent to a Uint8Array of 8 zeros
38
-
39
40
  if value.is_a?(String)
40
41
  Amount.assert_xrp_is_valid(value)
41
-
42
- number = value.to_i # Use to_i for equivalent BigInt handling
43
-
44
- int_buf = [Array.new(4, 0), Array.new(4, 0)]
45
- BinaryCodec.write_uint32be(int_buf[0], (number >> 32) & 0xFFFFFFFF, 0)
46
- BinaryCodec.write_uint32be(int_buf[1], number & 0xFFFFFFFF, 0)
47
-
48
- amount = int_buf.flatten
49
-
50
- amount[0] |= 0x40
51
-
52
- return Amount.new(amount)
42
+ number = value.to_i
43
+ amount_bytes = int_to_bytes(number, 8)
44
+ amount_bytes[0] |= 0x40
45
+ return Amount.new(amount_bytes)
53
46
  end
54
47
 
55
- if is_amount_object_iou?(value)
56
- number = BigDecimal(value[:value])
57
- self.assert_iou_is_valid(number)
48
+ if value.respond_to?(:key?)
49
+ val = value[:value] || value['value']
50
+ cur = value[:currency] || value['currency']
51
+ iss = value[:issuer] || value['issuer']
58
52
 
59
- if number.zero?
60
- amount[0] |= 0x80
61
- else
62
- scale = number.frac.to_s('F').split('.').last.size
63
- unscaled_value = (number * (10**scale)).to_i
64
- int_string = unscaled_value.abs.to_s.ljust(16, '0')
65
- num = int_string.to_i
53
+ if val && cur && iss
54
+ number = BigDecimal(val.to_s)
55
+
56
+ if number.precision > MAX_IOU_PRECISION
57
+ raise ArgumentError, 'Decimal precision out of range'
58
+ end
66
59
 
67
- int_buf = [Array.new(4, 0), Array.new(4, 0)]
68
- BinaryCodec.write_uint32be(int_buf[0], (num >> 32) & 0xFFFFFFFF)
69
- BinaryCodec.write_uint32be(int_buf[1], num & 0xFFFFFFFF)
60
+ currency_inst = Currency.from(cur)
61
+ issuer_inst = AccountId.from(iss)
70
62
 
71
- amount = int_buf.flatten
63
+ if number.zero?
64
+ iou_bytes = [0x80, 0, 0, 0, 0, 0, 0, 0]
65
+ return Amount.new(iou_bytes + currency_inst.to_bytes + issuer_inst.to_bytes)
66
+ end
72
67
 
73
- amount[0] |= 0x80
68
+ is_positive = number >= 0
69
+ abs_value = number.abs
70
+
71
+ exponent = (Math.log10(abs_value.to_f).floor) - 15
72
+ mantissa = (abs_value / (BigDecimal(10)**exponent)).to_i
74
73
 
75
- if number > 0
76
- amount[0] |= 0x40
74
+ while mantissa < 1000000000000000
75
+ mantissa *= 10
76
+ exponent -= 1
77
+ end
78
+ while mantissa > 9999999999999999
79
+ mantissa /= 10
80
+ exponent += 1
77
81
  end
78
82
 
79
- exponent = number.exponent - 16
80
- exponent_byte = 97 + exponent
81
- amount[0] |= exponent_byte >> 2
82
- amount[1] |= (exponent_byte & 0x03) << 6
83
+ exponent_byte = exponent + 97
84
+ b1 = (is_positive ? 0x40 : 0) | 0x80 | (exponent_byte >> 2)
85
+ b2 = ((exponent_byte & 0x03) << 6) | (mantissa >> 48)
86
+
87
+ iou_bytes = [
88
+ b1, b2,
89
+ (mantissa >> 40) & 0xff,
90
+ (mantissa >> 32) & 0xff,
91
+ (mantissa >> 24) & 0xff,
92
+ (mantissa >> 16) & 0xff,
93
+ (mantissa >> 8) & 0xff,
94
+ mantissa & 0xff
95
+ ]
96
+ return Amount.new(iou_bytes + currency_inst.to_bytes + issuer_inst.to_bytes)
83
97
  end
98
+ end
84
99
 
85
- currency = Currency.from(value[:currency]).to_bytes
86
- issuer = AccountId.from(value[:issuer]).to_bytes
87
-
88
- return Amount.new(amount + currency + issuer)
100
+ if value.is_a?(Integer)
101
+ Amount.assert_xrp_is_valid(value.to_s)
102
+ amount_bytes = int_to_bytes(value, 8)
103
+ amount_bytes[0] |= 0x40
104
+ return Amount.new(amount_bytes)
89
105
  end
90
106
 
107
+ raise ArgumentError, "Cannot construct Amount from the value given"
91
108
  end
92
109
 
93
110
  # Read an amount from a BinaryParser
94
111
  #
95
112
  # @param parser [BinaryParser] The BinaryParser to read the Amount from
96
- # @return [Amount] An Amount object
97
- def self.from_parser(parser)
113
+ # @return [Amount] An Amount bundle exec rspec spec/binary-codec/types/st_object_spec.rb object
114
+ # Creates an Amount instance from a parser.
115
+ # @param parser [BinaryParser] The parser to read from.
116
+ # @param _size_hint [Integer, nil] Optional size hint (unused).
117
+ # @return [Amount] The created instance.
118
+ def self.from_parser(parser, _size_hint = nil)
98
119
  is_iou = parser.peek & 0x80 != 0
99
120
  return Amount.new(parser.read(48)) if is_iou
100
121
 
@@ -107,9 +128,13 @@ module BinaryCodec
107
128
  # The JSON representation of this Amount
108
129
  #
109
130
  # @return [Hash, String] The JSON interpretation of this.bytes
110
- def to_json
131
+ # Returns the JSON representation of the Amount.
132
+ # @param _definitions [Definitions, nil] Unused.
133
+ # @param _field_name [String, nil] Optional field name.
134
+ # @return [String, Hash] The JSON representation.
135
+ def to_json(_definitions = nil, _field_name = nil)
111
136
  if is_native?
112
- bytes = @bytes.dup # Duplicate the bytes to avoid mutation
137
+ bytes = @bytes.dup
113
138
  is_positive = (bytes[0] & 0x40) != 0
114
139
  sign = is_positive ? '' : '-'
115
140
  bytes[0] &= 0x3f
@@ -122,47 +147,54 @@ module BinaryCodec
122
147
  end
123
148
 
124
149
  if is_iou?
125
- parser = BinaryParser.new(to_s)
126
- mantissa = parser.read(8)
150
+ parser = BinaryParser.new(to_hex)
151
+ mantissa_bytes = parser.read(8)
127
152
  currency = Currency.from_parser(parser)
128
153
  issuer = AccountId.from_parser(parser)
129
154
 
130
- b1 = mantissa[0]
131
- b2 = mantissa[1]
155
+ b1 = mantissa_bytes[0]
156
+ b2 = mantissa_bytes[1]
132
157
 
133
158
  is_positive = (b1 & 0x40) != 0
134
- sign = is_positive ? '' : '-'
135
159
  exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97
136
160
 
137
- mantissa[0] = 0
138
- mantissa[1] &= 0x3f
139
- value = BigDecimal("#{sign}#{bytes_to_hex(mantissa).to_i(16)}") * BigDecimal("1e#{exponent}")
140
- self.class.assert_iou_is_valid(value)
161
+ mantissa_bytes[0] = 0
162
+ mantissa_bytes[1] &= 0x3f
163
+
164
+ # Convert mantissa bytes to integer
165
+ mantissa_int = mantissa_bytes.reduce(0) { |acc, b| (acc << 8) + b }
166
+
167
+ # value = mantissa * 10^exponent
168
+ value = BigDecimal(mantissa_int) * (BigDecimal(10)**exponent)
169
+ value = -value unless is_positive
170
+
171
+ # Format the value string to match xrpl.js (stripping trailing .0)
172
+ formatted_value = value.to_s('F').sub(/\.0$/, '')
141
173
 
142
174
  return {
143
- "value" => value.to_s('F'),
175
+ "value" => formatted_value,
144
176
  "currency" => currency.to_json,
145
177
  "issuer" => issuer.to_json
146
- }.to_s
178
+ }
147
179
  end
148
180
 
149
181
  if is_mpt?
150
- parser = BinaryParser.new(to_s)
182
+ parser = BinaryParser.new(to_hex)
151
183
  leading_byte = parser.read(1)
152
- amount = parser.read(8)
184
+ amount_bytes = parser.read(8)
153
185
  mpt_id = Hash192.from_parser(parser)
154
186
 
155
187
  is_positive = (leading_byte[0] & 0x40) != 0
156
188
  sign = is_positive ? '' : '-'
157
189
 
158
- msb = read_uint32be(amount[0, 4])
159
- lsb = read_uint32be(amount[4, 4])
190
+ msb = BinaryCodec.read_uint32be(amount_bytes[0, 4])
191
+ lsb = BinaryCodec.read_uint32be(amount_bytes[4, 4])
160
192
  num = (msb << 32) | lsb
161
193
 
162
194
  return {
163
- value: "#{sign}#{num}",
164
- mpt_issuance_id: mpt_id.to_s
165
- }.to_s
195
+ "value" => "#{sign}#{num}",
196
+ "mpt_issuance_id" => mpt_id.to_hex
197
+ }
166
198
  end
167
199
 
168
200
  raise 'Invalid amount to construct JSON'
@@ -172,17 +204,23 @@ module BinaryCodec
172
204
 
173
205
  # Type guard for AmountObjectIOU
174
206
  def self.is_amount_object_iou?(arg)
175
- keys = arg.transform_keys(&:to_s).keys.sort
176
-
177
- keys.length == 3 &&
178
- keys[0] == 'currency' &&
179
- keys[1] == 'issuer' &&
180
- keys[2] == 'value'
207
+ return false unless arg.is_a?(::Hash)
208
+
209
+ # Handle both string and symbol keys
210
+ processed = arg.transform_keys(&:to_s)
211
+
212
+ # Log for debugging
213
+ # puts "DEBUG: Checking if #{processed.keys.inspect} is IOU"
214
+
215
+ processed.key?('currency') &&
216
+ processed.key?('issuer') &&
217
+ processed.key?('value')
181
218
  end
182
219
 
183
220
  # Type guard for AmountObjectMPT
184
221
  def self.is_amount_object_mpt?(arg)
185
- keys = arg.keys.sort
222
+ return false unless arg.is_a?(::Hash)
223
+ keys = arg.transform_keys(&:to_s).keys.sort
186
224
 
187
225
  keys.length == 2 &&
188
226
  keys[0] == 'mpt_issuance_id' &&
@@ -198,9 +236,9 @@ module BinaryCodec
198
236
  raise "#{amount} is an illegal amount"
199
237
  end
200
238
 
201
- decimal = BigDecimal(amount)
239
+ decimal = amount.to_i
202
240
  unless decimal.zero?
203
- if decimal < MIN_XRP || decimal > MAX_DROPS
241
+ if decimal < MIN_XRP_DROPS || decimal > MAX_XRP_DROPS
204
242
  raise "#{amount} is an illegal amount"
205
243
  end
206
244
  end
@@ -247,21 +285,25 @@ module BinaryCodec
247
285
  # Ensure that the value, after being multiplied by the exponent, does not
248
286
  # contain a decimal. This function is typically used to validate numbers
249
287
  # that need to be represented as precise integers after scaling, such as
250
- # amounts in financial transactions.
288
+ # amounts in financial transactions. Example failure:1.1234567891234567
251
289
  #
252
290
  # @param decimal [BigDecimal] A BigDecimal object
253
291
  # @raise [ArgumentError] if the value contains a decimal
254
292
  # @return [String] The decimal converted to a string without a decimal point
255
293
  def self.verify_no_decimal(decimal)
256
- exponent = -((decimal.exponent || 0) - 16)
257
- scaled_decimal = decimal * 10 ** exponent
258
-
259
- raise ArgumentError, 'Decimal place found in int_string' unless scaled_decimal.frac == 0
294
+ # p is the number of significant digits
295
+ # e is the power of 10 to multiply by the mantissa to get the number
296
+ # BigDecimal('1.1234567891234567').precision => 17
297
+ if decimal.precision > MAX_IOU_PRECISION
298
+ raise ArgumentError, 'Decimal precision out of range'
299
+ end
260
300
  end
261
301
 
262
302
  # Check if this amount is in units of Native Currency (XRP)
263
303
  #
264
304
  # @return [Boolean] true if Native (XRP)
305
+ # Checks if the amount is a native XRP amount.
306
+ # @return [Boolean] True if native, false otherwise.
265
307
  def is_native?
266
308
  (self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20).zero?
267
309
  end
@@ -269,6 +311,8 @@ module BinaryCodec
269
311
  # Check if this amount is in units of MPT
270
312
  #
271
313
  # @return [Boolean] true if MPT
314
+ # Checks if the amount is a multi-purpose token (MPT).
315
+ # @return [Boolean] True if MPT, false otherwise.
272
316
  def is_mpt?
273
317
  (self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20) != 0
274
318
  end
@@ -276,6 +320,8 @@ module BinaryCodec
276
320
  # Check if this amount is in units of IOU
277
321
  #
278
322
  # @return [Boolean] true if IOU
323
+ # Checks if the amount is an IOU amount.
324
+ # @return [Boolean] True if IOU, false otherwise.
279
325
  def is_iou?
280
326
  (self.bytes[0] & 0x80) != 0
281
327
  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?
@@ -2,24 +2,49 @@
2
2
 
3
3
  module BinaryCodec
4
4
  class Hash < ComparableSerializedType
5
+ # Returns the width of the Hash type in bytes.
6
+ # @return [Integer] The width.
7
+ def self.width
8
+ @width
9
+ end
5
10
 
6
- def initialize(bytes, width)
7
- @bytes = bytes
8
- @width = width
9
- if bytes.length != @width
10
- # raise StandardError, "Invalid Hash length #{bytes.length} for width #{@width}"
11
- raise StandardError, "Invalid Hash length #{bytes.length}"
11
+ def initialize(bytes = nil)
12
+ bytes = Array.new(self.class.width, 0) if bytes.nil? || bytes.empty?
13
+ super(bytes)
14
+ if @bytes.length != self.class.width
15
+ raise StandardError, "Invalid Hash length #{@bytes.length}"
12
16
  end
13
17
  end
14
18
 
15
- def self.from(hex_string)
16
- new(hex_to_bytes(hex_string))
19
+ # Creates a new Hash instance from a value.
20
+ # @param value [Hash, String, Array<Integer>] The value to convert.
21
+ # @return [Hash] The created instance.
22
+ def self.from(value)
23
+ return value if value.is_a?(self)
24
+
25
+ if value.is_a?(String)
26
+ return new if value.empty?
27
+ return new(hex_to_bytes(value))
28
+ end
29
+
30
+ if value.is_a?(::Array)
31
+ return new(value)
32
+ end
33
+
34
+ raise StandardError, "Cannot construct #{self} from the value given"
17
35
  end
18
36
 
37
+ # Creates a Hash instance from a parser.
38
+ # @param parser [BinaryParser] The parser to read from.
39
+ # @param hint [Integer, nil] Optional width hint.
40
+ # @return [Hash] The created instance.
19
41
  def self.from_parser(parser, hint = nil)
20
42
  new(parser.read(hint || width))
21
43
  end
22
44
 
45
+ # Compares this Hash to another Hash.
46
+ # @param other [Hash] The other Hash to compare to.
47
+ # @return [Integer] Comparison result (-1, 0, or 1).
23
48
  def compare_to(other)
24
49
  @bytes <=> other.bytes
25
50
  end
@@ -42,15 +67,9 @@ module BinaryCodec
42
67
 
43
68
  class Hash128 < Hash
44
69
  @width = 16
45
- @zero_128 = [0] * @width # Array.new(@width, 0)
46
-
47
- class << self
48
- attr_reader :width, :zero_128
49
- end
50
70
 
51
71
  def initialize(bytes = nil)
52
- bytes = self.class.zero_128 if bytes&.empty?
53
- super(bytes, self.class.width)
72
+ super(bytes)
54
73
  end
55
74
 
56
75
  def to_hex
@@ -62,43 +81,25 @@ module BinaryCodec
62
81
 
63
82
  class Hash160 < Hash
64
83
  @width = 20
65
- @zero_160 = [0] * @width # Array.new(@width, 0)
66
-
67
- class << self
68
- attr_reader :width, :zero_160
69
- end
70
84
 
71
85
  def initialize(bytes = nil)
72
- bytes = self.class.zero_160 if bytes&.empty?
73
- super(bytes, self.class.width)
86
+ super(bytes)
74
87
  end
75
88
  end
76
89
 
77
90
  class Hash192 < Hash
78
91
  @width = 24
79
- @zero_192 = [0] * @width # Array.new(@width, 0)
80
-
81
- class << self
82
- attr_reader :width, :zero_192
83
- end
84
92
 
85
93
  def initialize(bytes = nil)
86
- bytes = self.class.zero_192 if bytes&.empty?
87
- super(bytes, self.class.width)
94
+ super(bytes)
88
95
  end
89
96
  end
90
97
 
91
98
  class Hash256 < Hash
92
99
  @width = 32
93
- @zero_256 = [0] * @width # Array.new(@width, 0)
94
-
95
- class << self
96
- attr_reader :width, :zero_256
97
- end
98
100
 
99
101
  def initialize(bytes = nil)
100
- bytes = self.class.zero_256 if bytes&.empty?
101
- super(bytes, self.class.width)
102
+ super(bytes)
102
103
  end
103
104
  end
104
105
 
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BinaryCodec
4
+ class Issue < SerializedType
5
+ def initialize(bytes = nil)
6
+ super(bytes || [])
7
+ end
8
+
9
+ def self.from(value)
10
+ return value if value.is_a?(Issue)
11
+
12
+ if value.is_a?(String)
13
+ return Issue.new(hex_to_bytes(value))
14
+ end
15
+
16
+ if value.is_a?(Hash) || value.is_a?(::Hash)
17
+ bytes = []
18
+ bytes.concat(Currency.from(value['currency']).to_bytes)
19
+ bytes.concat(AccountId.from(value['issuer']).to_bytes) if value['issuer']
20
+ return Issue.new(bytes)
21
+ end
22
+
23
+ raise StandardError, "Cannot construct Issue from #{value.class}"
24
+ end
25
+
26
+ def self.from_parser(parser, size_hint = nil)
27
+ bytes = []
28
+ return Issue.new(bytes) if parser.end?
29
+ bytes.concat(parser.read(20)) # Currency
30
+ unless parser.end? || (size_hint && size_hint <= 20)
31
+ bytes.concat(parser.read(20))
32
+ end
33
+ Issue.new(bytes)
34
+ end
35
+
36
+ def to_json(_definitions = nil, _field_name = nil)
37
+ parser = BinaryParser.new(to_hex)
38
+ result = {}
39
+ result['currency'] = Currency.from_parser(parser).to_json
40
+ result['issuer'] = AccountId.from_parser(parser).to_json unless parser.end?
41
+ result
42
+ rescue
43
+ # Fallback for partial/invalid binary
44
+ { 'currency' => Currency.new(to_bytes[0, 20]).to_json }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BinaryCodec
4
+ class PathSet < SerializedType
5
+ PATH_STEP_END_MARKER = 0xFF
6
+ PATH_END_MARKER = 0xFE
7
+
8
+ TYPE_ACCOUNT = 0x01
9
+ TYPE_CURRENCY = 0x10
10
+ TYPE_ISSUER = 0x20
11
+
12
+ def initialize(bytes = nil)
13
+ super(bytes || [])
14
+ end
15
+
16
+ def self.from(value)
17
+ return value if value.is_a?(PathSet)
18
+
19
+ if value.is_a?(String)
20
+ return PathSet.new(hex_to_bytes(value))
21
+ end
22
+
23
+ if value.is_a?(::Array)
24
+ bytes = []
25
+ value.each_with_index do |path, index|
26
+ path.each do |step|
27
+ type = 0
28
+ step_bytes = []
29
+
30
+ if step['account']
31
+ type |= TYPE_ACCOUNT
32
+ step_bytes.concat(AccountId.from(step['account']).to_bytes)
33
+ end
34
+ if step['currency']
35
+ type |= TYPE_CURRENCY
36
+ step_bytes.concat(Currency.from(step['currency']).to_bytes)
37
+ end
38
+ if step['issuer']
39
+ type |= TYPE_ISSUER
40
+ step_bytes.concat(AccountId.from(step['issuer']).to_bytes)
41
+ end
42
+
43
+ bytes << type
44
+ bytes.concat(step_bytes)
45
+ end
46
+ bytes << (index == value.length - 1 ? PATH_END_MARKER : PATH_STEP_END_MARKER)
47
+ end
48
+ return PathSet.new(bytes)
49
+ end
50
+
51
+ raise StandardError, "Cannot construct PathSet from #{value.class}"
52
+ end
53
+
54
+ def self.from_parser(parser, _hint = nil)
55
+ bytes = []
56
+ loop do
57
+ type = parser.read_uint8
58
+ bytes << type
59
+ break if type == PATH_END_MARKER
60
+
61
+ if type != PATH_STEP_END_MARKER
62
+ bytes.concat(parser.read(20)) if (type & TYPE_ACCOUNT) != 0
63
+ bytes.concat(parser.read(20)) if (type & TYPE_CURRENCY) != 0
64
+ bytes.concat(parser.read(20)) if (type & TYPE_ISSUER) != 0
65
+ end
66
+ end
67
+ PathSet.new(bytes)
68
+ end
69
+
70
+ def to_json(_definitions = nil, _field_name = nil)
71
+ parser = BinaryParser.new(to_hex)
72
+ paths = []
73
+ current_path = []
74
+
75
+ until parser.end?
76
+ type = parser.read_uint8
77
+ if type == PATH_END_MARKER || type == PATH_STEP_END_MARKER
78
+ paths << current_path
79
+ current_path = []
80
+ break if type == PATH_END_MARKER
81
+ next
82
+ end
83
+
84
+ step = {}
85
+ step['account'] = AccountId.from_parser(parser).to_json if (type & TYPE_ACCOUNT) != 0
86
+ step['currency'] = Currency.from_parser(parser).to_json if (type & TYPE_CURRENCY) != 0
87
+ step['issuer'] = AccountId.from_parser(parser).to_json if (type & TYPE_ISSUER) != 0
88
+ current_path << step
89
+ end
90
+ paths
91
+ end
92
+ end
93
+ end