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
@@ -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
- field_header = FieldHeader.new(type: @type_ordinals[field_info.type], nth: field_info.nth)
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
- @field_id_name_map[Digest::MD5.hexdigest(Marshal.dump(field_header))] = field_name
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
- def get_field_header_from_name(field_name)
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
- def get_field_name_from_header(field_header)
56
- @field_id_name_map[Digest::MD5.hexdigest(Marshal.dump(field_header))]
57
- end
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
- def get_field_instance(field_name)
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: (field_header.type << 16) | field_info.nth, # @type_ordinals[field_info.type],
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 # SerializedType::getTypeByName($this->type)::class;
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
- type = read_uint8
88
- nth = type & 15
89
- type >>= 4
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) # (type << 16) | nth for read_field_ordinal
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
- 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'
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
- 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