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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6895a3507b4f4d76bb12cd9c150c2f395a35e44146ee45dea5579076c780fc56
4
- data.tar.gz: a641537d7f0ce171f7ddf5380b0e3ab0d396a5a2ec5f884e090fe12ba1b49eb6
3
+ metadata.gz: a26ce8102c50640eec9433b64a9296c7d92635d66c42f2995456d913fff60d75
4
+ data.tar.gz: 60d3c026f1378a1d7cb79adcd6bbf830d518bb554b35286fe980010a14f4f11a
5
5
  SHA512:
6
- metadata.gz: 453610629153989c93ff0ac3e504dfd4fa8fb90ccc3adbbac104522dee5b03192a7a6ba1d4206424f17174080ece7b897fc02b5bd513432979269df0dba7768b
7
- data.tar.gz: ca5696e3df36e747e395f351043223d81a7445970db5d8b40d3cd398f9a1c70bff3495f926dea412920bb81cdfdbb78a825c9b3afa13e6b381ba84ea04ecfec4
6
+ metadata.gz: 975f1af5db2890d44ba6ac7266c2a927dc5e8bf4d3000a8e70bd864734dbba1e90495718b48300df5ca183077076de844b00c2844aa87c050d61263d5e43a12a
7
+ data.tar.gz: 85a70a5ebc31bde02941394182c93aeb1c09a57987fbadfcbac26efdd16e1b30fe786d1aefdefd2ee1fa3cf742b18dbde2edbb2983052869b8165411b6207fd5
data/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.6.0] - 2026-08-05
9
+
10
+ ### Added
11
+ - **Connection readiness**: `Client#connect!` (and `connect(wait: true)`) block until the
12
+ WebSocket connection is open; `#open?` and `#wait_until_open` expose the state, so requests
13
+ no longer race with connection setup.
14
+ - **Faucet helper**: `XRPL.fund_wallet(client)` / `XRPL::Faucet` creates and funds a wallet on
15
+ the Testnet and waits until the account is funded on the ledger.
16
+ - **Transaction lifecycle** on the client (client-centric design): `Client#autofill`,
17
+ `Client#submit`, and `Client#submit_and_wait` (reliable submission that polls until the
18
+ transaction is included in a validated ledger).
19
+ - **Optional logger**: `Client.new(url, logger:)` — the library is silent by default and only
20
+ emits diagnostics through an injected logger.
21
+ - Example scripts for funding a wallet, querying account info, and sending a payment.
22
+
23
+ ### Changed
24
+ - The library no longer writes to `stdout` on its own; connection messages go through the
25
+ optional logger instead.
26
+
27
+ ## [0.5.2]
28
+
29
+ ### Added
30
+ - Binary codec, address codec, key pairs (secp256k1 / ed25519), wallet, and a WebSocket
31
+ client with account and ledger public API wrappers.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Alexander Busse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # XRPL-Ruby
2
+
3
+ A pure-Ruby library to interact with the [XRP Ledger](https://xrpl.org) (XRPL) blockchain.
4
+
5
+ ## Features
6
+
7
+ - Key and wallet management (secp256k1 and ed25519)
8
+ - Address codec and binary (transaction) codec
9
+ - WebSocket client for the XRP Ledger public API
10
+ - Testnet faucet helper to create and fund wallets
11
+ - Transaction lifecycle: autofill, sign, submit, and reliable "submit and wait"
12
+
13
+ ## Requirements
14
+
15
+ - Ruby 3.0 or later
16
+
17
+ ## Installation
18
+
19
+ Install the gem:
20
+
21
+ ```sh
22
+ gem install xrpl-ruby
23
+ ```
24
+
25
+ Or add it to your `Gemfile`:
26
+
27
+ ```ruby
28
+ gem 'xrpl-ruby'
29
+ ```
30
+
31
+ ## Quick start
32
+
33
+ ```ruby
34
+ require 'xrpl-ruby'
35
+
36
+ # 1. Connect to the Testnet (blocks until the connection is ready)
37
+ client = XRPL::Client.new(:testnet)
38
+ client.connect!
39
+
40
+ # 2. Create and fund a wallet using the Testnet faucet
41
+ wallet = XRPL.fund_wallet(client)[:wallet]
42
+ puts wallet.classic_address
43
+
44
+ # 3. Look up the account on the ledger
45
+ info = client.account_info_response(
46
+ account: wallet.classic_address,
47
+ ledger_index: 'validated'
48
+ )
49
+ puts info.dig('result', 'account_data', 'Balance')
50
+
51
+ # 4. Send 1 XRP (1,000,000 drops) and wait for validation
52
+ receiver = XRPL.fund_wallet(client)[:wallet]
53
+ payment = {
54
+ 'TransactionType' => 'Payment',
55
+ 'Account' => wallet.classic_address,
56
+ 'Destination' => receiver.classic_address,
57
+ 'Amount' => '1000000'
58
+ }
59
+ result = client.submit_and_wait(payment, wallet: wallet)
60
+ puts result.dig('result', 'meta', 'TransactionResult') # => "tesSUCCESS"
61
+
62
+ client.disconnect
63
+ ```
64
+
65
+ The client is silent by default. To see diagnostic output, pass a logger:
66
+
67
+ ```ruby
68
+ require 'logger'
69
+ client = XRPL::Client.new(:testnet, logger: Logger.new($stdout))
70
+ ```
71
+
72
+ More runnable examples are in the [`examples/`](examples) directory.
73
+
74
+ ## Running the tests
75
+
76
+ ```sh
77
+ bundle install
78
+ bundle exec rspec
79
+ ```
80
+
81
+ Integration tests talk to the real Testnet and are skipped by default. Enable them explicitly:
82
+
83
+ ```sh
84
+ XRPL_NETWORK=1 bundle exec rspec spec/integration
85
+ ```
86
+
87
+ ## Contributing
88
+
89
+ Bug reports and pull requests are welcome on GitHub at
90
+ <https://github.com/AlexanderBuzz/xrpl-ruby>.
91
+
92
+ ## License
93
+
94
+ Released under the [MIT License](LICENSE).
@@ -11,21 +11,31 @@ module AddressCodec
11
11
 
12
12
  MAX_32_BIT_UNSIGNED_INT = 4294967295
13
13
 
14
+ # Converts a classic address to an X-address.
15
+ # @param classic_address [String] The classic XRPL address to convert.
16
+ # @param tag [Integer, false, nil] The destination tag.
17
+ # @param test [Boolean] Whether the address is for a test network.
18
+ # @return [String] The encoded X-address.
14
19
  def classic_address_to_x_address(classic_address, tag, test)
15
20
  account_id = decode_account_id(classic_address)
16
21
  encode_x_address(account_id, tag, test)
17
22
  end
18
23
 
24
+ # Encodes an account ID and tag into an X-address.
25
+ # @param account_id [Array<Integer>] 20 bytes for the account ID.
26
+ # @param tag [Integer, false, nil] The destination tag.
27
+ # @param test [Boolean] Whether the address is for a test network.
28
+ # @return [String] The encoded X-address.
19
29
  def encode_x_address(account_id, tag, test)
20
30
  if account_id.length != 20
21
31
  # RIPEMD160 -> 160 Bits = 20 Bytes
22
32
  raise 'Account ID must be 20 bytes'
23
33
  end
24
- if tag != false && tag > MAX_32_BIT_UNSIGNED_INT
34
+ if tag && tag != false && tag > MAX_32_BIT_UNSIGNED_INT
25
35
  raise 'Invalid tag'
26
36
  end
27
37
  the_tag = tag || 0
28
- flag = tag == false || tag.nil? ? 0 : 1
38
+ flag = (tag == false || tag.nil?) ? 0 : 1
29
39
 
30
40
  bytes = concat_args(
31
41
  test ? PREFIX_BYTES[:test] : PREFIX_BYTES[:main],
@@ -46,6 +56,9 @@ module AddressCodec
46
56
  encode_checked(bytes)
47
57
  end
48
58
 
59
+ # Converts an X-address to a classic address.
60
+ # @param x_address [String] The X-address string to convert.
61
+ # @return [Hash] A hash containing :classic_address, :tag, and :test.
49
62
  def x_address_to_classic_address(x_address)
50
63
  decoded = decode_x_address(x_address)
51
64
  account_id = decoded[:account_id]
@@ -59,6 +72,9 @@ module AddressCodec
59
72
  }
60
73
  end
61
74
 
75
+ # Decodes an X-address into its underlying account ID, tag, and network type.
76
+ # @param x_address [String] The X-address string to decode.
77
+ # @return [Hash] A hash containing :account_id, :tag, and :test.
62
78
  def decode_x_address(x_address)
63
79
  decoded = decode_checked(x_address)
64
80
  test = is_uint8_array_for_test_address(decoded)
@@ -71,6 +87,9 @@ module AddressCodec
71
87
  }
