Class: XRPL::Transaction

Inherits:
Object
  • Object
show all
Defined in:
lib/xrpl/transaction.rb

Overview

A transaction, built from the field formats in definitions.json.

The subclasses below are not written by hand: one is created for each entry in TRANSACTION_FORMATS when this file loads, so syncing definitions.json updates the models with it and they cannot drift from the ledger.

tx = XRPL::Transaction::Payment.new(
  account: wallet.classic_address,
  destination: receiver.classic_address,
  amount: '1000000'
)
tx.validate!
client.submit_and_wait(tx, wallet: wallet)

Fields are given in snake_case and stored under the ledger's own PascalCase names, so #to_h hands the binary codec exactly what it expects. A plain Hash still works everywhere a transaction is accepted; these classes are an addition, not a replacement.

Defined Under Namespace

Classes: ValidationError

Constant Summary collapse

REQUIRED =

rippled's SOEStyle: what the format says about a field.

0
OPTIONAL =
1
DEFAULT =
2
DEFINITIONS =
BinaryCodec::Definitions.instance.raw
COMMON_FORMAT =

Fields every transaction carries, regardless of type.

DEFINITIONS['TRANSACTION_FORMATS'].fetch('common').freeze
SUPPLIED_LATER =

Required by the format, but supplied by autofill and signing rather than by the caller. Demanding them up front would make #validate! useless at the point where it is actually worth running.

%w[
  TransactionType Sequence Fee SigningPubKey TxnSignature LastLedgerSequence
].freeze
FIELD_TO_ACCESSOR =
DEFINITIONS['FIELDS'].to_h { |name, _| [name, underscore(name)] }.freeze
ACCESSOR_TO_FIELD =
FIELD_TO_ACCESSOR.invert.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fields = {}) ⇒ Transaction

Returns a new instance of Transaction.



94
95
96
97
# File 'lib/xrpl/transaction.rb', line 94

def initialize(fields = {})
  @fields = {}
  fields.each { |name, value| self[name] = value }
end

Class Attribute Details

.flagsObject (readonly)

Flag name -> bit, e.g. "tfPartialPayment" => 131072.



74
75
76
# File 'lib/xrpl/transaction.rb', line 74

def flags
  @flags
end

.formatObject (readonly)

Ledger field name -> optionality, including the common fields.



71
72
73
# File 'lib/xrpl/transaction.rb', line 71

def format
  @format
end

.transaction_typeObject (readonly)

The ledger's name for this transaction type, e.g. "Payment".



68
69
70
# File 'lib/xrpl/transaction.rb', line 68

def transaction_type
  @transaction_type
end

Class Method Details

.define_types!Object

Builds one subclass per transaction type, with an accessor for every field the type accepts and a constant for every flag it defines.



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/xrpl/transaction.rb', line 172

def self.define_types!
  DEFINITIONS['TRANSACTION_FORMATS'].each do |type, own_format|
    next if type == 'common'

    format = (COMMON_FORMAT + own_format)
             .to_h { |field| [field['name'], field['optionality']] }
             .freeze

    klass = Class.new(self)
    klass.instance_variable_set(:@transaction_type, type)
    klass.instance_variable_set(:@format, format)
    klass.instance_variable_set(:@flags, (DEFINITIONS['TRANSACTION_FLAGS'][type] || {}).freeze)

    format.each_key do |field|
      accessor = FIELD_TO_ACCESSOR[field] or next

      klass.define_method(accessor) { self[field] }
      klass.define_method("#{accessor}=") { |value| self[field] = value }
    end

    klass.flags.each do |name, bit|
      klass.const_set(underscore(name).upcase, bit)
    end

    const_set(type, klass)
  end
end

.for(type) ⇒ Object

The class for a transaction type name, or nil if the ledger has no such type.



79
80
81
# File 'lib/xrpl/transaction.rb', line 79

def self.for(type)
  const_get(type) if const_defined?(type, false)
end

.from(hash) ⇒ Object

Build the right subclass from a transaction hash, PascalCase or snake.

Raises:



