xrpl-ruby 0.6.0 → 0.7.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 +86 -0
- data/README.md +49 -0
- data/lib/binary-codec/enums/definitions.json +3716 -112
- data/lib/binary-codec/enums/definitions.rb +8 -0
- data/lib/binary-codec/types/amount.rb +5 -2
- data/lib/binary-codec/types/currency.rb +8 -1
- data/lib/binary-codec/types/issue.rb +68 -23
- data/lib/binary-codec/types/number.rb +196 -0
- data/lib/binary-codec/types/serialized_type.rb +5 -2
- data/lib/binary-codec/types/st_array.rb +17 -19
- data/lib/binary-codec/types/st_object.rb +15 -13
- data/lib/binary-codec/types/uint.rb +23 -5
- data/lib/binary-codec/types/xchain_bridge.rb +17 -2
- data/lib/xrpl/client.rb +17 -3
- data/lib/xrpl/transaction.rb +202 -0
- data/lib/xrpl/version.rb +1 -1
- data/lib/xrpl-ruby.rb +2 -0
- metadata +19 -3
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XRPL
|
|
4
|
+
# A transaction, built from the field formats in definitions.json.
|
|
5
|
+
#
|
|
6
|
+
# The subclasses below are not written by hand: one is created for each entry
|
|
7
|
+
# in TRANSACTION_FORMATS when this file loads, so syncing definitions.json
|
|
8
|
+
# updates the models with it and they cannot drift from the ledger.
|
|
9
|
+
#
|
|
10
|
+
# tx = XRPL::Transaction::Payment.new(
|
|
11
|
+
# account: wallet.classic_address,
|
|
12
|
+
# destination: receiver.classic_address,
|
|
13
|
+
# amount: '1000000'
|
|
14
|
+
# )
|
|
15
|
+
# tx.validate!
|
|
16
|
+
# client.submit_and_wait(tx, wallet: wallet)
|
|
17
|
+
#
|
|
18
|
+
# Fields are given in snake_case and stored under the ledger's own PascalCase
|
|
19
|
+
# names, so #to_h hands the binary codec exactly what it expects. A plain
|
|
20
|
+
# Hash still works everywhere a transaction is accepted; these classes are an
|
|
21
|
+
# addition, not a replacement.
|
|
22
|
+
class Transaction
|
|
23
|
+
# Raised when required fields are missing or a field is not part of the
|
|
24
|
+
# transaction type.
|
|
25
|
+
class ValidationError < StandardError; end
|
|
26
|
+
|
|
27
|
+
# rippled's SOEStyle: what the format says about a field.
|
|
28
|
+
REQUIRED = 0
|
|
29
|
+
OPTIONAL = 1
|
|
30
|
+
DEFAULT = 2
|
|
31
|
+
|
|
32
|
+
DEFINITIONS = BinaryCodec::Definitions.instance.raw
|
|
33
|
+
|
|
34
|
+
# Fields every transaction carries, regardless of type.
|
|
35
|
+
COMMON_FORMAT = DEFINITIONS['TRANSACTION_FORMATS'].fetch('common').freeze
|
|
36
|
+
|
|
37
|
+
# Required by the format, but supplied by autofill and signing rather than
|
|
38
|
+
# by the caller. Demanding them up front would make #validate! useless at
|
|
39
|
+
# the point where it is actually worth running.
|
|
40
|
+
SUPPLIED_LATER = %w[
|
|
41
|
+
TransactionType Sequence Fee SigningPubKey TxnSignature LastLedgerSequence
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
44
|
+
# Ledger field name -> snake_case accessor, and back.
|
|
45
|
+
#
|
|
46
|
+
# XChain, NFToken and MPToken are brand names rather than acronyms, so they
|
|
47
|
+
# are folded to a single word the way the reference SDKs write them.
|
|
48
|
+
def self.underscore(name)
|
|
49
|
+
name
|
|
50
|
+
.sub(/\AXChain/, 'Xchain')
|
|
51
|
+
.sub(/\ANFToken/, 'Nftoken')
|
|
52
|
+
.sub(/\AMPToken/, 'Mptoken')
|
|
53
|
+
.gsub(/([A-Z]{2,})s(?=[A-Z]|\z)/) { "#{Regexp.last_match(1).capitalize}s" }
|
|
54
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
55
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
56
|
+
.downcase
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
FIELD_TO_ACCESSOR = DEFINITIONS['FIELDS'].to_h { |name, _| [name, underscore(name)] }.freeze
|
|
60
|
+
ACCESSOR_TO_FIELD = FIELD_TO_ACCESSOR.invert.freeze
|
|
61
|
+
|
|
62
|
+
if ACCESSOR_TO_FIELD.size != FIELD_TO_ACCESSOR.size
|
|
63
|
+
raise "Ambiguous accessor names in definitions.json"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class << self
|
|
67
|
+
# The ledger's name for this transaction type, e.g. "Payment".
|
|
68
|
+
attr_reader :transaction_type
|
|
69
|
+
|
|
70
|
+
# Ledger field name -> optionality, including the common fields.
|
|
71
|
+
attr_reader :format
|
|
72
|
+
|
|
73
|
+
# Flag name -> bit, e.g. "tfPartialPayment" => 131072.
|
|
74
|
+
attr_reader :flags
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The class for a transaction type name, or nil if the ledger has no such
|
|
78
|
+
# type.
|
|
79
|
+
def self.for(type)
|
|
80
|
+
const_get(type) if const_defined?(type, false)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Build the right subclass from a transaction hash, PascalCase or snake.
|
|
84
|
+
def self.from(hash)
|
|
85
|
+
type = hash['TransactionType'] || hash[:TransactionType] || hash[:transaction_type]
|
|
86
|
+
raise ValidationError, 'Transaction hash has no TransactionType' unless type
|
|
87
|
+
|
|
88
|
+
klass = self.for(type.to_s)
|
|
89
|
+
raise ValidationError, "Unknown transaction type #{type}" unless klass
|
|
90
|
+
|
|
91
|
+
klass.new(hash)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def initialize(fields = {})
|
|
95
|
+
@fields = {}
|
|
96
|
+
fields.each { |name, value| self[name] = value }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Reads a field by accessor name, symbol or ledger name.
|
|
100
|
+
def [](name)
|
|
101
|
+
@fields[self.class.resolve(name)]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Writes a field, rejecting anything the type does not define.
|
|
105
|
+
def []=(name, value)
|
|
106
|
+
field = self.class.resolve(name)
|
|
107
|
+
|
|
108
|
+
unless self.class.format.key?(field)
|
|
109
|
+
raise ValidationError, "#{self.class.transaction_type} has no field #{field}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
value.nil? ? @fields.delete(field) : @fields[field] = value
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Translates an accessor name to the ledger's field name.
|
|
116
|
+
def self.resolve(name)
|
|
117
|
+
key = name.to_s
|
|
118
|
+
return key if FIELD_TO_ACCESSOR.key?(key)
|
|
119
|
+
|
|
120
|
+
ACCESSOR_TO_FIELD[key] || key
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# The transaction as the binary codec wants it: ledger field names, with
|
|
124
|
+
# TransactionType filled in.
|
|
125
|
+
def to_h
|
|
126
|
+
{ 'TransactionType' => self.class.transaction_type }.merge(@fields)
|
|
127
|
+
end
|
|
128
|
+
alias to_hash to_h
|
|
129
|
+
|
|
130
|
+
# Fields the format requires that have not been set, ignoring the ones
|
|
131
|
+
# autofill and signing provide.
|
|
132
|
+
def missing_fields
|
|
133
|
+
self.class.format
|
|
134
|
+
.select { |field, optionality| optionality == REQUIRED }
|
|
135
|
+
.keys
|
|
136
|
+
.reject { |field| SUPPLIED_LATER.include?(field) || @fields.key?(field) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def valid?
|
|
140
|
+
missing_fields.empty?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Raises unless every required field is present.
|
|
144
|
+
def validate!
|
|
145
|
+
missing = missing_fields
|
|
146
|
+
return self if missing.empty?
|
|
147
|
+
|
|
148
|
+
raise ValidationError,
|
|
149
|
+
"#{self.class.transaction_type} is missing #{missing.join(', ')}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# The serialised transaction, as hex.
|
|
153
|
+
def to_blob
|
|
154
|
+
BinaryCodec.json_to_binary(to_h)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def ==(other)
|
|
158
|
+
other.is_a?(Transaction) && other.to_h == to_h
|
|
159
|
+
end
|
|
160
|
+
alias eql? ==
|
|
161
|
+
|
|
162
|
+
def hash
|
|
163
|
+
to_h.hash
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def inspect
|
|
167
|
+
"#<#{self.class.name} #{to_h.inspect}>"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Builds one subclass per transaction type, with an accessor for every
|
|
171
|
+
# field the type accepts and a constant for every flag it defines.
|
|
172
|
+
def self.define_types!
|
|
173
|
+
DEFINITIONS['TRANSACTION_FORMATS'].each do |type, own_format|
|
|
174
|
+
next if type == 'common'
|
|
175
|
+
|
|
176
|
+
format = (COMMON_FORMAT + own_format)
|
|
177
|
+
.to_h { |field| [field['name'], field['optionality']] }
|
|
178
|
+
.freeze
|
|
179
|
+
|
|
180
|
+
klass = Class.new(self)
|
|
181
|
+
klass.instance_variable_set(:@transaction_type, type)
|
|
182
|
+
klass.instance_variable_set(:@format, format)
|
|
183
|
+
klass.instance_variable_set(:@flags, (DEFINITIONS['TRANSACTION_FLAGS'][type] || {}).freeze)
|
|
184
|
+
|
|
185
|
+
format.each_key do |field|
|
|
186
|
+
accessor = FIELD_TO_ACCESSOR[field] or next
|
|
187
|
+
|
|
188
|
+
klass.define_method(accessor) { self[field] }
|
|
189
|
+
klass.define_method("#{accessor}=") { |value| self[field] = value }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
klass.flags.each do |name, bit|
|
|
193
|
+
klass.const_set(underscore(name).upcase, bit)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
const_set(type, klass)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
define_types!
|
|
201
|
+
end
|
|
202
|
+
end
|
data/lib/xrpl/version.rb
CHANGED
data/lib/xrpl-ruby.rb
CHANGED
|
@@ -21,6 +21,7 @@ require_relative 'binary-codec/serdes/binary_serializer'
|
|
|
21
21
|
require_relative 'binary-codec/types/serialized_type'
|
|
22
22
|
require_relative 'binary-codec/types/hash'
|
|
23
23
|
require_relative 'binary-codec/types/uint'
|
|
24
|
+
require_relative 'binary-codec/types/number'
|
|
24
25
|
require_relative 'binary-codec/types/account_id'
|
|
25
26
|
require_relative 'binary-codec/types/amount'
|
|
26
27
|
require_relative 'binary-codec/types/blob'
|
|
@@ -39,5 +40,6 @@ require_relative 'key-pairs/ed25519'
|
|
|
39
40
|
require_relative 'key-pairs/secp256k1'
|
|
40
41
|
require_relative 'key-pairs/key_pairs'
|
|
41
42
|
|
|
43
|
+
require_relative 'xrpl/transaction'
|
|
42
44
|
require_relative 'xrpl/client'
|
|
43
45
|
require_relative 'xrpl/faucet'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xrpl-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Busse
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -52,6 +52,20 @@ dependencies:
|
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: bigdecimal
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.1'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.1'
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: ed25519
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -137,6 +151,7 @@ files:
|
|
|
137
151
|
- lib/binary-codec/types/currency.rb
|
|
138
152
|
- lib/binary-codec/types/hash.rb
|
|
139
153
|
- lib/binary-codec/types/issue.rb
|
|
154
|
+
- lib/binary-codec/types/number.rb
|
|
140
155
|
- lib/binary-codec/types/path_set.rb
|
|
141
156
|
- lib/binary-codec/types/serialized_type.rb
|
|
142
157
|
- lib/binary-codec/types/st_array.rb
|
|
@@ -156,6 +171,7 @@ files:
|
|
|
156
171
|
- lib/xrpl-ruby.rb
|
|
157
172
|
- lib/xrpl/client.rb
|
|
158
173
|
- lib/xrpl/faucet.rb
|
|
174
|
+
- lib/xrpl/transaction.rb
|
|
159
175
|
- lib/xrpl/version.rb
|
|
160
176
|
homepage: https://github.com/AlexanderBuzz/xrpl-ruby
|
|
161
177
|
licenses:
|
|
@@ -173,7 +189,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
173
189
|
requirements:
|
|
174
190
|
- - ">="
|
|
175
191
|
- !ruby/object:Gem::Version
|
|
176
|
-
version: '3.
|
|
192
|
+
version: '3.2'
|
|
177
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
194
|
requirements:
|
|
179
195
|
- - ">="
|