72
88
  end
73
89
 
90
+ # Checks if a string is a valid X-address.
91
+ # @param x_address [String] The X-address string to check.
92
+ # @return [Boolean] True if the X-address is valid, false otherwise.
74
93
  def valid_x_address?(x_address)
75
94
  begin
76
95
  decode_x_address(x_address)
@@ -97,11 +116,9 @@ module AddressCodec
97
116
  def tag_from_uint8_array(bytes)
98
117
  flag = bytes[22]
99
118
  if flag >= 2
100
- # Keine Unterstützung für 64-Bit-Tags zu diesem Zeitpunkt
101
119
  raise 'Unsupported X-address'
102
120
  end
103
121
  if flag == 1
104
- # Little-endian zu Big-endian
105
122
  return bytes[23] + bytes[24] * 0x100 + bytes[25] * 0x10000 + bytes[26] * 0x1000000
106
123
  end
107
124
  if flag != 0
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  require 'digest'
4
- require_relative "../core/core"
5
4
 
6
5
  module AddressCodec
7
6
  class Codec
@@ -10,11 +9,19 @@ module AddressCodec
10
9
  @codec = Core::Base58XRP.new
11
10
  end
12
11
 
12
+ # Encodes a byte array into a base58 string with a version prefix and checksum.
13
+ # @param bytes [Array<Integer>] The byte array to encode.
14
+ # @param opts [Hash] Options for encoding (e.g., :versions, :expected_length).
15
+ # @return [String] The encoded base58 string.
13
16
  def encode(bytes, opts)