84
85
86
87
88
89
90
91
92
# File 'lib/xrpl/transaction.rb', line 84

def self.from(hash)
  type = hash['TransactionType'] || hash[:TransactionType] || hash[:transaction_type]
  raise ValidationError, 'Transaction hash has no TransactionType' unless type

  klass = self.for(type.to_s)
  raise ValidationError, "Unknown transaction type #{type}" unless klass

  klass.new(hash)
end

.resolve(name) ⇒ Object

Translates an accessor name to the ledger's field name.



116
117
118
119
120
121
# File 'lib/xrpl/transaction.rb', line 116

def self.resolve(name)
  key = name.to_s
  return key if FIELD_TO_ACCESSOR.key?(key)

  ACCESSOR_TO_FIELD[key] || key
end

.underscore(name) ⇒ Object

Ledger field name -> snake_case accessor, and back.

XChain, NFToken and MPToken are brand names rather than acronyms, so they are folded to a single word the way the reference SDKs write them.



48
49
50
51
52
53
54
55
56
57
# File 'lib/xrpl/transaction.rb', line 48

def self.underscore(name)
  name
    .sub(/\AXChain/, 'Xchain')
    .sub(/\ANFToken/, 'Nftoken')
    .sub(/\AMPToken/, 'Mptoken')
    .gsub(/([A-Z]{2,})s(?=[A-Z]|\z)/) { "#{Regexp.last_match(1).capitalize}s" }
    .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
    .gsub(/([a-z\d])([A-Z])/, '\1_\2')
    .downcase
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



157
158
159
# File 'lib/xrpl/transaction.rb', line 157

def ==(other)
  other.is_a?(Transaction) && other.to_h == to_h
end

#[](name) ⇒ Object

Reads a field by accessor name, symbol or ledger name.



100
101
102
# File 'lib/xrpl/transaction.rb', line 100

def [](name)
  @fields[self.class.resolve(name)]
end

#[]=(name, value) ⇒ Object

Writes a field, rejecting anything the type does not define.



105
106
107
108
109
110
111
112
113
# File 'lib/xrpl/transaction.rb', line 105

def []=(name, value)
  field = self.class.resolve(name)

  unless self.class.format.key?(field)
    raise ValidationError, "#{self.class.transaction_type} has no field #{field}"
  end

  value.nil? ? @fields.delete(field) : @fields[field] = value
end

#hashObject



162
163
164
# File 'lib/xrpl/transaction.rb', line 162

def hash
  to_h.hash
end

#inspectObject



166
167
168
# File 'lib/xrpl/transaction.rb', line 166

def inspect
  "#<#{self.class.name} #{to_h.inspect}>"
end

#missing_fieldsObject

Fields the format requires that have not been set, ignoring the ones autofill and signing provide.



132
133
134
135
136
137
# File 'lib/xrpl/transaction.rb', line 132

def missing_fields
  self.class.format
      .select { |field, optionality| optionality == REQUIRED }
      .keys
      .reject { |field| SUPPLIED_LATER.include?(field) || @fields.key?(field) }
end

#to_blobObject

The serialised transaction, as hex.



153
154
155
# File 'lib/xrpl/transaction.rb', line 153

def to_blob
  BinaryCodec.json_to_binary(to_h)
end

#to_hObject Also known as: to_hash

The transaction as the binary codec wants it: ledger field names, with TransactionType filled in.



125
126
127
# File 'lib/xrpl/transaction.rb', line 125

def to_h
  { 'TransactionType' => self.class.transaction_type }.merge(@fields)
end

#valid?Boolean

Returns:

  • (Boolean)


139
140
141
# File 'lib/xrpl/transaction.rb', line 139

def valid?
  missing_fields.empty?
end

#validate!Object

Raises unless every required field is present.

Raises:



144
145
146
147
148
149
150
# File 'lib/xrpl/transaction.rb', line 144

def validate!
  missing = missing_fields
  return self if missing.empty?

  raise ValidationError,
        "#{self.class.transaction_type} is missing #{missing.join(', ')}"
end