14
17
  versions = opts[:versions]
15
18
  encode_versioned(bytes, versions, opts[:expected_length])
16
19
  end
17
20
 
21
+ # Decodes a base58 string and verifies its version and checksum.
22
+ # @param base58string [String] The base58 string to decode.
23
+ # @param opts [Hash] Options for decoding (e.g., :versions, :version_types, :expected_length).
24
+ # @return [Hash] The decoded data including version, bytes, and type.
18
25
  def decode(base58string, opts)
19
26
  versions = opts[:versions]
20
27
  types = opts[:version_types]
@@ -44,11 +51,17 @@ module AddressCodec
44
51
  raise 'version_invalid: version bytes do not match any of the provided version(s)'
45
52
  end
46
53
 
54
+ # Encodes a byte array into a base58 string with a checksum.
55
+ # @param bytes [Array<Integer>] The byte array to encode.
56
+ # @return [String] The encoded base58 string.
47
57
  def encode_checked(bytes)
48
58
  check = sha256(sha256(bytes))[0, 4]
49
59
  encode_raw(bytes + check)
50
60
  end
51
61
 
62
+ # Decodes a base58 string and verifies its checksum.
63
+ # @param base58string [String] The base58 string to decode.
64
+ # @return [Array<Integer>] The decoded byte array (without checksum).
52
65
  def decode_checked(base58string)
53
66
  bytes = decode_raw(base58string)
54
67
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../core/core"
4
-
5
3
  module AddressCodec
6
4
 
7
5
  class XrpCodec < Codec
@@ -13,6 +11,10 @@ module AddressCodec
13
11
  NODE_PUBLIC = 0x1c # 28; Validation public key (33 bytes)
14
12
  ED25519_SEED = [0x01, 0xe1, 0x4b].freeze # [1, 225, 75]
15
13
 
14
+ # Encodes entropy into a seed string.
15
+ # @param entropy [Array<Integer>] 16 bytes of entropy.
16
+ # @param type [String, nil] The seed type ('ed25519' or 'secp256k1').
17
+ # @return [String] The encoded seed string.
16
18
  def encode_seed(entropy, type = nil)
17
19
  unless check_byte_length(entropy, 16)
18
20
  raise 'entropy must have length 16'
@@ -27,6 +29,10 @@ module AddressCodec
27
29
  encode(entropy, opts)
28
30
  end
29
31
 
32
+ # Decodes a seed string into its underlying bytes and type.
33
+ # @param seed [String] The seed string to decode.
34
+ # @param opts [Hash] Options for decoding (e.g., :versions, :version_types, :expected_length).
35
+ # @return [Hash] The decoded data including version, bytes, and type.
30
36
  def decode_seed(seed, opts = {
31
37
  version_types: ['ed25519', 'secp256k1'],
32
38
  versions: [ED25519_SEED, FAMILY_SEED],
@@ -35,36 +41,57 @@ module AddressCodec
35
41
  decode(seed, opts)
36
42
  end
37
43
 
44
+ # Encodes a byte array into an account ID string.
45
+ # @param bytes [Array<Integer>] 20 bytes for the account ID.
46
+ # @return [String] The encoded account ID string.
38
47
  def encode_account_id(bytes)
39
48
  opts = { versions: [ACCOUNT_ID], expected_length: 20 }
40
49
  encode(bytes, opts)
41
50
  end
42
51
 
52
+ # Decodes an account ID string into its underlying bytes.
53
+ # @param account_id [String] The account ID string to decode.
54
+ # @return [Array<Integer>] The decoded bytes.
43
55
  def decode_account_id(account_id)
44
56
  opts = { versions: [ACCOUNT_ID], expected_length: 20 }
45
57
  decode(account_id, opts)[:bytes]
46
58
  end
47
59
 
60
+ # Decodes a node public key string into its underlying bytes.
61
+ # @param base58string [String] The node public key string to decode.
62
+ # @return [Array<Integer>] The decoded bytes.
48
63
  def decode_node_public(base58string)
49
64
  opts = { versions: [NODE_PUBLIC], expected_length: 33 }
50
65
  decode(base58string, opts)[:bytes]
51
66
  end
52
67
 
68
+ # Encodes a byte array into a node public key string.
69
+ # @param bytes [Array<Integer>] 33 bytes for the node public key.
70
+ # @return [String] The encoded node public key string.
53
71
  def encode_node_public(bytes)
54
72
  opts = { versions: [NODE_PUBLIC], expected_length: 33 }
55
73
  encode(bytes, opts)
56
74
  end
57
75
 
76
+ # Encodes a byte array into an account public key string.
77
+ # @param bytes [Array<Integer>] 33 bytes for the account public key.
78
+ # @return [String] The encoded account public key string.
58
79
  def encode_account_public(bytes)
59
80
  opts = { versions: [ACCOUNT_PUBLIC_KEY], expected_length: 33 }
60
81
  encode(bytes, opts)
61
82
  end
62
83
 
84
+ # Decodes an account public key string into its underlying bytes.
85
+ # @param base58string [String] The account public key string to decode.
86
+ # @return [Array<Integer>] The decoded bytes.
63
87
  def decode_account_public(base58string)
64
88
  opts = { versions: [ACCOUNT_PUBLIC_KEY], expected_length: 33 }
65
89
  decode(base58string, opts)[:bytes]
66
90
  end
67
91
 
92
+ # Checks if a string is a valid classic XRPL address.
93
+ # @param address [String] The address string to check.
94
+ # @return [Boolean] True if the address is valid, false otherwise.
68
95
  def valid_classic_address?(address)
69
96
  begin
70
97
  decode_account_id(address)
@@ -1,38 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BinaryCodec
4
- class BinaryCodec
5
-
6
- # from here: https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/binary.ts
4
+ # Hash prefixes for serialization.
5
+ HASH_PREFIX = {
6
+ transaction_sig: 0x53545800, # 'STX\0'
7
+ transaction_multi_sig: 0x534D5400, # 'SMT\0'
8
+ validation: 0x56414C00, # 'VAL\0'
9
+ proposal: 0x50525000 # 'PRP\0'
10
+ }.freeze
7
11
 
12
+ # from here: https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/binary.ts
13
+ class << self
14
+ # Creates a BinaryParser for the given bytes.
15
+ # @param bytes [String, Array<Integer>] The bytes to parse (hex string or byte array).
16
+ # @param definitions [Definitions, nil] Optional definitions.
17
+ # @return [BinaryParser] The created parser.
8
18
  def make_parser(bytes, definitions = nil)
9
19
  BinaryParser.new(bytes.is_a?(String) ? bytes : bytes_to_hex(bytes))
10
20
  end
11
21
 
12
- #def read_json(parser, definitions = DEFAULT_DEFINITIONS)
13
- def read_json(parser)
14
- # parser.read_type(core_types[:STObject]).to_json(definitions)
15
- parser.read_type(core_types[:STObject]).to_json()
16
- end
17
-
22
+ # Converts a hex string to its JSON representation.
23
+ # @param hex [String] The hex string to convert.
24
+ # @return [Hash] The decoded JSON object.
18
25
  def binary_to_json(hex)
19
26
  parser = make_parser(hex)
20
- read_json(parser)
27
+ st_object = SerializedType.get_type_by_name('STObject')
28
+ result = st_object.from_parser(parser).to_json
29
+ result = JSON.generate(result) unless result.is_a?(String)
30
+ JSON.parse(result)
21
31
  end
22
32
 
23
- def serialize_object(obj, definitions = nil)
24
- BytesList.new.put_type(core_types[:STObject], obj)
33
+ # Converts a JSON object to its binary representation.
34
+ # @param json [Hash] The JSON object to convert.
35
+ # @return [String] The serialized hex string.
36
+ def json_to_binary(json)
37
+ st_object = SerializedType.get_type_by_name('STObject')
38
+ st_object.from(json).to_hex
25
39
  end
26
40
 
27
- def signing_data(transaction, prefix = HashPrefix[:transaction_sig], opts = {})
28
- serialize_object(
29
- transaction,
30
- prefix: prefix,
31
- signing_fields_only: true,
32
- definitions: opts[:definitions]
33
- )
34
- end
41
+ # Generates signing data for a transaction.
42
+ # @param transaction [Hash] The transaction to serialize.
43
+ # @param prefix [Integer] The prefix to add to the serialized data.
44
+ # @param opts [Hash] Optional settings (e.g., :definitions, :signing_fields_only).
45
+ # @return [Array<Integer>] The serialized signing data.
46
+ def signing_data(transaction, prefix = HASH_PREFIX[:transaction_sig], opts = {})
47
+ # 1. Start with the prefix bytes
48
+ prefix_bytes = int_to_bytes(prefix, 4)
35
49
 
36
- end
50
+ # 2. Serialize the object, only including signing fields
51
+ st_object_class = SerializedType.get_type_by_name('STObject')
37
52
 
53
+ filter = if opts[:signing_fields_only]
54
+ lambda { |field_name| Definitions.instance.get_field_instance(field_name).is_signing_field }
55
+ else
56
+ nil
57
+ end
58
+
59
+ serialized_bytes = st_object_class.from(transaction, filter).to_bytes
60
+
61
+ prefix_bytes + serialized_bytes
62
+ end
63
+ end
38
64
  end